Generally speaking, you should keep as little logic as possible in it, and you should think about what the class needs and not how it is going to be instantiated.
Passing complex objects into ``__init__`` and then using them to derive data for the class unnecessarily couples your new class with the old class which makes it harder to test and also will cause problems later.
So assuming you use an ORM and want to extract 2D points from a row object, do not write code like this::
Now you can instantiate ``Point``\ s without creating fake row objects in your tests and you can have as many smart creation helpers as you want, in case more data sources appear.
For similar reasons, we strongly discourage from patterns like::
If you look for object serialization, there's a bunch of projects listed on our ``attrs`` extensions `Wiki page`_.
Some of them even support nested schemas.
Private Attributes
------------------
One thing people tend to find confusing is the treatment of private attributes that start with an underscore.
``attrs`` follows the doctrine that `there is no such thing as a private argument`_ and strips the underscores from the name when writing the ``__init__`` method signature:
It's important that the decorated method -- or any other method or property! -- doesn't have the same name as the attribute, otherwise it would overwrite the attribute definition.
Please note that as with function and method signatures, ``default=[]`` will *not* do what you may think it might do:
Please note that the decorator based defaults have one gotcha:
they are executed when the attribute is set, that means depending on the order of attributes, the ``self`` object may not be fully initialized when they're called.
Therefore you should use ``self`` as little as possible.
Even the smartest of us can `get confused`_ by what happens if you pass partially initialized objects around.
It takes either a callable or a list of callables (usually functions) and treats them as validators that receive the same arguments as with the decorator approach.
TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
Of course you can mix and match the two approaches at your convenience.
If you define validators both ways for an attribute, they are both ran:
TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=one), <class 'int'>, '128')
TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=None), <class 'int'>, '128')
You can achieve the same by using the context manager:
TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=None), <class 'int'>, '128')
.._converters:
Converters
----------
Finally, sometimes you may want to normalize the values coming in.
For that ``attrs`` comes with converters.
Attributes can have a ``converter`` function specified, which will be called with the attribute's passed-in value to get a new value to use.
This can be useful for doing type-conversions on values that you don't want to force your callers to do.
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>`_.
However, sometimes you need to do that one quick thing before or after your class is initialized.
And for that ``attrs`` offers three means:
-``__attrs_pre_init__`` is automatically detected and run *before*``attrs`` starts initializing.
This is useful if you need to inject a call to ``super().__init__()``.
-``__attrs_post_init__`` is automatically detected and run *after*``attrs`` is done initializing your instance.
This is useful if you want to derive some attribute from others or perform some kind of validation over the whole instance.
-``__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__``).
This is useful if you want full control over the initialization process, but don't want to set the attributes by hand.
The sole reason for the existence of ``__attrs_pre_init__`` is to give users the chance to call ``super().__init__()``, because some subclassing-based APIs require that.
If you need more control, use the custom init approach described next.
Custom Init
~~~~~~~~~~~
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__``.
You have full control over the initialization, but also have to type out the types of your arguments etc.
If you need to set attributes on a frozen class, you'll have to resort to the `same trick <how-frozen>` as ``attrs`` and use :meth:`object.__setattr__`:
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.