Skip to content

Commit

Permalink
Add ability for __post_init__ method to be defined.
Browse files Browse the repository at this point in the history
If this method is defined for a class, it will get executed at the end
of ``__init__``.  Previously, there was no way to extend what was
done during ``__init__`` when using ``@attr.s``.
  • Loading branch information
tbeadle committed Nov 14, 2016
1 parent 4849dc1 commit 7724d31
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Changes:

- Don't overwrite ``__name__`` with ``__qualname__`` for ``attr.s(slots=True)`` classes.
`#99 <https://github.com/hynek/attrs/issues/99>`_
- Allow for a ``__post_init__`` method that, if defined, will get executed at
the end of the ``__init__`` that gets constructed for an ``@attr.s``-decorated
class.
`#111 <https://github.com/hynek/attrs/pull/111>`_


----
Expand Down
20 changes: 20 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,26 @@ You can still have power over the attributes if you pass a dictionary of name: `
>>> i.y
[]

Sometimes, you want to have your class's ``__init__`` method do more than just
the initialization, validation, etc. that gets done for you automatically when
using ``@attr.s``.
To do this, just define a ``__post_init__`` method in your class.
It will get called at the end of the ``__init__`` method that has been
autogenerated.

.. doctest::

>>> @attr.s
... class C(object):
... x = attr.ib()
... y = attr.ib()
...
... def __post_init__(self):
... self.z = self.x + self.y
>>> obj = C(x=1, y=2)
>>> obj.z
3

Finally, you can exclude single attributes from certain methods:

.. doctest::
Expand Down
10 changes: 8 additions & 2 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,11 @@ def _add_init(cls, frozen):
sha1.hexdigest()
)

script, globs = _attrs_to_script(attrs, frozen)
script, globs = _attrs_to_script(
attrs,
frozen,
getattr(cls, '__post_init__', False),
)
locs = {}
bytecode = compile(script, unique_filename, "exec")
attr_dict = dict((a.name, a) for a in attrs)
Expand Down Expand Up @@ -540,7 +544,7 @@ def validate(inst):
a.validator(inst, a, getattr(inst, a.name))


def _attrs_to_script(attrs, frozen):
def _attrs_to_script(attrs, frozen, post_init):
"""
Return a script of an initializer for *attrs* and a dict of globals.
Expand Down Expand Up @@ -680,6 +684,8 @@ def fmt_setter_with_converter(attr_name, value_var):
a.name))
names_for_globals[val_name] = a.validator
names_for_globals[attr_name] = a
if post_init:
lines.append("self.__post_init__()")

return """\
def __init__(self, {args}):
Expand Down
15 changes: 15 additions & 0 deletions tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,21 @@ class D(object):
assert C.D.__name__ == "D"
assert C.D.__qualname__ == C.__qualname__ + ".D"

def test_post_init(self):
"""
Verify that __post_init__ gets called if defined.
"""
@attributes
class C(object):
x = attr()
y = attr()

def __post_init__(self2):
self2.z = self2.x + self2.y

c = C(x=10, y=20)
assert 30 == getattr(c, 'z', None)


@attributes
class GC(object):
Expand Down
8 changes: 7 additions & 1 deletion tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,13 @@ class HypClass:
frozen_flag = draw(st.booleans()) if frozen is None else frozen
slots_flag = draw(st.booleans()) if slots is None else slots

return make_class('HypClass', dict(zip(_gen_attr_names(), attrs)),
cls_dict = dict(zip(_gen_attr_names(), attrs))
post_init_flag = draw(st.booleans())
if post_init_flag:
def post_init(self):
pass
cls_dict['__post_init__'] = post_init
return make_class('HypClass', cls_dict,
slots=slots_flag, frozen=frozen_flag)

# Ok, so st.recursive works by taking a base strategy (in this case,
Expand Down

0 comments on commit 7724d31

Please sign in to comment.