or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

backend-context.mdcloudpickle-integration.mderror-handling.mdindex.mdprocess-pool-executor.mdreusable-executor.md

cloudpickle-integration.mddocs/

0

# Cloudpickle Integration

1

2

Loky provides transparent integration with cloudpickle to handle serialization of functions and objects that cannot be pickled with the standard library. This enables parallel execution of interactively defined functions, lambda expressions, and complex objects.

3

4

## Capabilities

5

6

### Wrap Non-Picklable Objects

7

8

Wraps objects that cannot be pickled with standard pickle using cloudpickle for serialization.

9

10

```python { .api }

11

def wrap_non_picklable_objects(obj, keep_wrapper=True):

12

"""

13

Wrapper for non-picklable objects to use cloudpickle for serialization.

14

15

Parameters:

16

- obj: The object to wrap (function, class, or instance)

17

- keep_wrapper (bool): Whether to keep the wrapper after deserialization

18

19

Returns:

20

CloudpickledObjectWrapper or CloudpickledClassWrapper: Wrapped object

21

22

Note:

23

This wrapper tends to slow down serialization as cloudpickle is typically

24

slower than pickle. The proper solution is to avoid defining functions in

25

main scripts and implement __reduce__ methods for complex classes.

26

"""

27

```

28

29

### Set Custom Pickler

30

31

Configure a custom pickler for loky's inter-process communication.

32

33

```python { .api }

34

def set_loky_pickler(loky_pickler=None):

35

"""

36

Set the pickler used by loky for inter-process communication.

37

38

Parameters:

39

- loky_pickler (optional): Custom pickler class. If None, resets to default

40

41

Returns:

42

None

43

"""

44

45

def register(type_, reduce_function):

46

"""

47

Register a reduce function for objects of the given type.

48

49

Parameters:

50

- type_: The type to register a reducer for

51

- reduce_function: Function that reduces objects of the given type

52

53

Returns:

54

None

55

"""

56

57

def dump(obj, file, reducers=None, protocol=None):

58

"""

59

Pickle an object to a file using loky's pickling system.

60

61

Parameters:

62

- obj: Object to pickle

63

- file: File-like object to write to

64

- reducers (dict, optional): Custom reducers to use

65

- protocol (int, optional): Pickle protocol version

66

67

Returns:

68

None

69

"""

70

71

def dumps(obj, reducers=None, protocol=None):

72

"""

73

Pickle an object to bytes using loky's pickling system.

74

75

Parameters:

76

- obj: Object to pickle

77

- reducers (dict, optional): Custom reducers to use

78

- protocol (int, optional): Pickle protocol version

79

80

Returns:

81

bytes: Pickled object as bytes

82

"""

83

```

84

85

### Wrapper Classes

86

87

Internal wrapper classes for handling non-picklable objects.

88

89

```python { .api }

90

class CloudpickledObjectWrapper:

91

"""Base wrapper for objects that need cloudpickle serialization."""

92

def __init__(self, obj, keep_wrapper=False): ...

93

def __reduce__(self): ...

94

def __getattr__(self, attr): ...

95

96

class CallableObjectWrapper(CloudpickledObjectWrapper):

97

"""Wrapper that preserves callable property of wrapped objects."""

98

def __call__(self, *args, **kwargs): ...

99

```

100

101

## Usage Examples

102

103

### Wrapping Lambda Functions

104

105

```python

106

from loky import get_reusable_executor, wrap_non_picklable_objects

107

108

# Lambda functions cannot be pickled with standard pickle

109

lambda_func = lambda x: x * x + 1

110

111

# Wrap the lambda for use with loky

112

wrapped_lambda = wrap_non_picklable_objects(lambda_func)

113

114

executor = get_reusable_executor(max_workers=2)

115

116

# Use wrapped lambda in parallel execution

117

results = list(executor.map(wrapped_lambda, range(10)))

118

print(f"Lambda results: {results}")

119

```

120

121

### Wrapping Interactively Defined Functions

122

123

```python

124

from loky import get_reusable_executor, wrap_non_picklable_objects

125

126

# Function defined in interactive session or __main__ module

127

def interactive_function(x, multiplier=2):

128

"""This function is defined interactively and needs wrapping."""

129

import math

130

return math.pow(x * multiplier, 2)

131

132

# Wrap for parallel execution

133

wrapped_func = wrap_non_picklable_objects(interactive_function)

134

135

executor = get_reusable_executor(max_workers=3)

136

inputs = [1, 2, 3, 4, 5]

137

results = list(executor.map(wrapped_func, inputs))

138

print(f"Interactive function results: {results}")

139

```

140

141

### Wrapping Classes

142

143

```python

144

from loky import get_reusable_executor, wrap_non_picklable_objects

145

146

# Class defined in main module

147

class DataProcessor:

148

def __init__(self, scale_factor):

149

self.scale_factor = scale_factor

150

151

def process(self, data):

152

return [x * self.scale_factor for x in data]

153

154

# Wrap the class

155

WrappedProcessor = wrap_non_picklable_objects(DataProcessor)

156

157

def process_with_wrapped_class(data_chunk):

158

processor = WrappedProcessor(2.5)

159

return processor.process(data_chunk)

160

161

executor = get_reusable_executor(max_workers=2)

162

data_chunks = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

163

results = list(executor.map(process_with_wrapped_class, data_chunks))

164

print(f"Class processing results: {results}")

165

```

166

167

### Automatic Wrapping

168

169

Loky automatically detects and wraps certain types of non-picklable objects:

170

171

```python

172

from loky import get_reusable_executor

173

from functools import partial

174

175

def multiply_add(x, multiplier, addend):

176

return x * multiplier + addend

177

178

# partial objects are automatically wrapped

179

partial_func = partial(multiply_add, multiplier=3, addend=10)

180

181

executor = get_reusable_executor(max_workers=2)

182

183

# No explicit wrapping needed - loky handles it automatically

184

results = list(executor.map(partial_func, range(5)))

185

print(f"Partial function results: {results}")

186

```

187

188

### Custom Pickler Configuration

189

190

```python

191

from loky import set_loky_pickler, get_reusable_executor

192

import pickle

193

194

class CustomPickler(pickle.Pickler):

195

"""Custom pickler with special handling for certain types."""

196

197

def save_global(self, obj, name=None):

198

# Custom handling for global objects

199

print(f"Pickling global object: {obj}")

200

super().save_global(obj, name)

201

202

# Set custom pickler

203

set_loky_pickler(CustomPickler)

204

205

def test_function(x):

206

return x ** 2

207

208

executor = get_reusable_executor(max_workers=2)

209

results = list(executor.map(test_function, [1, 2, 3]))

210

211

# Reset to default pickler

212

set_loky_pickler(None)

213

```

214

215

### Custom Type Reduction

216

217

```python

218

from loky.backend.reduction import register, dump, dumps

219

from loky import get_reusable_executor

220

import io

221

222

class CustomData:

223

"""Custom class that needs special serialization."""

224

def __init__(self, value):

225

self.value = value

226

self._computed_result = None

227

228

def compute_expensive_result(self):

229

# Simulate expensive computation that we don't want to serialize

230

if self._computed_result is None:

231

self._computed_result = self.value * 1000

232

return self._computed_result

233

234

def reduce_custom_data(obj):

235

"""Custom reducer that only serializes the essential data."""

236

return rebuild_custom_data, (obj.value,)

237

238

def rebuild_custom_data(value):

239

"""Rebuild CustomData from reduced form."""

240

return CustomData(value)

241

242

# Register the custom reducer

243

register(CustomData, reduce_custom_data)

244

245

def process_custom_data(data):

246

"""Task that processes CustomData objects."""

247

result = data.compute_expensive_result()

248

return f"Processed {data.value} -> {result}"

249

250

# Use with custom serialization

251

executor = get_reusable_executor(max_workers=2)

252

253

# Create objects that will use custom serialization

254

custom_objects = [CustomData(i) for i in range(5)]

255

results = list(executor.map(process_custom_data, custom_objects))

256

257

for i, result in enumerate(results):

258

print(f"Object {i}: {result}")

259

```

260

261

### Manual Pickling Operations

262

263

```python

264

from loky.backend.reduction import dump, dumps

265

import io

266

267

class ComplexObject:

268

def __init__(self, data):

269

self.data = data

270

271

def process(self):

272

return sum(self.data)

273

274

# Create test object

275

obj = ComplexObject([1, 2, 3, 4, 5])

276

277

# Serialize to bytes

278

serialized_bytes = dumps(obj)

279

print(f"Serialized size: {len(serialized_bytes)} bytes")

280

281

# Serialize to file

282

with io.BytesIO() as buffer:

283

dump(obj, buffer)

284

file_size = buffer.tell()

285

print(f"File serialization size: {file_size} bytes")

286

287

# Use with custom reducers

288

custom_reducers = {

289

ComplexObject: lambda obj: (ComplexObject, (obj.data,))

290

}

291

292

custom_serialized = dumps(obj, reducers=custom_reducers)

293

print(f"Custom serialized size: {len(custom_serialized)} bytes")

294

```

295

296

### Handling Closures

297

298

```python

299

from loky import get_reusable_executor, wrap_non_picklable_objects

300

301

def create_closure_function(base_value):

302

"""Create a closure that captures base_value."""

303

def closure_func(x):

304

return x + base_value # References base_value from outer scope

305

return closure_func

306

307

# Closures need wrapping due to captured variables

308

closure = create_closure_function(100)

309

wrapped_closure = wrap_non_picklable_objects(closure)

310

311

executor = get_reusable_executor(max_workers=2)

312

results = list(executor.map(wrapped_closure, range(5)))

313

print(f"Closure results: {results}")

314

```

315

316

### Nested Function Handling

317

318

```python

319

from loky import get_reusable_executor

320

321

def outer_function():

322

"""Outer function that defines a nested function."""

323

324

def nested_worker(data):

325

# Nested functions are automatically wrapped

326

return [x * 2 for x in data]

327

328

executor = get_reusable_executor(max_workers=2)

329

330

# Loky automatically detects and wraps nested functions

331

data_chunks = [[1, 2], [3, 4], [5, 6]]

332

results = list(executor.map(nested_worker, data_chunks))

333

return results

334

335

# Call function with nested parallel processing

336

results = outer_function()

337

print(f"Nested function results: {results}")

338

```

339

340

## Best Practices

341

342

### Performance Considerations

343

344

- **Minimize Cloudpickle Usage**: While convenient, cloudpickle is slower than standard pickle

345

- **Define Functions at Module Level**: Avoid interactive definitions when possible

346

- **Implement __reduce__ Methods**: For custom classes, implement proper serialization

347

348

### Memory Management

349

350

- **Control Wrapper Retention**: Use `keep_wrapper=False` when wrapper is not needed after deserialization

351

- **Cache Wrapped Objects**: Reuse wrapped objects instead of wrapping repeatedly

352

353

### Error Handling

354

355

```python

356

from loky import get_reusable_executor, wrap_non_picklable_objects

357

import pickle

358

359

def problematic_function(x):

360

# Function that might have serialization issues

361

return x

362

363

try:

364

# Attempt standard execution

365

executor = get_reusable_executor(max_workers=2)

366

results = list(executor.map(problematic_function, [1, 2, 3]))

367

except (pickle.PicklingError, AttributeError) as e:

368

print(f"Pickling failed: {e}")

369

# Fall back to wrapped version

370

wrapped_func = wrap_non_picklable_objects(problematic_function)

371

results = list(executor.map(wrapped_func, [1, 2, 3]))

372

print(f"Wrapped execution succeeded: {results}")

373

```