Skip to content

Speed up Attribute instantiation. #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 24, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
@@ -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):
"""
@@ -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__"]

@@ -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()
3 changes: 2 additions & 1 deletion tests/test_funcs.py
Original file line number Diff line number Diff line change
@@ -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)

24 changes: 0 additions & 24 deletions tests/test_make.py
Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@
from attr._compat import PY2
from attr._make import (
Attribute,
NOTHING,
_CountingAttr,
_transform_attrs,
attr,
@@ -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`.
20 changes: 12 additions & 8 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -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):
@@ -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))
@@ -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,