Skip to content

Commit

Permalink
Merge pull request #7 from cloudpipe/master
Browse files Browse the repository at this point in the history
Drop Python 2 support in cloudpickle (cloudpipe#342)
  • Loading branch information
sthagen authored Feb 14, 2020
2 parents 73569b2 + 65b2408 commit e565e4a
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 312 deletions.
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

0 comments on commit e565e4a

Please sign in to comment.