2017-10-26 15:55:45 +00:00
`` attrs `` by Example
====================
2015-01-28 15:28:47 +00:00
2015-02-05 10:04:25 +00:00
Basics
------
2017-11-08 06:11:10 +00:00
The simplest possible usage is:
2015-01-28 15:28:47 +00:00
.. doctest ::
2021-12-27 09:07:18 +00:00
>>> from attrs import define
2021-11-22 06:35:36 +00:00
>>> @define
... class Empty:
2015-01-28 15:28:47 +00:00
... pass
>>> Empty()
Empty()
>>> Empty() == Empty()
True
>>> Empty() is Empty()
False
2016-08-16 10:25:22 +00:00
So in other words: `` attrs `` is useful even without actual attributes!
2015-01-28 15:28:47 +00:00
But you'll usually want some data on your classes, so let's add some:
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class Coordinates:
... x: int
... y: int
2015-01-28 15:28:47 +00:00
2016-08-15 08:00:23 +00:00
By default, all features are added, so you immediately have a fully functional data class with a nice `` repr `` string and comparison methods.
2015-01-28 15:28:47 +00:00
.. doctest ::
>>> c1 = Coordinates(1, 2)
>>> c1
Coordinates(x=1, y=2)
>>> c2 = Coordinates(x=2, y=1)
>>> c2
Coordinates(x=2, y=1)
>>> c1 == c2
False
2016-08-15 08:00:23 +00:00
As shown, the generated `` __init__ `` method allows for both positional and keyword arguments.
2015-01-28 16:36:58 +00:00
2015-02-08 11:32:32 +00:00
For private attributes, `` attrs `` will strip the leading underscores for keyword arguments:
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... _x: int
2015-02-08 11:32:32 +00:00
>>> C(x=1)
C(_x=1)
2015-12-19 12:51:31 +00:00
If you want to initialize your private attributes yourself, you can do that too:
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... _x: int = field(init=False, default=42)
2015-12-19 12:51:31 +00:00
>>> C()
C(_x=42)
>>> C(23)
Traceback (most recent call last):
...
TypeError: __init__() takes exactly 1 argument (2 given)
2017-05-23 00:32:39 +00:00
An additional way of defining attributes is supported too.
2015-02-08 11:32:32 +00:00
This is useful in times when you want to enhance classes that are not yours (nice `` __repr__ `` for Django models anyone?):
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> class SomethingFromSomeoneElse:
2015-02-08 11:32:32 +00:00
... def __init__(self, x):
... self.x = x
2021-11-22 06:35:36 +00:00
>>> SomethingFromSomeoneElse = define(
2017-11-08 16:24:01 +00:00
... these={
2021-11-22 06:35:36 +00:00
... "x": field()
2017-11-08 16:24:01 +00:00
... }, init=False)(SomethingFromSomeoneElse)
2015-02-08 11:32:32 +00:00
>>> SomethingFromSomeoneElse(1)
SomethingFromSomeoneElse(x=1)
2017-11-08 06:11:10 +00:00
`Subclassing is bad for you <https://www.youtube.com/watch?v=3MNVP9-hglc> `_ , but `` attrs `` will still do what you'd hope for:
2015-02-18 20:31:32 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define(slots=False)
... class A:
... a: int
2015-02-18 20:31:32 +00:00
... def get_a(self):
... return self.a
2021-11-22 06:35:36 +00:00
>>> @define(slots=False)
... class B:
... b: int
>>> @define(slots=False)
... class C(B, A):
... c: int
2015-02-18 20:31:32 +00:00
>>> i = C(1, 2, 3)
>>> i
C(a=1, b=2, c=3)
>>> i == C(1, 2, 3)
True
>>> i.get_a()
1
2021-11-22 06:35:36 +00:00
:term: `Slotted classes <slotted classes>` , which are the default for the new APIs, don't play well with multiple inheritance so we don't use them in the example.
2015-02-20 08:44:50 +00:00
2021-11-22 06:35:36 +00:00
The order of the attributes is defined by the `MRO <https://www.python.org/download/releases/2.3/mro/> `_ .
2015-01-29 09:17:08 +00:00
2018-08-11 04:40:01 +00:00
Keyword-only Attributes
~~~~~~~~~~~~~~~~~~~~~~~
2020-10-19 09:00:00 +00:00
You can also add `keyword-only <https://docs.python.org/3/glossary.html#keyword-only-parameter> `_ attributes:
2018-08-11 04:40:01 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
2018-08-11 04:40:01 +00:00
... class A:
2021-11-22 06:35:36 +00:00
... a: int = field(kw_only=True)
2018-08-11 04:40:01 +00:00
>>> A()
Traceback (most recent call last):
...
TypeError: A() missing 1 required keyword-only argument: 'a'
>>> A(a=1)
A(a=1)
2021-11-22 06:35:36 +00:00
`` kw_only `` may also be specified at via `` define `` , and will apply to all attributes:
2018-08-11 04:40:01 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define(kw_only=True)
2018-08-11 04:40:01 +00:00
... class A:
2021-11-22 06:35:36 +00:00
... a: int
... b: int
2018-08-11 04:40:01 +00:00
>>> A(1, 2)
Traceback (most recent call last):
...
TypeError: __init__() takes 1 positional argument but 3 were given
>>> A(a=1, b=2)
A(a=1, b=2)
If you create an attribute with `` init=False `` , the `` kw_only `` argument is ignored.
Keyword-only attributes allow subclasses to add attributes without default values, even if the base class defines attributes with default values:
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
2018-08-11 04:40:01 +00:00
... class A:
2021-11-22 06:35:36 +00:00
... a: int = 0
>>> @define
2018-08-11 04:40:01 +00:00
... class B(A):
2021-11-22 06:35:36 +00:00
... b: int = field(kw_only=True)
2018-08-11 04:40:01 +00:00
>>> B(b=1)
B(a=0, b=1)
>>> B()
Traceback (most recent call last):
...
TypeError: B() missing 1 required keyword-only argument: 'b'
If you don't set `` kw_only=True `` , then there's is no valid attribute ordering and you'll get an error:
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
2018-08-11 04:40:01 +00:00
... class A:
2021-11-22 06:35:36 +00:00
... a: int = 0
>>> @define
2018-08-11 04:40:01 +00:00
... class B(A):
2021-11-22 06:35:36 +00:00
... b: int
2018-08-11 04:40:01 +00:00
Traceback (most recent call last):
...
2021-11-22 06:35:36 +00:00
ValueError: No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: Attribute(name='b', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, converter=None, metadata=mappingproxy({}), type=int, kw_only=False)
2018-08-11 04:40:01 +00:00
2016-09-10 17:14:34 +00:00
.. _asdict:
2016-09-11 06:54:39 +00:00
Converting to Collections Types
-------------------------------
2015-02-05 10:04:25 +00:00
2019-09-09 13:02:16 +00:00
When you have a class with data, it often is very convenient to transform that class into a `dict` (for example if you want to serialize it to JSON):
2015-02-05 10:04:25 +00:00
.. doctest ::
2021-12-27 09:07:18 +00:00
>>> from attrs import asdict
2021-11-22 06:35:36 +00:00
>>> asdict(Coordinates(x=1, y=2))
2017-03-04 06:49:57 +00:00
{'x': 1, 'y': 2}
2015-02-05 10:04:25 +00:00
Some fields cannot or should not be transformed.
2021-12-27 09:07:18 +00:00
For that, `attrs.asdict` offers a callback that decides whether an attribute should be included:
2015-02-05 10:04:25 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
2015-02-05 10:04:25 +00:00
... class User(object):
2021-11-22 06:35:36 +00:00
... email: str
... password: str
>>> @define
... class UserList:
2021-12-15 12:57:16 +00:00
... users: list[User]
2021-11-22 06:35:36 +00:00
>>> asdict(UserList([User("jane@doe.invalid", "s33kred"),
... User("joe@doe.invalid", "p4ssw0rd")]),
... filter=lambda attr, value: attr.name != "password")
2015-02-05 10:04:25 +00:00
{'users': [{'email': 'jane@doe.invalid'}, {'email': 'joe@doe.invalid'}]}
2019-09-09 13:02:16 +00:00
For the common case where you want to `include <attr.filters.include>` or `exclude <attr.filters.exclude>` certain types or attributes, `` attrs `` ships with a few helpers:
2015-02-20 15:34:21 +00:00
.. doctest ::
2021-12-27 09:07:18 +00:00
>>> from attrs import asdict, filters, fields
2021-11-22 06:35:36 +00:00
>>> @define
... class User:
... login: str
... password: str
... id: int
>>> asdict(
2017-11-08 06:11:10 +00:00
... User("jane", "s33kred", 42),
2021-11-22 06:35:36 +00:00
... filter=filters.exclude(fields(User).password, int))
2015-02-20 15:34:21 +00:00
{'login': 'jane'}
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... x: str
... y: str
... z: int
>>> asdict(C("foo", "2", 3),
... filter=filters.include(int, fields(C).x))
2017-03-04 06:49:57 +00:00
{'x': 'foo', 'z': 3}
2015-02-20 15:34:21 +00:00
2016-09-11 06:54:39 +00:00
Other times, all you want is a tuple and `` attrs `` won't let you down:
.. doctest ::
>>> import sqlite3
2021-12-27 09:07:18 +00:00
>>> from attrs import astuple
2021-11-22 06:35:36 +00:00
>>> @define
2016-09-11 06:54:39 +00:00
... class Foo:
2021-11-22 06:35:36 +00:00
... a: int
... b: int
2016-09-11 06:54:39 +00:00
>>> foo = Foo(2, 3)
>>> with sqlite3.connect(":memory:") as conn:
... c = conn.cursor()
... c.execute("CREATE TABLE foo (x INTEGER PRIMARY KEY ASC, y)") #doctest: +ELLIPSIS
2021-11-22 06:35:36 +00:00
... c.execute("INSERT INTO foo VALUES (?, ?)", astuple(foo)) #doctest: +ELLIPSIS
2016-09-11 06:54:39 +00:00
... foo2 = Foo(*c.execute("SELECT x, y FROM foo").fetchone())
<sqlite3.Cursor object at ...>
<sqlite3.Cursor object at ...>
>>> foo == foo2
True
2021-11-22 06:35:36 +00:00
For more advanced transformations and conversions, we recommend you look at a companion library (such as `cattrs <https://github.com/python-attrs/cattrs> `_ ).
2016-09-11 06:54:39 +00:00
2015-02-05 10:04:25 +00:00
Defaults
--------
2015-01-28 16:36:58 +00:00
Sometimes you want to have default values for your initializer.
2021-11-22 06:35:36 +00:00
And sometimes you even want mutable objects as default values (ever accidentally used `` def f(arg=[]) `` ?).
2015-01-28 16:36:58 +00:00
`` attrs `` has you covered in both cases:
.. doctest ::
>>> import collections
2021-11-22 06:35:36 +00:00
>>> @define
... class Connection:
... socket: int
2015-01-28 16:36:58 +00:00
... @classmethod
2016-08-15 13:59:10 +00:00
... def connect(cls, db_string):
2016-08-16 10:25:22 +00:00
... # ... connect somehow to db_string ...
2016-08-15 13:59:10 +00:00
... return cls(socket=42)
2021-11-22 06:35:36 +00:00
>>> @define
... class ConnectionPool:
... db_string: str
... pool: collections.deque = Factory(collections.deque)
... debug: bool = False
2015-01-28 16:36:58 +00:00
... def get_connection(self):
... try:
... return self.pool.pop()
... except IndexError:
... if self.debug:
2016-02-17 11:51:02 +00:00
... print("New connection!")
2015-01-28 16:36:58 +00:00
... return Connection.connect(self.db_string)
... def free_connection(self, conn):
... if self.debug:
2016-02-17 11:51:02 +00:00
... print("Connection returned!")
2015-01-28 16:36:58 +00:00
... self.pool.appendleft(conn)
...
>>> cp = ConnectionPool("postgres://localhost")
>>> cp
ConnectionPool(db_string='postgres://localhost', pool=deque([]), debug=False)
>>> conn = cp.get_connection()
>>> conn
Connection(socket=42)
>>> cp.free_connection(conn)
>>> cp
ConnectionPool(db_string='postgres://localhost', pool=deque([Connection(socket=42)]), debug=False)
2021-11-23 12:56:38 +00:00
More information on why class methods for constructing objects are awesome can be found in this insightful `blog post <https://web.archive.org/web/20210130220433/http://as.ynchrono.us/2014/12/asynchronous-object-initialization.html> `_ .
2015-01-29 11:20:17 +00:00
2021-11-22 06:35:36 +00:00
Default factories can also be set using the `` factory `` argument to `` field `` , and using a decorator.
2017-07-20 15:30:20 +00:00
The method receives the partially initialized instance which enables you to base a default value on other attributes:
2017-05-16 07:36:39 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... x: int = 1
... y: int = field()
2017-05-16 07:36:39 +00:00
... @y.default
2019-06-17 07:17:29 +00:00
... def _any_name_except_a_name_of_an_attribute(self):
2017-05-16 07:36:39 +00:00
... return self.x + 1
2021-11-22 06:35:36 +00:00
... z: list = field(factory=list)
2017-05-16 07:36:39 +00:00
>>> C()
2021-11-22 06:35:36 +00:00
C(x=1, y=2, z=[])
2015-02-05 10:04:25 +00:00
2018-03-14 18:05:48 +00:00
2017-02-11 15:55:39 +00:00
.. _examples_validators:
2015-02-05 10:04:25 +00:00
Validators
----------
2017-04-20 12:59:33 +00:00
Although your initializers should do as little as possible (ideally: just initialize your instance according to the arguments!), it can come in handy to do some kind of validation on the arguments.
2015-02-09 12:16:56 +00:00
2018-08-22 13:56:54 +00:00
`` attrs `` offers two ways to define validators for each attribute and it's up to you to choose which one suits your style and project better.
2017-02-11 15:55:39 +00:00
2018-04-24 22:07:03 +00:00
You can use a decorator:
2017-02-11 15:55:39 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... x: int = field()
2017-02-11 15:55:39 +00:00
... @x.validator
... def check(self, attribute, value):
... if value > 42:
2017-06-07 04:22:44 +00:00
... raise ValueError("x must be smaller or equal to 42")
2017-02-11 15:55:39 +00:00
>>> C(42)
C(x=42)
>>> C(43)
Traceback (most recent call last):
...
ValueError: x must be smaller or equal to 42
2018-07-28 14:44:45 +00:00
...or a callable...
2015-01-29 11:26:25 +00:00
.. doctest ::
2021-12-27 09:07:18 +00:00
>>> from attrs import validators
2021-11-22 06:35:36 +00:00
2015-02-09 12:30:29 +00:00
>>> def x_smaller_than_y(instance, attribute, value):
... if value >= instance.y:
... raise ValueError("'x' has to be smaller than 'y'!")
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... x: int = field(validator=[validators.instance_of(int),
... x_smaller_than_y])
... y: int
2015-02-09 12:30:29 +00:00
>>> C(x=3, y=4)
C(x=3, y=4)
>>> C(x=4, y=3)
2015-01-29 11:26:25 +00:00
Traceback (most recent call last):
...
2015-02-09 12:30:29 +00:00
ValueError: 'x' has to be smaller than 'y'!
2015-01-29 11:26:25 +00:00
2018-04-24 22:07:03 +00:00
...or both at once:
2017-02-04 13:37:49 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... x: int = field(validator=validators.instance_of(int))
2017-02-11 15:55:39 +00:00
... @x.validator
... def fits_byte(self, attribute, value):
2018-03-14 15:57:12 +00:00
... if not 0 <= value < 256:
2017-02-11 15:55:39 +00:00
... raise ValueError("value out of bounds")
2017-02-04 13:37:49 +00:00
>>> C(128)
C(x=128)
>>> C("128")
Traceback (most recent call last):
...
2021-11-22 06:35:36 +00:00
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=int, converter=None, kw_only=False), <class 'int'>, '128')
2017-02-04 13:37:49 +00:00
>>> C(256)
Traceback (most recent call last):
...
ValueError: value out of bounds
2021-11-22 06:35:36 +00:00
Please note that the decorator approach only works if -- and only if! -- the attribute in question has a `` field `` assigned.
Therefore if you use `` @default `` , it is *not* enough to annotate said attribute with a type.
2015-02-20 10:30:46 +00:00
2019-09-09 13:02:16 +00:00
`` attrs `` ships with a bunch of validators, make sure to `check them out <api_validators>` before writing your own:
2018-04-24 22:07:03 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... x: int = field(validator=validators.instance_of(int))
2018-04-24 22:07:03 +00:00
>>> C(42)
C(x=42)
>>> C("42")
2015-02-20 10:30:46 +00:00
Traceback (most recent call last):
...
2018-08-11 04:40:01 +00:00
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, kw_only=False), <type 'int'>, '42')
2018-04-24 22:07:03 +00:00
2021-12-25 14:15:10 +00:00
Please note that if you use `attr.s` (and not `attrs.define` ) to define your class, validators only run on initialization by default.
2021-10-28 05:00:01 +00:00
This behavior can be changed using the `` on_setattr `` argument.
2019-09-09 13:02:16 +00:00
Check out `validators` for more details.
2015-02-20 10:30:46 +00:00
2015-02-05 10:04:25 +00:00
2015-09-16 20:25:32 +00:00
Conversion
----------
2017-12-23 07:46:10 +00:00
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.
2015-11-18 16:53:38 +00:00
This can be useful for doing type-conversions on values that you don't want to force your callers to do.
2015-09-16 20:25:32 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... x: int = field(converter=int)
2015-09-16 20:25:32 +00:00
>>> o = C("1")
>>> o.x
1
2021-10-28 05:00:01 +00:00
Please note that converters only run on initialization.
2019-09-09 13:02:16 +00:00
Check out `converters` for more details.
2015-09-16 20:25:32 +00:00
2016-11-19 08:47:03 +00:00
.. _metadata:
Metadata
--------
2017-05-10 04:11:16 +00:00
All `` attrs `` attributes may include arbitrary metadata in the form of a read-only dictionary.
2016-11-19 08:47:03 +00:00
.. doctest ::
2021-12-27 09:07:18 +00:00
>>> from attrs import fields
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... x = field(metadata={'my_metadata': 1})
>>> fields(C).x.metadata
2016-11-19 08:47:03 +00:00
mappingproxy({'my_metadata': 1})
2021-11-22 06:35:36 +00:00
>>> fields(C).x.metadata['my_metadata']
2016-11-19 08:47:03 +00:00
1
Metadata is not used by `` attrs `` , and is meant to enable rich functionality in third-party libraries.
The metadata dictionary follows the normal dictionary rules: keys need to be hashable, and both keys and values are recommended to be immutable.
2019-09-09 13:02:16 +00:00
If you're the author of a third-party library with `` attrs `` integration, please see `Extending Metadata <extending_metadata>` .
2016-11-19 08:47:03 +00:00
2017-11-08 10:15:21 +00:00
Types
-----
2019-09-09 13:02:16 +00:00
`` attrs `` also allows you to associate a type with an attribute using either the *type* argument to `attr.ib` or -- as of Python 3.6 -- using `PEP 526 <https://www.python.org/dev/peps/pep-0526/> `_ -annotations:
2017-11-08 10:15:21 +00:00
.. doctest ::
2021-12-27 09:07:18 +00:00
>>> from attrs import attrib, fields
2021-11-22 06:35:36 +00:00
>>> @define
2017-11-08 10:15:21 +00:00
... class C:
2021-11-22 06:35:36 +00:00
... x: int
>>> fields(C).x.type
2017-11-08 10:15:21 +00:00
<class 'int'>
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... x = attrib(type=int)
>>> fields(C).x.type
2017-11-08 10:15:21 +00:00
<class 'int'>
2021-12-25 14:15:10 +00:00
If you don't mind annotating *all* attributes, you can even drop the `attrs.field` and assign default values instead:
2017-11-08 10:15:21 +00:00
.. doctest ::
>>> import typing
2021-12-27 09:07:18 +00:00
>>> from attrs import fields
2021-11-22 06:35:36 +00:00
>>> @define
2017-11-08 10:15:21 +00:00
... class AutoC:
... cls_var: typing.ClassVar[int] = 5 # this one is ignored
2021-12-15 12:57:16 +00:00
... l: list[int] = Factory(list)
2017-11-08 10:15:21 +00:00
... x: int = 1
2021-11-22 06:35:36 +00:00
... foo: str = "every attrib needs a type if auto_attribs=True"
2017-11-08 10:15:21 +00:00
... bar: typing.Any = None
2021-11-22 06:35:36 +00:00
>>> fields(AutoC).l.type
2021-12-15 12:57:16 +00:00
list[int]
2021-11-22 06:35:36 +00:00
>>> fields(AutoC).x.type
2017-11-08 10:15:21 +00:00
<class 'int'>
2021-11-22 06:35:36 +00:00
>>> fields(AutoC).foo.type
2017-11-08 10:15:21 +00:00
<class 'str'>
2021-11-22 06:35:36 +00:00
>>> fields(AutoC).bar.type
2017-11-08 10:15:21 +00:00
typing.Any
>>> AutoC()
AutoC(l=[], x=1, foo='every attrib needs a type if auto_attribs=True', bar=None)
>>> AutoC.cls_var
5
2018-04-06 08:36:29 +00:00
The generated `` __init__ `` method will have an attribute called `` __annotations__ `` that contains this type information.
2017-11-08 10:15:21 +00:00
2021-12-25 14:15:10 +00:00
If your annotations contain strings (e.g. forward references),
you can resolve these after all references have been defined by using :func: `attrs.resolve_types` .
2020-07-22 09:43:07 +00:00
This will replace the *type* attribute in the respective fields.
.. doctest ::
2021-12-27 09:07:18 +00:00
>>> from attrs import fields, resolve_types
2021-11-22 06:35:36 +00:00
>>> @define
2020-07-22 09:43:07 +00:00
... class A:
2021-12-15 12:57:16 +00:00
... a: 'list[A]'
2020-07-22 09:43:07 +00:00
... b: 'B'
...
2021-11-22 06:35:36 +00:00
>>> @define
2020-07-22 09:43:07 +00:00
... class B:
... a: A
...
2021-11-22 06:35:36 +00:00
>>> fields(A).a.type
2021-12-15 12:57:16 +00:00
'list[A]'
2021-11-22 06:35:36 +00:00
>>> fields(A).b.type
2020-07-22 09:43:07 +00:00
'B'
2021-11-22 06:35:36 +00:00
>>> resolve_types(A, globals(), locals())
2020-07-22 09:43:07 +00:00
<class 'A'>
2021-11-22 06:35:36 +00:00
>>> fields(A).a.type
2021-12-15 12:57:16 +00:00
list[A]
2021-11-22 06:35:36 +00:00
>>> fields(A).b.type
2020-07-22 09:43:07 +00:00
<class 'B'>
2021-12-15 12:57:16 +00:00
.. note ::
If you find yourself using string type annotations to handle forward references, wrap the entire type annotation in quotes instead of only the type you need a forward reference to (so `` 'list[A]' `` instead of `` list['A'] `` ).
This is a limitation of the Python typing system.
2017-11-08 10:15:21 +00:00
.. warning ::
`` attrs `` itself doesn't have any features that work on top of type metadata *yet* .
However it's useful for writing your own validators or serialization frameworks.
2016-03-28 19:05:00 +00:00
Slots
-----
2020-04-28 07:21:19 +00:00
:term: `Slotted classes <slotted classes>` have several advantages on CPython.
2021-12-25 14:15:10 +00:00
Defining `` __slots__ `` by hand is tedious, in `` attrs `` it's just a matter of using `attrs.define` or passing `` slots=True `` to `attr.s` :
2016-03-28 19:05:00 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> import attr
2016-03-28 19:05:00 +00:00
>>> @attr.s(slots=True)
2021-11-22 06:35:36 +00:00
... class Coordinates:
... x: int
... y: int
2016-03-28 19:05:00 +00:00
2016-08-27 07:27:16 +00:00
Immutability
------------
2015-02-05 10:04:25 +00:00
2016-08-27 07:27:16 +00:00
Sometimes you have instances that shouldn't be changed after instantiation.
Immutability is especially popular in functional programming and is generally a very good thing.
If you'd like to enforce it, `` attrs `` will try to help:
2015-01-29 19:50:42 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @frozen
... class C:
... x: int
2016-08-27 07:27:16 +00:00
>>> i = C(1)
>>> i.x = 2
Traceback (most recent call last):
...
attr.exceptions.FrozenInstanceError: can't set attribute
>>> i.x
1
2019-09-09 13:02:16 +00:00
Please note that true immutability is impossible in Python but it will `get <how-frozen>` you 99% there.
2016-08-27 07:27:16 +00:00
By themselves, immutable classes are useful for long-lived objects that should never change; like configurations for example.
In order to use them in regular program flow, you'll need a way to easily create new instances with changed attributes.
2019-09-09 13:02:16 +00:00
In Clojure that function is called `assoc <https://clojuredocs.org/clojure.core/assoc> `_ and `` attrs `` shamelessly imitates it: `attr.evolve` :
2016-08-27 07:27:16 +00:00
.. doctest ::
2021-12-27 09:07:18 +00:00
>>> from attrs import evolve
2021-11-22 06:35:36 +00:00
>>> @frozen
... class C:
... x: int
... y: int
2015-01-29 19:50:42 +00:00
>>> i1 = C(1, 2)
>>> i1
C(x=1, y=2)
2021-11-22 06:35:36 +00:00
>>> i2 = evolve(i1, y=3)
2015-01-29 19:50:42 +00:00
>>> i2
C(x=1, y=3)
>>> i1 == i2
False
2015-01-30 07:57:33 +00:00
2016-08-20 16:45:15 +00:00
2016-08-27 07:27:16 +00:00
Other Goodies
-------------
2016-08-20 16:45:15 +00:00
2015-01-30 07:57:33 +00:00
Sometimes you may want to create a class programmatically.
2021-12-25 14:15:10 +00:00
`` attrs `` won't let you down and gives you `attrs.make_class` :
2015-01-30 07:57:33 +00:00
.. doctest ::
2021-12-25 14:15:10 +00:00
>>> from attrs import fields, make_class
2021-11-22 06:35:36 +00:00
>>> @define
... class C1:
... x = field()
... y = field()
>>> C2 = make_class("C2", ["x", "y"])
>>> fields(C1) == fields(C2)
2015-01-30 07:57:33 +00:00
True
2021-11-22 06:35:36 +00:00
You can still have power over the attributes if you pass a dictionary of name: `` field `` mappings and can pass arguments to `` @attr.s `` :
2015-01-30 07:57:33 +00:00
.. doctest ::
2021-12-27 09:07:18 +00:00
>>> from attrs import make_class
2021-11-22 06:35:36 +00:00
>>> C = make_class("C", {"x": field(default=42),
... "y": field(default=Factory(list))},
... repr=False)
2015-01-30 07:57:33 +00:00
>>> i = C()
>>> i # no repr added!
2017-11-04 13:38:03 +00:00
<__main__.C object at ...>
2015-01-30 07:57:33 +00:00
>>> i.x
42
>>> i.y
[]
2015-01-30 16:48:34 +00:00
2021-12-25 14:15:10 +00:00
If you need to dynamically make a class with `attrs.make_class` and it needs to be a subclass of something else than `` object `` , use the `` bases `` argument:
2017-02-21 06:44:59 +00:00
.. doctest ::
2021-12-27 09:07:18 +00:00
>>> from attrs import make_class
2021-11-22 06:35:36 +00:00
>>> class D:
... def __eq__(self, other):
... return True # arbitrary example
>>> C = make_class("C", {}, bases=(D,), cmp=False)
>>> isinstance(C(), D)
True
2017-02-21 06:44:59 +00:00
2016-11-11 04:16:48 +00:00
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
2021-11-22 06:35:36 +00:00
using `` @define `` .
2016-11-20 13:39:34 +00:00
To do this, just define a `` __attrs_post_init__ `` method in your class.
2016-11-20 13:15:29 +00:00
It will get called at the end of the generated `` __init__ `` method.
2016-11-11 04:16:48 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... x: int
... y: int
... z: int = field(init=False)
2016-11-11 04:16:48 +00:00
...
2016-11-20 13:39:34 +00:00
... def __attrs_post_init__(self):
2016-11-11 04:16:48 +00:00
... self.z = self.x + self.y
>>> obj = C(x=1, y=2)
2016-11-20 13:15:29 +00:00
>>> obj
C(x=1, y=2, z=3)
2016-11-11 04:16:48 +00:00
2019-09-06 12:26:08 +00:00
You can exclude single attributes from certain methods:
2015-01-30 16:48:34 +00:00
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... user: str
... password: str = field(repr=False)
2015-01-30 16:48:34 +00:00
>>> C("me", "s3kr3t")
C(user='me')
2019-09-06 12:26:08 +00:00
Alternatively, to influence how the generated `` __repr__() `` method formats a specific attribute, specify a custom callable to be used instead of the `` repr() `` built-in function:
.. doctest ::
2021-11-22 06:35:36 +00:00
>>> @define
... class C:
... user: str
... password: str = field(repr=lambda value: '***')
2019-09-06 12:26:08 +00:00
>>> C("me", "s3kr3t")
C(user='me', password=*** )