Bugfix/slot super (#226)
This commit is contained in:
parent
7f2490132b
commit
47583a9459
|
@ -18,6 +18,8 @@ matrix:
|
|||
env: TOXENV=py36
|
||||
- python: "pypy"
|
||||
env: TOXENV=pypy
|
||||
- python: "pypy3.5-5.8.0"
|
||||
env: TOXENV=pypy3
|
||||
|
||||
# Meta
|
||||
- python: "3.6"
|
||||
|
|
|
@ -24,9 +24,12 @@ Deprecations:
|
|||
Changes:
|
||||
^^^^^^^^
|
||||
|
||||
- Fix *str* on Python 2 when ``slots=True``.
|
||||
- ``super()`` and ``__class__`` now work on Python 3 when ``slots=True``.
|
||||
`#102 <https://github.com/python-attrs/attrs/issues/102>`_
|
||||
`#226 <https://github.com/python-attrs/attrs/issues/226>`_
|
||||
- The combination of ``str=True`` and ``slots=True`` now works on Python 2.
|
||||
`#198 <https://github.com/python-attrs/attrs/issues/198>`_
|
||||
- ``attr.Factory`` is now hashable again.
|
||||
- ``attr.Factory`` is hashable again.
|
||||
`#204 <https://github.com/python-attrs/attrs/issues/204>`_
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import platform
|
||||
import sys
|
||||
import types
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PYPY = platform.python_implementation() == "PyPy"
|
||||
|
||||
|
||||
if PY2:
|
||||
|
@ -88,3 +90,12 @@ else:
|
|||
|
||||
def metadata_proxy(d):
|
||||
return types.MappingProxyType(dict(d))
|
||||
|
||||
if PYPY: # pragma: no cover
|
||||
def set_closure_cell(cell, value):
|
||||
cell.__setstate__((value,))
|
||||
else:
|
||||
import ctypes
|
||||
set_closure_cell = ctypes.pythonapi.PyCell_Set
|
||||
set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object)
|
||||
set_closure_cell.restype = ctypes.c_int
|
||||
|
|
|
@ -6,7 +6,14 @@ import linecache
|
|||
from operator import itemgetter
|
||||
|
||||
from . import _config
|
||||
from ._compat import PY2, iteritems, isclass, iterkeys, metadata_proxy
|
||||
from ._compat import (
|
||||
PY2,
|
||||
iteritems,
|
||||
isclass,
|
||||
iterkeys,
|
||||
metadata_proxy,
|
||||
set_closure_cell,
|
||||
)
|
||||
from .exceptions import (
|
||||
DefaultAlreadySetError,
|
||||
FrozenInstanceError,
|
||||
|
@ -373,12 +380,27 @@ def attributes(maybe_cls=None, these=None, repr_ns=None,
|
|||
# It might not actually be in there, e.g. if using 'these'.
|
||||
cls_dict.pop(ca_name, None)
|
||||
cls_dict.pop("__dict__", None)
|
||||
old_cls = cls
|
||||
|
||||
qualname = getattr(cls, "__qualname__", None)
|
||||
cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
|
||||
if qualname is not None:
|
||||
cls.__qualname__ = qualname
|
||||
|
||||
# The following is a fix for
|
||||
# https://github.com/python-attrs/attrs/issues/102. On Python 3,
|
||||
# if a method mentions `__class__` or uses the no-arg super(), the
|
||||
# compiler will bake a reference to the class in the method itself
|
||||
# as `method.__closure__`. Since we replace the class with a
|
||||
# clone, we rewrite these references so it keeps working.
|
||||
for item in cls.__dict__.values():
|
||||
closure_cells = getattr(item, "__closure__", None)
|
||||
if not closure_cells: # Catch None or the empty list.
|
||||
continue
|
||||
for cell in closure_cells:
|
||||
if cell.cell_contents is old_cls:
|
||||
set_closure_cell(cell, cls)
|
||||
|
||||
return cls
|
||||
|
||||
# attrs_or class type depends on the usage of the decorator. It's a class
|
||||
|
|
|
@ -13,6 +13,8 @@ except: # Won't be an import error.
|
|||
|
||||
import attr
|
||||
|
||||
from attr._compat import PY2
|
||||
|
||||
|
||||
@attr.s
|
||||
class C1(object):
|
||||
|
@ -30,6 +32,14 @@ class C1(object):
|
|||
def staticmethod():
|
||||
return "staticmethod"
|
||||
|
||||
if not PY2:
|
||||
def my_class(self):
|
||||
return __class__ # NOQA: F821
|
||||
|
||||
def my_super(self):
|
||||
"""Just to test out the no-arg super."""
|
||||
return super().__repr__()
|
||||
|
||||
|
||||
@attr.s(slots=True, hash=True)
|
||||
class C1Slots(object):
|
||||
|
@ -47,6 +57,14 @@ class C1Slots(object):
|
|||
def staticmethod():
|
||||
return "staticmethod"
|
||||
|
||||
if not PY2:
|
||||
def my_class(self):
|
||||
return __class__ # NOQA: F821
|
||||
|
||||
def my_super(self):
|
||||
"""Just to test out the no-arg super."""
|
||||
return super().__repr__()
|
||||
|
||||
|
||||
def test_slots_being_used():
|
||||
"""
|
||||
|
@ -298,3 +316,52 @@ def test_bare_inheritance_from_slots():
|
|||
hash(c2) # Just to assert it doesn't raise.
|
||||
|
||||
assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2)
|
||||
|
||||
|
||||
@pytest.mark.skipif(PY2, reason="closure cell rewriting is PY3-only.")
|
||||
def test_closure_cell_rewriting():
|
||||
"""
|
||||
Slot classes support proper closure cell rewriting.
|
||||
|
||||
This affects features like `__class__` and the no-arg super().
|
||||
"""
|
||||
non_slot_instance = C1(x=1, y="test")
|
||||
slot_instance = C1Slots(x=1, y="test")
|
||||
|
||||
assert non_slot_instance.my_class() is C1
|
||||
assert slot_instance.my_class() is C1Slots
|
||||
|
||||
# Just assert they return something, and not an exception.
|
||||
assert non_slot_instance.my_super()
|
||||
assert slot_instance.my_super()
|
||||
|
||||
|
||||
@pytest.mark.skipif(PY2, reason="closure cell rewriting is PY3-only.")
|
||||
def test_closure_cell_rewriting_inheritance():
|
||||
"""
|
||||
Slot classes support proper closure cell rewriting when inheriting.
|
||||
|
||||
This affects features like `__class__` and the no-arg super().
|
||||
"""
|
||||
@attr.s
|
||||
class C2(C1):
|
||||
def my_subclass(self):
|
||||
return __class__ # NOQA: F821
|
||||
|
||||
@attr.s
|
||||
class C2Slots(C1Slots):
|
||||
def my_subclass(self):
|
||||
return __class__ # NOQA: F821
|
||||
|
||||
non_slot_instance = C2(x=1, y="test")
|
||||
slot_instance = C2Slots(x=1, y="test")
|
||||
|
||||
assert non_slot_instance.my_class() is C1
|
||||
assert slot_instance.my_class() is C1Slots
|
||||
|
||||
# Just assert they return something, and not an exception.
|
||||
assert non_slot_instance.my_super()
|
||||
assert slot_instance.my_super()
|
||||
|
||||
assert non_slot_instance.my_subclass() is C2
|
||||
assert slot_instance.my_subclass() is C2Slots
|
||||
|
|
Loading…
Reference in New Issue