Skip to content

Commit

Permalink
Speed up Attribute instantiation. (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinche authored and hynek committed Aug 24, 2016
1 parent c6fbec9 commit a7ba512
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 45 deletions.
29 changes: 17 additions & 12 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from ._compat import exec_, iteritems, isclass, iterkeys
from .exceptions import FrozenInstanceError

# This is used at least twice, so cache it here.
_obj_setattr = object.__setattr__


class _Nothing(object):
"""
Expand Down Expand Up @@ -414,7 +417,7 @@ def _add_init(cls, frozen):
if frozen is True:
# Save the lookup overhead in __init__ if we need to circumvent
# immutability.
globs["_cached_setattr"] = object.__setattr__
globs["_cached_setattr"] = _obj_setattr
exec_(bytecode, globs, locs)
init = locs["__init__"]

Expand Down Expand Up @@ -596,17 +599,19 @@ class Attribute(object):

_optional = {"convert": None}

def __init__(self, **kw):
if len(kw) > len(Attribute.__slots__):
raise TypeError("Too many arguments.")
for a in Attribute.__slots__:
try:
object.__setattr__(self, a, kw[a])
except KeyError:
if a in Attribute._optional:
object.__setattr__(self, a, self._optional[a])
else:
raise TypeError("Missing argument '{arg}'.".format(arg=a))
def __init__(self, name, default, validator, repr, cmp, hash, init,
convert=None):
# Cache this descriptor here to speed things up later.
__bound_setattr = _obj_setattr.__get__(self, Attribute)

__bound_setattr('name', name)
__bound_setattr('default', default)
__bound_setattr('validator', validator)
__bound_setattr('repr', repr)
__bound_setattr('cmp', cmp)
__bound_setattr('hash', hash)
__bound_setattr('init', init)
__bound_setattr('convert', convert)

def __setattr__(self, name, value):
raise FrozenInstanceError()
Expand Down
3 changes: 2 additions & 1 deletion tests/test_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ def assert_proper_dict_class(obj, obj_dict):
assert isinstance(obj_dict[field.name], dict_class)
for key, val in field_val.items():
if has(val.__class__):
assert_proper_dict_class(val, obj_dict[key])
assert_proper_dict_class(val,
obj_dict[field.name][key])

assert_proper_dict_class(obj, obj_dict)

Expand Down
24 changes: 0 additions & 24 deletions tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from attr._compat import PY2
from attr._make import (
Attribute,
NOTHING,
_CountingAttr,
_transform_attrs,
attr,
Expand Down Expand Up @@ -294,29 +293,6 @@ class D(object):
pass


class TestAttribute(object):
"""
Tests for `Attribute`.
"""
def test_missing_argument(self):
"""
Raises `TypeError` if an Argument is missing.
"""
with pytest.raises(TypeError) as e:
Attribute(default=NOTHING, validator=None)
assert ("Missing argument 'name'.",) == e.value.args

def test_too_many_arguments(self):
"""
Raises `TypeError` if extra arguments are passed.
"""
with pytest.raises(TypeError) as e:
Attribute(name="foo", default=NOTHING,
factory=NOTHING, validator=None,
repr=True, cmp=True, hash=True, init=True, convert=None)
assert ("Too many arguments.",) == e.value.args


class TestMakeClass(object):
"""
Tests for `make_class`.
Expand Down
20 changes: 12 additions & 8 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ def _create_hyp_nested_strategy(simple_class_strategy):
Create a recursive attrs class.
Given a strategy for building (simpler) classes, create and return
a strategy for building classes that have the simpler class as an
attribute.
a strategy for building classes that have as an attribute: either just
the simpler class, a list of simpler classes, or a dict mapping the string
"cls" to a simpler class.
"""
# Use a tuple strategy to combine simple attributes and an attr class.
def just_class(tup):
Expand All @@ -105,10 +106,13 @@ def dict_of_class(tup):
combined_attrs.append(attr.ib(default=default))
return _create_hyp_class(combined_attrs)

return st.one_of(st.tuples(st.lists(simple_attrs), simple_class_strategy)
.map(just_class),
st.tuples(st.lists(simple_attrs), simple_class_strategy)
.map(list_of_class))
# A strategy producing tuples of the form ([list of attributes], <given
# class strategy>).
attrs_and_classes = st.tuples(list_of_attrs, simple_class_strategy)

return st.one_of(attrs_and_classes.map(just_class),
attrs_and_classes.map(list_of_class),
attrs_and_classes.map(dict_of_class))

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

# Python functions support up to 255 arguments.
simple_classes = (st.lists(simple_attrs, average_size=9, max_size=50)
.map(_create_hyp_class))
list_of_attrs = st.lists(simple_attrs, average_size=9, max_size=50)
simple_classes = list_of_attrs.map(_create_hyp_class)

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

0 comments on commit a7ba512

Please sign in to comment.