Make Attribute instances immutable and slotted. (#51)
This commit is contained in:
parent
74b31faca8
commit
46ac6b6046
|
@ -25,8 +25,9 @@ Changes:
|
|||
|
||||
- ``attr.asdict``\ 's ``dict_factory`` arguments is now propagated on recursion.
|
||||
`#45 <https://github.com/hynek/attrs/issues/45>`_
|
||||
- ``attr.asdict`` and ``attr.has`` are significantly faster.
|
||||
- ``attr.asdict``, ``attr.has`` and ``attr.fields`` are significantly faster.
|
||||
`#48 <https://github.com/hynek/attrs/issues/48>`_
|
||||
`#51 <https://github.com/hynek/attrs/issues/51>`_
|
||||
|
||||
|
||||
----
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import absolute_import, division, print_function
|
|||
import copy
|
||||
|
||||
from ._compat import iteritems
|
||||
from ._make import Attribute, NOTHING, _fast_attrs_iterate
|
||||
from ._make import Attribute, NOTHING, fields
|
||||
|
||||
|
||||
def asdict(inst, recurse=True, filter=None, dict_factory=dict):
|
||||
|
@ -30,7 +30,7 @@ def asdict(inst, recurse=True, filter=None, dict_factory=dict):
|
|||
.. versionadded:: 16.0.0
|
||||
*dict_factory*
|
||||
"""
|
||||
attrs = _fast_attrs_iterate(inst)
|
||||
attrs = fields(inst.__class__)
|
||||
rv = dict_factory()
|
||||
for a in attrs:
|
||||
v = getattr(inst, a.name)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import linecache
|
||||
|
||||
|
@ -417,16 +416,7 @@ def fields(cl):
|
|||
raise ValueError("{cl!r} is not an attrs-decorated class.".format(
|
||||
cl=cl
|
||||
))
|
||||
return copy.deepcopy(attrs)
|
||||
|
||||
|
||||
def _fast_attrs_iterate(inst):
|
||||
"""
|
||||
Fast internal iteration over the attr descriptors.
|
||||
|
||||
Using fields to iterate is slow because it involves deepcopy.
|
||||
"""
|
||||
return inst.__class__.__attrs_attrs__
|
||||
return attrs
|
||||
|
||||
|
||||
def validate(inst):
|
||||
|
@ -440,7 +430,7 @@ def validate(inst):
|
|||
if _config._run_validators is False:
|
||||
return
|
||||
|
||||
for a in _fast_attrs_iterate(inst):
|
||||
for a in fields(inst.__class__):
|
||||
if a.validator is not None:
|
||||
a.validator(inst, a, getattr(inst, a.name))
|
||||
|
||||
|
@ -453,7 +443,7 @@ def _convert(inst):
|
|||
|
||||
:param inst: Instance of a class with ``attrs`` attributes.
|
||||
"""
|
||||
for a in _fast_attrs_iterate(inst):
|
||||
for a in inst.__class__.__attrs_attrs__:
|
||||
if a.convert is not None:
|
||||
setattr(inst, a.name, a.convert(getattr(inst, a.name)))
|
||||
|
||||
|
@ -534,37 +524,38 @@ class Attribute(object):
|
|||
|
||||
Plus *all* arguments of :func:`attr.ib`.
|
||||
"""
|
||||
_attributes = [
|
||||
"name", "default", "validator", "repr", "cmp", "hash", "init",
|
||||
"convert"
|
||||
] # we can't use ``attrs`` so we have to cheat a little.
|
||||
__slots__ = ('name', 'default', 'validator', 'repr', 'cmp', 'hash', 'init',
|
||||
'convert')
|
||||
|
||||
_optional = {"convert": None}
|
||||
|
||||
def __init__(self, **kw):
|
||||
if len(kw) > len(Attribute._attributes):
|
||||
if len(kw) > len(Attribute.__slots__):
|
||||
raise TypeError("Too many arguments.")
|
||||
for a in Attribute._attributes:
|
||||
for a in Attribute.__slots__:
|
||||
try:
|
||||
setattr(self, a, kw[a])
|
||||
object.__setattr__(self, a, kw[a])
|
||||
except KeyError:
|
||||
if a in Attribute._optional:
|
||||
setattr(self, a, self._optional[a])
|
||||
object.__setattr__(self, a, self._optional[a])
|
||||
else:
|
||||
raise TypeError("Missing argument '{arg}'.".format(arg=a))
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
raise AttributeError("can't set attribute") # To mirror namedtuple.
|
||||
|
||||
@classmethod
|
||||
def from_counting_attr(cl, name, ca):
|
||||
return cl(name=name,
|
||||
**dict((k, getattr(ca, k))
|
||||
for k
|
||||
in Attribute._attributes
|
||||
in Attribute.__slots__
|
||||
if k != "name"))
|
||||
|
||||
|
||||
_a = [Attribute(name=name, default=NOTHING, validator=None,
|
||||
repr=True, cmp=True, hash=True, init=True)
|
||||
for name in Attribute._attributes]
|
||||
for name in Attribute.__slots__]
|
||||
Attribute = _add_hash(
|
||||
_add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a), attrs=_a
|
||||
)
|
||||
|
|
|
@ -6,9 +6,9 @@ from __future__ import absolute_import, division, print_function
|
|||
|
||||
import pytest
|
||||
from hypothesis import given
|
||||
from hypothesis.strategies import booleans
|
||||
from hypothesis.strategies import booleans, sampled_from
|
||||
|
||||
from . import simple_attr
|
||||
from . import simple_attr, simple_attrs
|
||||
from attr import _config
|
||||
from attr._compat import PY3
|
||||
from attr._make import (
|
||||
|
@ -23,6 +23,8 @@ from attr._make import (
|
|||
validate,
|
||||
)
|
||||
|
||||
attrs = simple_attrs.map(lambda c: Attribute.from_counting_attr('name', c))
|
||||
|
||||
|
||||
class TestCountingAttr(object):
|
||||
"""
|
||||
|
@ -184,6 +186,14 @@ class TestAttributes(object):
|
|||
assert "C3()" == repr(C3())
|
||||
assert C3() == C3()
|
||||
|
||||
@given(attr=attrs, attr_name=sampled_from(Attribute.__slots__))
|
||||
def test_immutable(self, attr, attr_name):
|
||||
"""
|
||||
Attribute instances are immutable.
|
||||
"""
|
||||
with pytest.raises(AttributeError):
|
||||
setattr(attr, attr_name, 1)
|
||||
|
||||
@pytest.mark.parametrize("method_name", [
|
||||
"__repr__",
|
||||
"__eq__",
|
||||
|
@ -386,15 +396,6 @@ class TestFields(object):
|
|||
"""
|
||||
assert all(isinstance(a, Attribute) for a in fields(C))
|
||||
|
||||
def test_copies(self, C):
|
||||
"""
|
||||
Returns a new list object with new `Attribute` objects.
|
||||
"""
|
||||
assert C.__attrs_attrs__ is not fields(C)
|
||||
assert all(new == original and new is not original
|
||||
for new, original
|
||||
in zip(fields(C), C.__attrs_attrs__))
|
||||
|
||||
|
||||
class TestConvert(object):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue