Skip to content

Commit a7ba512

Browse files
Tinchehynek
authored andcommitted
Speed up Attribute instantiation. (#70)
1 parent c6fbec9 commit a7ba512

File tree

4 files changed

+31
-45
lines changed

4 files changed

+31
-45
lines changed

src/attr/_make.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
from ._compat import exec_, iteritems, isclass, iterkeys
88
from .exceptions import FrozenInstanceError
99

10+
# This is used at least twice, so cache it here.
11+
_obj_setattr = object.__setattr__
12+
1013

1114
class _Nothing(object):
1215
"""
@@ -414,7 +417,7 @@ def _add_init(cls, frozen):
414417
if frozen is True:
415418
# Save the lookup overhead in __init__ if we need to circumvent
416419
# immutability.
417-
globs["_cached_setattr"] = object.__setattr__
420+
globs["_cached_setattr"] = _obj_setattr
418421
exec_(bytecode, globs, locs)
419422
init = locs["__init__"]
420423

@@ -596,17 +599,19 @@ class Attribute(object):
596599

597600
_optional = {"convert": None}
598601

599-
def __init__(self, **kw):
600-
if len(kw) > len(Attribute.__slots__):
601-
raise TypeError("Too many arguments.")
602-
for a in Attribute.__slots__:
603-
try:
604-
object.__setattr__(self, a, kw[a])
605-
except KeyError:
606-
if a in Attribute._optional:
607-
object.__setattr__(self, a, self._optional[a])
608-
else:
609-
raise TypeError("Missing argument '{arg}'.".format(arg=a))
602+
def __init__(self, name, default, validator, repr, cmp, hash, init,
603+
convert=None):
604+
# Cache this descriptor here to speed things up later.
605+
__bound_setattr = _obj_setattr.__get__(self, Attribute)
606+
607+
__bound_setattr('name', name)
608+
__bound_setattr('default', default)
609+
__bound_setattr('validator', validator)
610+
__bound_setattr('repr', repr)
611+
__bound_setattr('cmp', cmp)
612+
__bound_setattr('hash', hash)
613+
__bound_setattr('init', init)
614+
__bound_setattr('convert', convert)
610615

611616
def __setattr__(self, name, value):
612617
raise FrozenInstanceError()

tests/test_funcs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ def assert_proper_dict_class(obj, obj_dict):
7979
assert isinstance(obj_dict[field.name], dict_class)
8080
for key, val in field_val.items():
8181
if has(val.__class__):
82-
assert_proper_dict_class(val, obj_dict[key])
82+
assert_proper_dict_class(val,
83+
obj_dict[field.name][key])
8384

8485
assert_proper_dict_class(obj, obj_dict)
8586

tests/test_make.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from attr._compat import PY2
1414
from attr._make import (
1515
Attribute,
16-
NOTHING,
1716
_CountingAttr,
1817
_transform_attrs,
1918
attr,
@@ -294,29 +293,6 @@ class D(object):
294293
pass
295294

296295

297-
class TestAttribute(object):
298-
"""
299-
Tests for `Attribute`.
300-
"""
301-
def test_missing_argument(self):
302-
"""
303-
Raises `TypeError` if an Argument is missing.
304-
"""
305-
with pytest.raises(TypeError) as e:
306-
Attribute(default=NOTHING, validator=None)
307-
assert ("Missing argument 'name'.",) == e.value.args
308-
309-
def test_too_many_arguments(self):
310-
"""
311-
Raises `TypeError` if extra arguments are passed.
312-
"""
313-
with pytest.raises(TypeError) as e:
314-
Attribute(name="foo", default=NOTHING,
315-
factory=NOTHING, validator=None,
316-
repr=True, cmp=True, hash=True, init=True, convert=None)
317-
assert ("Too many arguments.",) == e.value.args
318-
319-
320296
class TestMakeClass(object):
321297
"""
322298
Tests for `make_class`.

tests/utils.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ def _create_hyp_nested_strategy(simple_class_strategy):
8484
Create a recursive attrs class.
8585
8686
Given a strategy for building (simpler) classes, create and return
87-
a strategy for building classes that have the simpler class as an
88-
attribute.
87+
a strategy for building classes that have as an attribute: either just
88+
the simpler class, a list of simpler classes, or a dict mapping the string
89+
"cls" to a simpler class.
8990
"""
9091
# Use a tuple strategy to combine simple attributes and an attr class.
9192
def just_class(tup):
@@ -105,10 +106,13 @@ def dict_of_class(tup):
105106
combined_attrs.append(attr.ib(default=default))
106107
return _create_hyp_class(combined_attrs)
107108

108-
return st.one_of(st.tuples(st.lists(simple_attrs), simple_class_strategy)
109-
.map(just_class),
110-
st.tuples(st.lists(simple_attrs), simple_class_strategy)
111-
.map(list_of_class))
109+
# A strategy producing tuples of the form ([list of attributes], <given
110+
# class strategy>).
111+
attrs_and_classes = st.tuples(list_of_attrs, simple_class_strategy)
112+
113+
return st.one_of(attrs_and_classes.map(just_class),
114+
attrs_and_classes.map(list_of_class),
115+
attrs_and_classes.map(dict_of_class))
112116

113117
bare_attrs = st.just(attr.ib(default=None))
114118
int_attrs = st.integers().map(lambda i: attr.ib(default=i))
@@ -121,8 +125,8 @@ def dict_of_class(tup):
121125
dict_attrs)
122126

123127
# Python functions support up to 255 arguments.
124-
simple_classes = (st.lists(simple_attrs, average_size=9, max_size=50)
125-
.map(_create_hyp_class))
128+
list_of_attrs = st.lists(simple_attrs, average_size=9, max_size=50)
129+
simple_classes = list_of_attrs.map(_create_hyp_class)
126130

127131
# Ok, so st.recursive works by taking a base strategy (in this case,
128132
# simple_classes) and a special function. This function receives a strategy,

0 commit comments

Comments
 (0)