Skip to content

Commit 7131b12

Browse files
committed
Python 3 Metaclasses
1 parent 57dfdec commit 7131b12

File tree

2 files changed

+110
-20
lines changed

2 files changed

+110
-20
lines changed

dill/_dill.py

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,17 @@
6565
SliceType = slice
6666
TypeType = type # 'new-style' classes #XXX: unregistered
6767
XRangeType = range
68-
from types import MappingProxyType as DictProxyType
68+
from types import MappingProxyType as DictProxyType, new_class
6969
from pickle import DEFAULT_PROTOCOL, HIGHEST_PROTOCOL, PickleError, PicklingError, UnpicklingError
7070
import __main__ as _main_module
7171
import marshal
7272
import gc
7373
# import zlib
74+
import abc
7475
import dataclasses
7576
from weakref import ReferenceType, ProxyType, CallableProxyType
7677
from collections import OrderedDict
78+
from enum import Enum, EnumMeta
7779
from functools import partial
7880
from operator import itemgetter, attrgetter
7981
GENERATOR_FAIL = False
@@ -1669,6 +1671,35 @@ def save_module(pickler, obj):
16691671
logger.trace(pickler, "# M2")
16701672
return
16711673

1674+
# The following function is based on '_extract_class_dict' from 'cloudpickle'
1675+
# Copyright (c) 2012, Regents of the University of California.
1676+
# Copyright (c) 2009 `PiCloud, Inc. <http://www.picloud.com>`_.
1677+
# License: https://github.com/cloudpipe/cloudpickle/blob/master/LICENSE
1678+
def _get_typedict_type(cls, clsdict, attrs, postproc_list):
1679+
"""Retrieve a copy of the dict of a class without the inherited methods"""
1680+
if len(cls.__bases__) == 1:
1681+
inherited_dict = cls.__bases__[0].__dict__
1682+
else:
1683+
inherited_dict = {}
1684+
for base in reversed(cls.__bases__):
1685+
inherited_dict.update(base.__dict__)
1686+
to_remove = []
1687+
for name, value in dict.items(clsdict):
1688+
try:
1689+
base_value = inherited_dict[name]
1690+
if value is base_value:
1691+
to_remove.append(name)
1692+
except KeyError:
1693+
pass
1694+
for name in to_remove:
1695+
dict.pop(clsdict, name)
1696+
1697+
if issubclass(type(cls), type):
1698+
clsdict.pop('__dict__', None)
1699+
clsdict.pop('__weakref__', None)
1700+
# clsdict.pop('__prepare__', None)
1701+
return clsdict, attrs
1702+
16721703
@register(TypeType)
16731704
def save_type(pickler, obj, postproc_list=None):
16741705
if obj in _typemap:
@@ -1680,15 +1711,22 @@ def save_type(pickler, obj, postproc_list=None):
16801711
elif obj.__bases__ == (tuple,) and all([hasattr(obj, attr) for attr in ('_fields','_asdict','_make','_replace')]):
16811712
# special case: namedtuples
16821713
logger.trace(pickler, "T6: %s", obj)
1714+
1715+
obj_name = getattr(obj, '__qualname__', getattr(obj, '__name__', None))
1716+
if obj.__name__ != obj_name:
1717+
if postproc_list is None:
1718+
postproc_list = []
1719+
postproc_list.append((setattr, (obj, '__qualname__', obj_name)))
1720+
16831721
if not obj._field_defaults:
1684-
pickler.save_reduce(_create_namedtuple, (obj.__name__, obj._fields, obj.__module__), obj=obj)
1722+
_save_with_postproc(pickler, (_create_namedtuple, (obj.__name__, obj._fields, obj.__module__)), obj=obj, postproc_list=postproc_list)
16851723
else:
16861724
defaults = [obj._field_defaults[field] for field in obj._fields if field in obj._field_defaults]
1687-
pickler.save_reduce(_create_namedtuple, (obj.__name__, obj._fields, obj.__module__, defaults), obj=obj)
1725+
_save_with_postproc(pickler, (_create_namedtuple, (obj.__name__, obj._fields, obj.__module__, defaults)), obj=obj, postproc_list=postproc_list)
16881726
logger.trace(pickler, "# T6")
16891727
return
16901728

1691-
# special cases: NoneType, NotImplementedType, EllipsisType
1729+
# special cases: NoneType, NotImplementedType, EllipsisType, EnumMeta
16921730
elif obj is type(None):
16931731
logger.trace(pickler, "T7: %s", obj)
16941732
#XXX: pickler.save_reduce(type, (None,), obj=obj)
@@ -1702,35 +1740,64 @@ def save_type(pickler, obj, postproc_list=None):
17021740
logger.trace(pickler, "T7: %s", obj)
17031741
pickler.save_reduce(type, (Ellipsis,), obj=obj)
17041742
logger.trace(pickler, "# T7")
1743+
elif obj is EnumMeta:
1744+
logger.trace(pickler, "T7: %s", obj)
1745+
pickler.write(GLOBAL + b'enum\nEnumMeta\n')
1746+
logger.trace(pickler, "# T7")
17051747

17061748
else:
17071749
obj_name = getattr(obj, '__qualname__', getattr(obj, '__name__', None))
17081750
_byref = getattr(pickler, '_byref', None)
17091751
obj_recursive = id(obj) in getattr(pickler, '_postproc', ())
17101752
incorrectly_named = not _locate_function(obj, pickler)
17111753
if not _byref and not obj_recursive and incorrectly_named: # not a function, but the name was held over
1754+
if postproc_list is None:
1755+
postproc_list = []
1756+
17121757
# thanks to Tom Stepleton pointing out pickler._session unneeded
17131758
logger.trace(pickler, "T2: %s", obj)
1714-
_dict = obj.__dict__.copy() # convert dictproxy to dict
1759+
_dict, attrs = _get_typedict_type(obj, obj.__dict__.copy(), None, postproc_list) # copy dict proxy to a dict
1760+
17151761
#print (_dict)
17161762
#print ("%s\n%s" % (type(obj), obj.__name__))
17171763
#print ("%s\n%s" % (obj.__bases__, obj.__dict__))
17181764
slots = _dict.get('__slots__', ())
1719-
if type(slots) == str: slots = (slots,) # __slots__ accepts a single string
1765+
if type(slots) == str:
1766+
# __slots__ accepts a single string
1767+
slots = (slots,)
1768+
17201769
for name in slots:
1721-
del _dict[name]
1722-
_dict.pop('__dict__', None)
1723-
_dict.pop('__weakref__', None)
1724-
_dict.pop('__prepare__', None)
1725-
if obj_name != obj.__name__:
1726-
if postproc_list is None:
1727-
postproc_list = []
1728-
postproc_list.append((setattr, (obj, '__qualname__', obj_name)))
1729-
_save_with_postproc(pickler, (_create_type, (
1730-
type(obj), obj.__name__, obj.__bases__, _dict
1731-
)), obj=obj, postproc_list=postproc_list)
1770+
_dict.pop(name, None)
1771+
1772+
qualname = getattr(obj, '__qualname__', None)
1773+
if attrs is not None:
1774+
for k, v in attrs.items():
1775+
postproc_list.append((setattr, (obj, k, v)))
1776+
# TODO: Consider using the state argument to save_reduce?
1777+
if qualname is not None:
1778+
postproc_list.append((setattr, (obj, '__qualname__', qualname)))
1779+
1780+
if not hasattr(obj, '__orig_bases__'):
1781+
_save_with_postproc(pickler, (_create_type, (
1782+
type(obj), obj.__name__, obj.__bases__, _dict
1783+
)), obj=obj, postproc_list=postproc_list)
1784+
else:
1785+
# This case will always work, but might be overkill.
1786+
_metadict = {
1787+
'metaclass': type(obj)
1788+
}
1789+
1790+
if _dict:
1791+
_dict_update = PartialType(_setitems, source=_dict)
1792+
else:
1793+
_dict_update = None
1794+
1795+
_save_with_postproc(pickler, (new_class, (
1796+
obj.__name__, obj.__orig_bases__, _metadict, _dict_update
1797+
)), obj=obj, postproc_list=postproc_list)
17321798
logger.trace(pickler, "# T2")
17331799
else:
1800+
obj_name = getattr(obj, '__qualname__', getattr(obj, '__name__', None))
17341801
logger.trace(pickler, "T4: %s", obj)
17351802
if incorrectly_named:
17361803
warnings.warn(
@@ -1753,14 +1820,17 @@ def save_type(pickler, obj, postproc_list=None):
17531820
return
17541821

17551822
@register(property)
1823+
@register(abc.abstractproperty)
17561824
def save_property(pickler, obj):
17571825
logger.trace(pickler, "Pr: %s", obj)
1758-
pickler.save_reduce(property, (obj.fget, obj.fset, obj.fdel, obj.__doc__),
1826+
pickler.save_reduce(type(obj), (obj.fget, obj.fset, obj.fdel, obj.__doc__),
17591827
obj=obj)
17601828
logger.trace(pickler, "# Pr")
17611829

17621830
@register(staticmethod)
17631831
@register(classmethod)
1832+
@register(abc.abstractstaticmethod)
1833+
@register(abc.abstractclassmethod)
17641834
def save_classmethod(pickler, obj):
17651835
logger.trace(pickler, "Cm: %s", obj)
17661836
orig_func = obj.__func__

dill/tests/test_classdef.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ def ok(self):
5454
nc = _newclass2()
5555
m = _mclass()
5656

57+
if sys.hexversion < 0x03090000:
58+
import typing
59+
class customIntList(typing.List[int]):
60+
pass
61+
else:
62+
class customIntList(list[int]):
63+
pass
64+
5765
# test pickles for class instances
5866
def test_class_instances():
5967
assert dill.pickles(o)
@@ -111,7 +119,7 @@ def test_namedtuple():
111119
assert tuple(Badi) == tuple(dill.loads(dill.dumps(Badi)))
112120

113121
class A:
114-
class B(namedtuple("B", ["one", "two"])):
122+
class B(namedtuple("C", ["one", "two"])):
115123
'''docstring'''
116124
B.__module__ = 'testing'
117125

@@ -123,6 +131,15 @@ class B(namedtuple("B", ["one", "two"])):
123131
assert dill.copy(A.B).__doc__ == 'docstring'
124132
assert dill.copy(A.B).__module__ == 'testing'
125133

134+
from typing import NamedTuple
135+
136+
def A():
137+
class B(NamedTuple):
138+
x: int
139+
return B
140+
141+
assert type(dill.copy(A()(8))).__qualname__ == type(A()(8)).__qualname__
142+
126143
def test_dtype():
127144
try:
128145
import numpy as np
@@ -210,6 +227,9 @@ def test_slots():
210227
assert dill.pickles(Y.y)
211228
assert dill.copy(y).y == value
212229

230+
def test_origbases():
231+
assert dill.copy(customIntList).__orig_bases__ == customIntList.__orig_bases__
232+
213233
def test_attr():
214234
import attr
215235
@attr.s
@@ -238,7 +258,6 @@ def __new__(cls):
238258

239259
assert dill.copy(subclass_with_new())
240260

241-
242261
if __name__ == '__main__':
243262
test_class_instances()
244263
test_class_objects()
@@ -249,4 +268,5 @@ def __new__(cls):
249268
test_array_subclass()
250269
test_method_decorator()
251270
test_slots()
271+
test_origbases()
252272
test_metaclass()

0 commit comments

Comments
 (0)