Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop Python 2 support in cloudpickle #342

Merged
merged 11 commits into from
Feb 14, 2020
2 changes: 1 addition & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python_version: [2.7, 3.5, 3.6, 3.7, 3.8, "pypy3"]
python_version: [3.5, 3.6, 3.7, 3.8, "pypy3"]
exclude:
# Do not test all minor versions on all platforms, especially if they
# are not the oldest/newest supported versions
Expand Down
260 changes: 60 additions & 200 deletions cloudpickle/cloudpickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from __future__ import print_function

import abc
import builtins
import dis
from functools import partial
import io
Expand All @@ -60,6 +61,11 @@
import uuid
import threading

from pickle import _Pickler as Pickler
from pickle import _getattribute
from io import BytesIO
from importlib._bootstrap import _find_spec


try:
from enum import Enum
Expand All @@ -85,25 +91,6 @@
# builtin-code objects only exist in pypy
builtin_code_type = type(float.__new__.__code__)

if sys.version_info[0] < 3: # pragma: no branch
from pickle import Pickler
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
import __builtin__ as builtins
string_types = (basestring,) # noqa
PY3 = False
PY2 = True
else:
from pickle import _Pickler as Pickler
from io import BytesIO as StringIO
string_types = (str,)
PY3 = True
PY2 = False
from importlib._bootstrap import _find_spec
import builtins

_extract_code_globals_cache = weakref.WeakKeyDictionary()


Expand All @@ -125,21 +112,6 @@ def _lookup_class_or_track(class_tracker_id, class_def):
_DYNAMIC_CLASS_TRACKER_BY_CLASS[class_def] = class_tracker_id
return class_def

if sys.version_info[:2] >= (3, 5):
from pickle import _getattribute
elif sys.version_info[:2] >= (3, 4):
from pickle import _getattribute as _py34_getattribute
# pickle._getattribute does not return the parent under Python 3.4
def _getattribute(obj, name):
return _py34_getattribute(obj, name), None
else:
# pickle._getattribute is a python3 addition and enchancement of getattr,
# that can handle dotted attribute names. In cloudpickle for python2,
# handling dotted names is not needed, so we simply define _getattribute as
# a wrapper around getattr.
def _getattribute(obj, name):
return getattr(obj, name, None), None


def _whichmodule(obj, name):
"""Find the module an object belongs to.
Expand Down Expand Up @@ -355,41 +327,23 @@ def _cell_set_factory(value):

co = _cell_set_factory.__code__

if PY2: # pragma: no branch
_cell_set_template_code = types.CodeType(
co.co_argcount,
co.co_nlocals,
co.co_stacksize,
co.co_flags,
co.co_code,
co.co_consts,
co.co_names,
co.co_varnames,
co.co_filename,
co.co_name,
co.co_firstlineno,
co.co_lnotab,
co.co_cellvars, # co_freevars is initialized with co_cellvars
(), # co_cellvars is made empty
)
else:
_cell_set_template_code = types.CodeType(
co.co_argcount,
co.co_kwonlyargcount, # Python 3 only argument
co.co_nlocals,
co.co_stacksize,
co.co_flags,
co.co_code,
co.co_consts,
co.co_names,
co.co_varnames,
co.co_filename,
co.co_name,
co.co_firstlineno,
co.co_lnotab,
co.co_cellvars, # co_freevars is initialized with co_cellvars
(), # co_cellvars is made empty
)
_cell_set_template_code = types.CodeType(
co.co_argcount,
co.co_kwonlyargcount, # Python 3 only argument
co.co_nlocals,
co.co_stacksize,
co.co_flags,
co.co_code,
co.co_consts,
co.co_names,
co.co_varnames,
co.co_filename,
co.co_name,
co.co_firstlineno,
co.co_lnotab,
co.co_cellvars, # co_freevars is initialized with co_cellvars
(), # co_cellvars is made empty
)
return _cell_set_template_code


Expand All @@ -415,41 +369,15 @@ def _builtin_type(name):
return getattr(types, name)


if sys.version_info < (3, 4): # pragma: no branch
def _walk_global_ops(code):
"""
Yield (opcode, argument number) tuples for all
global-referencing instructions in *code*.
"""
code = getattr(code, 'co_code', b'')
if PY2: # pragma: no branch
code = map(ord, code)

n = len(code)
i = 0
extended_arg = 0
while i < n:
op = code[i]
i += 1
if op >= HAVE_ARGUMENT:
oparg = code[i] + code[i + 1] * 256 + extended_arg
extended_arg = 0
i += 2
if op == EXTENDED_ARG:
extended_arg = oparg * 65536
if op in GLOBAL_OPS:
yield op, oparg

else:
def _walk_global_ops(code):
"""
Yield (opcode, argument number) tuples for all
global-referencing instructions in *code*.
"""
for instr in dis.get_instructions(code):
op = instr.opcode
if op in GLOBAL_OPS:
yield op, instr.arg
def _walk_global_ops(code):
"""
Yield (opcode, argument number) tuples for all
global-referencing instructions in *code*.
"""
for instr in dis.get_instructions(code):
op = instr.opcode
if op in GLOBAL_OPS:
yield op, instr.arg


def _extract_class_dict(cls):
Expand Down Expand Up @@ -501,12 +429,6 @@ def save_memoryview(self, obj):

dispatch[memoryview] = save_memoryview

if PY2: # pragma: no branch
def save_buffer(self, obj):
self.save(str(obj))

dispatch[buffer] = save_buffer # noqa: F821 'buffer' was removed in Python 3

def save_module(self, obj):
"""
Save a module as an import
Expand All @@ -524,29 +446,22 @@ def save_codeobject(self, obj):
"""
Save a code object
"""
if PY3: # pragma: no branch
if hasattr(obj, "co_posonlyargcount"): # pragma: no branch
args = (
obj.co_argcount, obj.co_posonlyargcount,
obj.co_kwonlyargcount, obj.co_nlocals, obj.co_stacksize,
obj.co_flags, obj.co_code, obj.co_consts, obj.co_names,
obj.co_varnames, obj.co_filename, obj.co_name,
obj.co_firstlineno, obj.co_lnotab, obj.co_freevars,
obj.co_cellvars
)
else:
args = (
obj.co_argcount, obj.co_kwonlyargcount, obj.co_nlocals,
obj.co_stacksize, obj.co_flags, obj.co_code, obj.co_consts,
obj.co_names, obj.co_varnames, obj.co_filename,
obj.co_name, obj.co_firstlineno, obj.co_lnotab,
obj.co_freevars, obj.co_cellvars
)
if hasattr(obj, "co_posonlyargcount"): # pragma: no branch
args = (
obj.co_argcount, obj.co_posonlyargcount,
obj.co_kwonlyargcount, obj.co_nlocals, obj.co_stacksize,
obj.co_flags, obj.co_code, obj.co_consts, obj.co_names,
obj.co_varnames, obj.co_filename, obj.co_name,
obj.co_firstlineno, obj.co_lnotab, obj.co_freevars,
obj.co_cellvars
)
else:
args = (
obj.co_argcount, obj.co_nlocals, obj.co_stacksize, obj.co_flags, obj.co_code,
obj.co_consts, obj.co_names, obj.co_varnames, obj.co_filename, obj.co_name,
obj.co_firstlineno, obj.co_lnotab, obj.co_freevars, obj.co_cellvars
obj.co_argcount, obj.co_kwonlyargcount, obj.co_nlocals,
obj.co_stacksize, obj.co_flags, obj.co_code, obj.co_consts,
obj.co_names, obj.co_varnames, obj.co_filename,
obj.co_name, obj.co_firstlineno, obj.co_lnotab,
obj.co_freevars, obj.co_cellvars
)
self.save_reduce(types.CodeType, args, obj=obj)

Expand Down Expand Up @@ -600,13 +515,12 @@ def _save_dynamic_enum(self, obj, clsdict):
"""
members = dict((e.name, e.value) for e in obj)

# Python 2.7 with enum34 can have no qualname:
qualname = getattr(obj, "__qualname__", None)

self.save_reduce(_make_skeleton_enum,
(obj.__bases__, obj.__name__, qualname, members,
obj.__module__, _ensure_tracking(obj), None),
obj=obj)
self.save_reduce(
_make_skeleton_enum,
(obj.__bases__, obj.__name__, obj.__qualname__,
members, obj.__module__, _ensure_tracking(obj), None),
obj=obj
)

# Cleanup the clsdict that will be passed to _rehydrate_skeleton_class:
# Those attributes are already handled by the metaclass.
Expand Down Expand Up @@ -658,7 +572,7 @@ def save_dynamic_class(self, obj):
# pickle string length optimization: member descriptors of obj are
# created automatically from obj's __slots__ attribute, no need to
# save them in obj's state
if isinstance(obj.__slots__, string_types):
if isinstance(obj.__slots__, str):
clsdict.pop(obj.__slots__)
else:
for k in obj.__slots__:
Expand Down Expand Up @@ -833,45 +747,6 @@ def extract_func_data(self, func):

return (code, f_globals, defaults, closure, dct, base_globals)

if not PY3: # pragma: no branch
# Python3 comes with native reducers that allow builtin functions and
# methods pickling as module/class attributes. The following method
# extends this for python2.
# Please note that currently, neither pickle nor cloudpickle support
# dynamically created builtin functions/method pickling.
def save_builtin_function_or_method(self, obj):
is_bound = getattr(obj, '__self__', None) is not None
if is_bound:
# obj is a bound builtin method.
rv = (getattr, (obj.__self__, obj.__name__))
return self.save_reduce(obj=obj, *rv)

is_unbound = hasattr(obj, '__objclass__')
if is_unbound:
# obj is an unbound builtin method (accessed from its class)
rv = (getattr, (obj.__objclass__, obj.__name__))
return self.save_reduce(obj=obj, *rv)

# Otherwise, obj is not a method, but a function. Fallback to
# default pickling by attribute.
return Pickler.save_global(self, obj)

dispatch[types.BuiltinFunctionType] = save_builtin_function_or_method

# A comprehensive summary of the various kinds of builtin methods can
# be found in PEP 579: https://www.python.org/dev/peps/pep-0579/
classmethod_descriptor_type = type(float.__dict__['fromhex'])
wrapper_descriptor_type = type(float.__repr__)
method_wrapper_type = type(1.5.__repr__)

dispatch[classmethod_descriptor_type] = save_builtin_function_or_method
dispatch[wrapper_descriptor_type] = save_builtin_function_or_method
dispatch[method_wrapper_type] = save_builtin_function_or_method

if sys.version_info[:2] < (3, 4):
method_descriptor = type(str.upper)
dispatch[method_descriptor] = save_builtin_function_or_method

def save_getset_descriptor(self, obj):
return self.save_reduce(getattr, (obj.__objclass__, obj.__name__))

Expand Down Expand Up @@ -901,20 +776,13 @@ def save_global(self, obj, name=None, pack=struct.pack):
Pickler.save_global(self, obj, name=name)

dispatch[type] = save_global
if PY2:
dispatch[types.ClassType] = save_global

def save_instancemethod(self, obj):
# Memoization rarely is ever useful due to python bounding
if obj.__self__ is None:
self.save_reduce(getattr, (obj.im_class, obj.__name__))
else:
if PY3: # pragma: no branch
self.save_reduce(types.MethodType, (obj.__func__, obj.__self__), obj=obj)
else:
self.save_reduce(
types.MethodType,
(obj.__func__, obj.__self__, type(obj.__self__)), obj=obj)
self.save_reduce(types.MethodType, (obj.__func__, obj.__self__), obj=obj)

dispatch[types.MethodType] = save_instancemethod

Expand Down Expand Up @@ -963,12 +831,10 @@ def save_inst(self, obj):
save(stuff)
write(pickle.BUILD)

if PY2: # pragma: no branch
dispatch[types.InstanceType] = save_inst

def save_property(self, obj):
# properties not correctly saved in python
self.save_reduce(property, (obj.fget, obj.fset, obj.fdel, obj.__doc__), obj=obj)
self.save_reduce(property, (obj.fget, obj.fset, obj.fdel, obj.__doc__),
obj=obj)

dispatch[property] = save_property

Expand Down Expand Up @@ -1016,10 +882,6 @@ def __getattribute__(self, item):

def save_file(self, obj):
"""Save a file"""
try:
import StringIO as pystringIO # we can't use cStringIO as it lacks the name attribute
except ImportError:
import io as pystringIO

if not hasattr(obj, 'name') or not hasattr(obj, 'mode'):
raise pickle.PicklingError("Cannot pickle files that do not map to an actual file")
Expand All @@ -1038,7 +900,8 @@ def save_file(self, obj):

name = obj.name

retval = pystringIO.StringIO()
# TODO: also support binary mode files with io.BytesIO
retval = io.StringIO()

try:
# Read the whole file
Expand Down Expand Up @@ -1142,7 +1005,7 @@ def dumps(obj, protocol=None):
Set protocol=pickle.DEFAULT_PROTOCOL instead if you need to ensure
compatibility with older versions of Python.
"""
file = StringIO()
file = BytesIO()
try:
cp = CloudPickler(file, protocol=protocol)
cp.dump(obj)
Expand Down Expand Up @@ -1364,10 +1227,7 @@ class id will also reuse this enum definition.
classdict[member_name] = member_value
enum_class = metacls.__new__(metacls, name, bases, classdict)
enum_class.__module__ = module

# Python 2.7 compat
if qualname is not None:
enum_class.__qualname__ = qualname
enum_class.__qualname__ = qualname

return _lookup_class_or_track(class_tracker_id, enum_class)

Expand Down
Loading