Skip to content

Commit 066d1ca

Browse files
committed
Add narrative docs for #731
Signed-off-by: Hynek Schlawack <[email protected]>
1 parent 7a75e4d commit 066d1ca

File tree

2 files changed

+61
-11
lines changed

2 files changed

+61
-11
lines changed

changelog.d/731.change.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
``__attrs__init__()`` will now be injected if ``init==False`` or if ``auto_detect=True`` and a user-defined ``__init__()`` exists.
1+
``__attrs__init__()`` will now be injected if ``init=False``, or if ``auto_detect=True`` and a user-defined ``__init__()`` exists.
22

33
This enables users to do "pre-init" work in their ``__init__()`` (such as ``super().__init__()``).
44

5-
``__init__()`` can then delegate constructor argument processing to ``__attrs_init__(*args, **kwargs)``.
5+
``__init__()`` can then delegate constructor argument processing to ``self.__attrs_init__(*args, **kwargs)``.

docs/init.rst

+59-9
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ For similar reasons, we strongly discourage from patterns like::
3434

3535
pt = Point(**row.attributes)
3636

37-
which couples your classes to the data model.
37+
which couples your classes to the database data model.
3838
Try to design your classes in a way that is clean and convenient to use -- not based on your database format.
3939
The database format can change anytime and you're stuck with a bad class design that is hard to change.
40-
Embrace classmethods as a filter between reality and what's best for you to work with.
40+
Embrace functions and classmethods as a filter between reality and what's best for you to work with.
4141

4242
If you look for object serialization, there's a bunch of projects listed on our ``attrs`` extensions `Wiki page`_.
4343
Some of them even support nested schemas.
@@ -315,13 +315,62 @@ A converter will override an explicit type annotation or ``type`` argument.
315315
{'return': None, 'x': <class 'str'>}
316316

317317

318-
Post-Init Hook
319-
--------------
318+
Hooking Yourself Into Initialization
319+
------------------------------------
320320

321321
Generally speaking, the moment you think that you need finer control over how your class is instantiated than what ``attrs`` offers, it's usually best to use a classmethod factory or to apply the `builder pattern <https://en.wikipedia.org/wiki/Builder_pattern>`_.
322322

323-
However, sometimes you need to do that one quick thing after your class is initialized.
324-
And for that ``attrs`` offers the ``__attrs_post_init__`` hook that is automatically detected and run after ``attrs`` is done initializing your instance:
323+
However, sometimes you need to do that one quick thing before or after your class is initialized.
324+
And for that ``attrs`` offers three means:
325+
326+
- ``__attrs_pre_init__`` is automatically detected and run *before* ``attrs`` starts initializing.
327+
This is useful if you need to inject a call to ``super().__init__()``.
328+
- ``__attrs_post_init__`` is automatically detected and run *after* ``attrs`` is done initializing your instance.
329+
This is useful if you want to derive some attribute from others or perform some kind of validation over the whole instance.
330+
- ``__attrs_init__`` is written and attached to your class *instead* of ``__init__``, if ``attrs`` is told to not write one (i.e. ``init=False`` or a combination of ``auto_detect=True`` and a custom ``__init__``).
331+
This is useful if you want full control over the initialization process, but don't want to set the attributes by hand.
332+
333+
334+
Pre Init
335+
~~~~~~~~
336+
337+
The sole reason for the existance of ``__attrs_pre_init__`` is to give users the chance to call ``super().__init__()``, because some subclassing-based APIs require that.
338+
339+
.. doctest::
340+
341+
>>> @attr.s
342+
... class C(object):
343+
... x = attr.ib()
344+
... def __attrs_pre_init__(self):
345+
... super().__init__()
346+
>>> C(42)
347+
C(x=42)
348+
349+
If you need more control, use the custom init approach described next.
350+
351+
352+
Custom Init
353+
~~~~~~~~~~~
354+
355+
If you tell ``attrs`` to not write an ``__init__``, it will write an ``__attrs_init__`` instead, with the same code that it would have used for ``__init__``.
356+
You have full control over the initialization, but also have to type out the types of your arguments etc.
357+
Here's an example of a manual default value:
358+
359+
.. doctest::
360+
361+
>>> from typing import Optional
362+
>>> @attr.s(auto_detect=True) # or init=False
363+
... class C(object):
364+
... x = attr.ib()
365+
...
366+
... def __init__(self, x: int = 42):
367+
... self.__attrs_init__(x)
368+
>>> C()
369+
C(x=42)
370+
371+
372+
Post Init
373+
~~~~~~~~~
325374

326375
.. doctest::
327376

@@ -370,13 +419,14 @@ Order of Execution
370419

371420
If present, the hooks are executed in the following order:
372421

373-
1. For each attribute, in the order it was declared:
422+
1. ``__attrs_pre_init__`` (if present on *current* class)
423+
2. For each attribute, in the order it was declared:
374424

375425
a. default factory
376426
b. converter
377427

378-
2. *all* validators
379-
3. ``__attrs_post_init__``
428+
3. *all* validators
429+
4. ``__attrs_post_init__`` (if present on *current* class)
380430

381431
Notably this means, that you can access all attributes from within your validators, but your converters have to deal with invalid values and have to return a valid value.
382432

0 commit comments

Comments
 (0)