Skip to content

Commit 6a0e12d

Browse files
colesburyogrisel
andauthored
Improve compatibility with "nogil" Python and 3.11 (#470)
* Improve compatibility with "nogil" Python and 3.11 This makes a number of changes to improve compatibility with the "nogil" Python fork as well as the upcoming 3.11 release. - Fix _code_reduce for 3.11b0 and nogil Python - Use instr.argval in _walk_global_ops. This avoids adding a special case for 3.11+ (and is useful for nogil Python). In 3.11+, the argval for LOAD_GLOBAL would need to be divided by two to access the correct name. The 'argval' field already stores the correct name. - Set '__builtins__' before constructing de-pickled functions. (Useful for nogil Python) - Fix test_recursion_during_pickling in Python 3.11+. Objects now have a default `__getstate__` method so `__getattr__` was never called, but `__getattribute__` would still be called. Co-authored-by: Olivier Grisel <[email protected]>
1 parent 2fc334d commit 6a0e12d

File tree

4 files changed

+34
-12
lines changed

4 files changed

+34
-12
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
- Support and CI configuration for Python 3.11.
1212
([PR #467](https://github.com/cloudpipe/cloudpickle/pull/467))
1313

14+
- Support for the experimental `nogil` variant of CPython
15+
([PR #470](https://github.com/cloudpipe/cloudpickle/pull/470))
16+
1417
2.0.0
1518
=====
1619

cloudpickle/cloudpickle.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -321,11 +321,10 @@ def _extract_code_globals(co):
321321
"""
322322
out_names = _extract_code_globals_cache.get(co)
323323
if out_names is None:
324-
names = co.co_names
325324
# We use a dict with None values instead of a set to get a
326325
# deterministic order (assuming Python 3.6+) and avoid introducing
327326
# non-deterministic pickle bytes as a results.
328-
out_names = {names[oparg]: None for _, oparg in _walk_global_ops(co)}
327+
out_names = {name: None for name in _walk_global_ops(co)}
329328

330329
# Declaring a function inside another one using the "def ..."
331330
# syntax generates a constant code object corresponding to the one
@@ -511,13 +510,12 @@ def _builtin_type(name):
511510

512511
def _walk_global_ops(code):
513512
"""
514-
Yield (opcode, argument number) tuples for all
515-
global-referencing instructions in *code*.
513+
Yield referenced name for all global-referencing instructions in *code*.
516514
"""
517515
for instr in dis.get_instructions(code):
518516
op = instr.opcode
519517
if op in GLOBAL_OPS:
520-
yield op, instr.arg
518+
yield instr.argval
521519

522520

523521
def _extract_class_dict(cls):
@@ -765,6 +763,12 @@ def _fill_function(*args):
765763
return func
766764

767765

766+
def _make_function(code, globals, name, argdefs, closure):
767+
# Setting __builtins__ in globals is needed for nogil CPython.
768+
globals["__builtins__"] = __builtins__
769+
return types.FunctionType(code, globals, name, argdefs, closure)
770+
771+
768772
def _make_empty_cell():
769773
if False:
770774
# trick the compiler into creating an empty cell in our lambda

cloudpickle/cloudpickle_fast.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
_is_parametrized_type_hint, PYPY, cell_set,
3636
parametrized_type_hint_getinitargs, _create_parametrized_type_hint,
3737
builtin_code_type,
38-
_make_dict_keys, _make_dict_values, _make_dict_items,
38+
_make_dict_keys, _make_dict_values, _make_dict_items, _make_function,
3939
)
4040

4141

@@ -248,17 +248,16 @@ def _code_reduce(obj):
248248
# of the specific type from types, for example:
249249
# >>> from types import CodeType
250250
# >>> help(CodeType)
251-
if hasattr(obj, "co_columntable"): # pragma: no branch
251+
if hasattr(obj, "co_exceptiontable"): # pragma: no branch
252252
# Python 3.11 and later: there are some new attributes
253253
# related to the enhanced exceptions.
254254
args = (
255255
obj.co_argcount, obj.co_posonlyargcount,
256256
obj.co_kwonlyargcount, obj.co_nlocals, obj.co_stacksize,
257257
obj.co_flags, obj.co_code, obj.co_consts, obj.co_names,
258258
obj.co_varnames, obj.co_filename, obj.co_name, obj.co_qualname,
259-
obj.co_firstlineno, obj.co_linetable, obj.co_endlinetable,
260-
obj.co_columntable, obj.co_exceptiontable, obj.co_freevars,
261-
obj.co_cellvars,
259+
obj.co_firstlineno, obj.co_linetable, obj.co_exceptiontable,
260+
obj.co_freevars, obj.co_cellvars,
262261
)
263262
elif hasattr(obj, "co_linetable"): # pragma: no branch
264263
# Python 3.10 and later: obj.co_lnotab is deprecated and constructor
@@ -271,6 +270,18 @@ def _code_reduce(obj):
271270
obj.co_firstlineno, obj.co_linetable, obj.co_freevars,
272271
obj.co_cellvars
273272
)
273+
elif hasattr(obj, "co_nmeta"): # pragma: no cover
274+
# "nogil" Python: modified attributes from 3.9
275+
args = (
276+
obj.co_argcount, obj.co_posonlyargcount,
277+
obj.co_kwonlyargcount, obj.co_nlocals, obj.co_framesize,
278+
obj.co_ndefaultargs, obj.co_nmeta,
279+
obj.co_flags, obj.co_code, obj.co_consts,
280+
obj.co_varnames, obj.co_filename, obj.co_name,
281+
obj.co_firstlineno, obj.co_lnotab, obj.co_exc_handlers,
282+
obj.co_jump_table, obj.co_freevars, obj.co_cellvars,
283+
obj.co_free2reg, obj.co_cell2reg
284+
)
274285
elif hasattr(obj, "co_posonlyargcount"):
275286
# Backward compat for 3.9 and older
276287
args = (
@@ -564,7 +575,7 @@ def _dynamic_function_reduce(self, func):
564575
"""Reduce a function that is not pickleable via attribute lookup."""
565576
newargs = self._function_getnewargs(func)
566577
state = _function_getstate(func)
567-
return (types.FunctionType, newargs, state, None, None,
578+
return (_make_function, newargs, state, None, None,
568579
_function_setstate)
569580

570581
def _function_reduce(self, obj):

tests/cloudpickle_test.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,10 @@ def g(y):
978978
res = loop.run_sync(functools.partial(g2, 5))
979979
self.assertEqual(res, 7)
980980

981+
@pytest.mark.skipif(
982+
(3, 11, 0, 'beta') <= sys.version_info < (3, 11, 0, 'beta', 2),
983+
reason="https://github.com/python/cpython/issues/92932"
984+
)
981985
def test_extended_arg(self):
982986
# Functions with more than 65535 global vars prefix some global
983987
# variable references with the EXTENDED_ARG opcode.
@@ -2245,7 +2249,7 @@ def inner_function():
22452249

22462250
def test_recursion_during_pickling(self):
22472251
class A:
2248-
def __getattr__(self, name):
2252+
def __getattribute__(self, name):
22492253
return getattr(self, name)
22502254

22512255
a = A()

0 commit comments

Comments
 (0)