attrs/docs/how-does-it-work.rst

78 lines
3.2 KiB
ReStructuredText
Raw Normal View History

2016-08-15 11:59:04 +00:00
.. _how:
How Does It Work?
=================
2016-08-27 07:27:16 +00:00
Boilerplate
-----------
2016-08-15 11:59:04 +00:00
``attrs`` certainly isn't the first library that aims to simplify class definition in Python.
But its **declarative** approach combined with **no runtime overhead** lets it stand out.
Once you apply the ``@attr.s`` decorator to a class, ``attrs`` searches the class object for instances of ``attr.ib``\ s.
Internally they're a representation of the data passed into ``attr.ib`` along with a counter to preserve the order of the attributes.
2016-08-15 15:24:09 +00:00
In order to ensure that sub-classing works as you'd expect it to work, ``attrs`` also walks the class hierarchy and collects the attributes of all super-classes.
2016-08-15 11:59:04 +00:00
Please note that ``attrs`` does *not* call ``super()`` *ever*.
It will write dunder methods to work on *all* of those attributes which also has performance benefits due to fewer function calls.
2016-08-15 11:59:04 +00:00
Once ``attrs`` knows what attributes it has to work on, it writes the requested dunder methods and attaches them to your class.
To be very clear: if you define a class with a single attribute without a default value, the generated ``__init__`` will look *exactly* how you'd expect:
.. doctest::
>>> import attr, inspect
>>> @attr.s
... class C:
... x = attr.ib()
>>> print(inspect.getsource(C.__init__))
def __init__(self, x):
self.x = x
<BLANKLINE>
No magic, no meta programming, no expensive introspection at runtime.
****
Everything until this point happens exactly *once* when the class is defined.
As soon as a class is done, it's done.
2016-08-27 07:27:16 +00:00
And it's just a regular Python class like any other, except for a single ``__attrs_attrs__`` attribute that can be used for introspection or for writing your own tools and decorators on top of ``attrs`` (like :func:`attr.asdict`).
2016-08-15 11:59:04 +00:00
And once you start instantiating your classes, ``attrs`` is out of your way completely.
This **static** approach was very much a design goal of ``attrs`` and what I strongly believe makes it distinct.
2016-08-27 07:27:16 +00:00
.. _how-frozen:
Immutability
------------
In order to give you immutability, ``attrs`` will attach a ``__setattr__`` method to your class that raises a :exc:`attr.exceptions.FrozenInstanceError` whenever anyone tries to set an attribute.
2017-08-18 15:19:11 +00:00
To circumvent that ourselves in ``__init__``, ``attrs`` uses (an aggressively cached) :meth:`object.__setattr__` to set your attributes.
This is (still) slower than a plain assignment:
2016-08-27 07:27:16 +00:00
.. code-block:: none
$ pyperf timeit --rigorous \
-s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True)" \
"C(1, 2, 3)"
........................................
Median +- std dev: 378 ns +- 12 ns
$ pyperf timeit --rigorous \
-s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True, frozen=True)" \
"C(1, 2, 3)"
........................................
Median +- std dev: 676 ns +- 16 ns
2017-08-18 15:19:11 +00:00
So on a standard notebook the difference is about 300 nanoseconds (1 second is 1,000,000,000 nanoseconds).
2016-08-27 07:27:16 +00:00
It's certainly something you'll feel in a hot loop but shouldn't matter in normal code.
Pick what's more important to you.
****
2017-08-18 15:19:11 +00:00
Once constructed, frozen instances don't differ in any way from regular ones except that you cannot change its attributes.