attrs/docs/api.rst

875 lines
26 KiB
ReStructuredText

API Reference
=============
.. currentmodule:: attr
``attrs`` works by decorating a class using `attrs.define` or `attr.s` and then optionally defining attributes on the class using `attrs.field`, `attr.ib`, or a type annotation.
If you're confused by the many names, please check out `names` for clarification.
What follows is the API explanation, if you'd like a more hands-on introduction, have a look at `examples`.
As of version 21.3.0, ``attrs`` consists of **two** top-level package names:
- The classic ``attr`` that powered the venerable `attr.s` and `attr.ib`
- The modern ``attrs`` that only contains most modern APIs and relies on `attrs.define` and `attrs.field` to define your classes.
Additionally it offers some ``attr`` APIs with nicer defaults (e.g. `attrs.asdict`).
The ``attrs`` namespace is built *on top of* ``attr`` which will *never* go away.
Core
----
.. note::
Please note that the ``attrs`` namespace has been added in version 21.3.0.
Most of the objects are simply re-imported from ``attr``.
Therefore if a class, method, or function claims that it has been added in an older version, it is only available in the ``attr`` namespace.
.. autodata:: attrs.NOTHING
:no-value:
.. autofunction:: attrs.define
.. function:: attrs.mutable(same_as_define)
Alias for `attrs.define`.
.. versionadded:: 20.1.0
.. function:: attrs.frozen(same_as_define)
Behaves the same as `attrs.define` but sets *frozen=True* and *on_setattr=None*.
.. versionadded:: 20.1.0
.. autofunction:: attrs.field
.. function:: define
Old import path for `attrs.define`.
.. function:: mutable
Old import path for `attrs.mutable`.
.. function:: frozen
Old import path for `attrs.frozen`.
.. function:: field
Old import path for `attrs.field`.
.. autoclass:: attrs.Attribute
:members: evolve
For example:
.. doctest::
>>> import attr
>>> @attr.s
... class C:
... x = attr.ib()
>>> attr.fields(C).x
Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x')
.. autofunction:: attrs.make_class
This is handy if you want to programmatically create classes.
For example:
.. doctest::
>>> C1 = attr.make_class("C1", ["x", "y"])
>>> C1(1, 2)
C1(x=1, y=2)
>>> C2 = attr.make_class("C2", {"x": attr.ib(default=42),
... "y": attr.ib(default=attr.Factory(list))})
>>> C2()
C2(x=42, y=[])
.. autoclass:: attrs.Factory
For example:
.. doctest::
>>> @attr.s
... class C:
... x = attr.ib(default=attr.Factory(list))
... y = attr.ib(default=attr.Factory(
... lambda self: set(self.x),
... takes_self=True)
... )
>>> C()
C(x=[], y=set())
>>> C([1, 2, 3])
C(x=[1, 2, 3], y={1, 2, 3})
Classic
~~~~~~~
.. data:: attr.NOTHING
Same as `attrs.NOTHING`.
.. autofunction:: attr.s(these=None, repr_ns=None, repr=None, cmp=None, hash=None, init=None, slots=False, frozen=False, weakref_slot=True, str=False, auto_attribs=False, kw_only=False, cache_hash=False, auto_exc=False, eq=None, order=None, auto_detect=False, collect_by_mro=False, getstate_setstate=None, on_setattr=None, field_transformer=None, match_args=True)
.. note::
``attrs`` also comes with a serious-business alias ``attr.attrs``.
For example:
.. doctest::
>>> import attr
>>> @attr.s
... class C:
... _private = attr.ib()
>>> C(private=42)
C(_private=42)
>>> class D:
... def __init__(self, x):
... self.x = x
>>> D(1)
<D object at ...>
>>> D = attr.s(these={"x": attr.ib()}, init=False)(D)
>>> D(1)
D(x=1)
>>> @attr.s(auto_exc=True)
... class Error(Exception):
... x = attr.ib()
... y = attr.ib(default=42, init=False)
>>> Error("foo")
Error(x='foo', y=42)
>>> raise Error("foo")
Traceback (most recent call last):
...
Error: ('foo', 42)
>>> raise ValueError("foo", 42) # for comparison
Traceback (most recent call last):
...
ValueError: ('foo', 42)
.. autofunction:: attr.ib
.. note::
``attrs`` also comes with a serious-business alias ``attr.attrib``.
The object returned by `attr.ib` also allows for setting the default and the validator using decorators:
.. doctest::
>>> @attr.s
... class C:
... x = attr.ib()
... y = attr.ib()
... @x.validator
... def _any_name_except_a_name_of_an_attribute(self, attribute, value):
... if value < 0:
... raise ValueError("x must be positive")
... @y.default
... def _any_name_except_a_name_of_an_attribute(self):
... return self.x + 1
>>> C(1)
C(x=1, y=2)
>>> C(-1)
Traceback (most recent call last):
...
ValueError: x must be positive
Exceptions
----------
All exceptions are available from both ``attr.exceptions`` and ``attrs.exceptions`` and are the same thing.
That means that it doesn't matter from from which namespace they've been raised and/or caught:
.. doctest::
>>> import attrs, attr
>>> try:
... raise attrs.exceptions.FrozenError()
... except attr.exceptions.FrozenError:
... print("this works!")
this works!
.. autoexception:: attrs.exceptions.PythonTooOldError
.. autoexception:: attrs.exceptions.FrozenError
.. autoexception:: attrs.exceptions.FrozenInstanceError
.. autoexception:: attrs.exceptions.FrozenAttributeError
.. autoexception:: attrs.exceptions.AttrsAttributeNotFoundError
.. autoexception:: attrs.exceptions.NotAnAttrsClassError
.. autoexception:: attrs.exceptions.DefaultAlreadySetError
.. autoexception:: attrs.exceptions.UnannotatedAttributeError
.. autoexception:: attrs.exceptions.NotCallableError
For example::
@attr.s(auto_attribs=True)
class C:
x: int
y = attr.ib() # <- ERROR!
.. _helpers:
Helpers
-------
``attrs`` comes with a bunch of helper methods that make working with it easier:
.. autofunction:: attrs.cmp_using
.. function:: attr.cmp_using
Same as `attrs.cmp_using`.
.. autofunction:: attrs.fields
For example:
.. doctest::
>>> @attr.s
... class C:
... x = attr.ib()
... y = attr.ib()
>>> attrs.fields(C)
(Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x'), Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y'))
>>> attrs.fields(C)[1]
Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y')
>>> attrs.fields(C).y is attrs.fields(C)[1]
True
.. function:: attr.fields
Same as `attrs.fields`.
.. autofunction:: attrs.fields_dict
For example:
.. doctest::
>>> @attr.s
... class C:
... x = attr.ib()
... y = attr.ib()
>>> attrs.fields_dict(C)
{'x': Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='x'), 'y': Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y')}
>>> attr.fields_dict(C)['y']
Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='y')
>>> attrs.fields_dict(C)['y'] is attrs.fields(C).y
True
.. function:: attr.fields_dict
Same as `attrs.fields_dict`.
.. autofunction:: attrs.has
For example:
.. doctest::
>>> @attr.s
... class C:
... pass
>>> attr.has(C)
True
>>> attr.has(object)
False
.. function:: attr.has
Same as `attrs.has`.
.. autofunction:: attrs.resolve_types
For example:
.. doctest::
>>> import typing
>>> @attrs.define
... class A:
... a: typing.List['A']
... b: 'B'
...
>>> @attrs.define
... class B:
... a: A
...
>>> attrs.fields(A).a.type
typing.List[ForwardRef('A')]
>>> attrs.fields(A).b.type
'B'
>>> attrs.resolve_types(A, globals(), locals())
<class 'A'>
>>> attrs.fields(A).a.type
typing.List[A]
>>> attrs.fields(A).b.type
<class 'B'>
.. function:: attr.resolve_types
Same as `attrs.resolve_types`.
.. autofunction:: attrs.asdict
For example:
.. doctest::
>>> @attrs.define
... class C:
... x: int
... y: int
>>> attrs.asdict(C(1, C(2, 3)))
{'x': 1, 'y': {'x': 2, 'y': 3}}
.. autofunction:: attr.asdict
.. autofunction:: attrs.astuple
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attr.field()
... y = attr.field()
>>> attrs.astuple(C(1,2))
(1, 2)
.. autofunction:: attr.astuple
``attrs`` includes some handy helpers for filtering the attributes in `attrs.asdict` and `attrs.astuple`:
.. autofunction:: attrs.filters.include
.. autofunction:: attrs.filters.exclude
.. function:: attr.filters.include
Same as `attrs.filters.include`.
.. function:: attr.filters.exclude
Same as `attrs.filters.exclude`.
See :func:`attrs.asdict` for examples.
All objects from ``attrs.filters`` are also available from ``attr.filters``.
----
.. autofunction:: attrs.evolve
For example:
.. doctest::
>>> @attrs.define
... class C:
... x: int
... y: int
>>> i1 = C(1, 2)
>>> i1
C(x=1, y=2)
>>> i2 = attrs.evolve(i1, y=3)
>>> i2
C(x=1, y=3)
>>> i1 == i2
False
``evolve`` creates a new instance using ``__init__``.
This fact has several implications:
* private attributes should be specified without the leading underscore, just like in ``__init__``.
* attributes with ``init=False`` can't be set with ``evolve``.
* the usual ``__init__`` validators will validate the new values.
.. function:: attr.evolve
Same as `attrs.evolve`.
.. autofunction:: attrs.validate
For example:
.. doctest::
>>> @attrs.define(on_setattr=attrs.setters.NO_OP)
... class C:
... x = attrs.field(validator=attrs.validators.instance_of(int))
>>> i = C(1)
>>> i.x = "1"
>>> attrs.validate(i)
Traceback (most recent call last):
...
TypeError: ("'x' must be <class 'int'> (got '1' that is a <class 'str'>).", ...)
.. function:: attr.validate
Same as `attrs.validate`.
Validators can be globally disabled if you want to run them only in development and tests but not in production because you fear their performance impact:
.. autofunction:: set_run_validators
.. autofunction:: get_run_validators
.. _api_validators:
Validators
----------
``attrs`` comes with some common validators in the ``attrs.validators`` module.
All objects from ``attrs.validators`` are also available from ``attr.validators``.
.. autofunction:: attrs.validators.lt
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attrs.field(validator=attrs.validators.lt(42))
>>> C(41)
C(x=41)
>>> C(42)
Traceback (most recent call last):
...
ValueError: ("'x' must be < 42: 42")
.. autofunction:: attrs.validators.le
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attrs.field(validator=attr.validators.le(42))
>>> C(42)
C(x=42)
>>> C(43)
Traceback (most recent call last):
...
ValueError: ("'x' must be <= 42: 43")
.. autofunction:: attrs.validators.ge
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attrs.field(validator=attrs.validators.ge(42))
>>> C(42)
C(x=42)
>>> C(41)
Traceback (most recent call last):
...
ValueError: ("'x' must be => 42: 41")
.. autofunction:: attrs.validators.gt
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attr.field(validator=attrs.validators.gt(42))
>>> C(43)
C(x=43)
>>> C(42)
Traceback (most recent call last):
...
ValueError: ("'x' must be > 42: 42")
.. autofunction:: attrs.validators.max_len
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attrs.field(validator=attrs.validators.max_len(4))
>>> C("spam")
C(x='spam')
>>> C("bacon")
Traceback (most recent call last):
...
ValueError: ("Length of 'x' must be <= 4: 5")
.. autofunction:: attrs.validators.min_len
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attrs.field(validator=attrs.validators.min_len(1))
>>> C("bacon")
C(x='bacon')
>>> C("")
Traceback (most recent call last):
...
ValueError: ("Length of 'x' must be => 1: 0")
.. autofunction:: attrs.validators.instance_of
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attrs.field(validator=attrs.validators.instance_of(int))
>>> C(42)
C(x=42)
>>> C("42")
Traceback (most recent call last):
...
TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None, kw_only=False), <type 'int'>, '42')
>>> C(None)
Traceback (most recent call last):
...
TypeError: ("'x' must be <type 'int'> (got None that is a <type 'NoneType'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, repr=True, cmp=True, hash=None, init=True, type=None, kw_only=False), <type 'int'>, None)
.. autofunction:: attrs.validators.in_
For example:
.. doctest::
>>> import enum
>>> class State(enum.Enum):
... ON = "on"
... OFF = "off"
>>> @attrs.define
... class C:
... state = attrs.field(validator=attrs.validators.in_(State))
... val = attrs.field(validator=attrs.validators.in_([1, 2, 3]))
>>> C(State.ON, 1)
C(state=<State.ON: 'on'>, val=1)
>>> C("on", 1)
Traceback (most recent call last):
...
ValueError: 'state' must be in <enum 'State'> (got 'on'), Attribute(name='state', default=NOTHING, validator=<in_ validator with options <enum 'State'>>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), <enum 'State'>, 'on')
>>> C(State.ON, 4)
Traceback (most recent call last):
...
ValueError: 'val' must be in [1, 2, 3] (got 4), Attribute(name='val', default=NOTHING, validator=<in_ validator with options [1, 2, 3]>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), [1, 2, 3], 4)
.. autofunction:: attrs.validators.provides
.. autofunction:: attrs.validators.and_
For convenience, it's also possible to pass a list to `attrs.field`'s validator argument.
Thus the following two statements are equivalent::
x = attrs.field(validator=attrs.validators.and_(v1, v2, v3))
x = attrs.field(validator=[v1, v2, v3])
.. autofunction:: attrs.validators.not_
For example:
.. doctest::
>>> reserved_names = {"id", "time", "source"}
>>> @attrs.define
... class Measurement:
... tags = attrs.field(
... validator=attrs.validators.deep_mapping(
... key_validator=attrs.validators.not_(
... attrs.validators.in_(reserved_names),
... msg="reserved tag key",
... ),
... value_validator=attrs.validators.instance_of((str, int)),
... )
... )
>>> Measurement(tags={"source": "universe"})
Traceback (most recent call last):
...
ValueError: ("reserved tag key", Attribute(name='tags', default=NOTHING, validator=<not_ validator wrapping <in_ validator with options {'id', 'time', 'source'}>, capturing (<class 'ValueError'>, <class 'TypeError'>)>, type=None, kw_only=False), <in_ validator with options {'id', 'time', 'source'}>, {'source_': 'universe'}, (<class 'ValueError'>, <class 'TypeError'>))
>>> Measurement(tags={"source_": "universe"})
Measurement(tags={'source_': 'universe'})
.. autofunction:: attrs.validators.optional
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attrs.field(validator=attrs.validators.optional(attr.validators.instance_of(int)))
>>> C(42)
C(x=42)
>>> C("42")
Traceback (most recent call last):
...
TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None, kw_only=False), <type 'int'>, '42')
>>> C(None)
C(x=None)
.. autofunction:: attrs.validators.is_callable
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attrs.field(validator=attrs.validators.is_callable())
>>> C(isinstance)
C(x=<built-in function isinstance>)
>>> C("not a callable")
Traceback (most recent call last):
...
attr.exceptions.NotCallableError: 'x' must be callable (got 'not a callable' that is a <class 'str'>).
.. autofunction:: attrs.validators.matches_re
For example:
.. doctest::
>>> @attrs.define
... class User:
... email = attrs.field(validator=attrs.validators.matches_re(
... "(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"))
>>> User(email="user@example.com")
User(email='user@example.com')
>>> User(email="user@example.com@test.com")
Traceback (most recent call last):
...
ValueError: ("'email' must match regex '(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\\\.[a-zA-Z0-9-.]+$)' ('user@example.com@test.com' doesn't)", Attribute(name='email', default=NOTHING, validator=<matches_re validator for pattern re.compile('(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)')>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), re.compile('(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)'), 'user@example.com@test.com')
.. autofunction:: attrs.validators.deep_iterable
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attrs.field(validator=attrs.validators.deep_iterable(
... member_validator=attrs.validators.instance_of(int),
... iterable_validator=attrs.validators.instance_of(list)
... ))
>>> C(x=[1, 2, 3])
C(x=[1, 2, 3])
>>> C(x=set([1, 2, 3]))
Traceback (most recent call last):
...
TypeError: ("'x' must be <class 'list'> (got {1, 2, 3} that is a <class 'set'>).", Attribute(name='x', default=NOTHING, validator=<deep_iterable validator for <instance_of validator for type <class 'list'>> iterables of <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'list'>, {1, 2, 3})
>>> C(x=[1, 2, "3"])
Traceback (most recent call last):
...
TypeError: ("'x' must be <class 'int'> (got '3' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=<deep_iterable validator for <instance_of validator for type <class 'list'>> iterables of <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'int'>, '3')
.. autofunction:: attrs.validators.deep_mapping
For example:
.. doctest::
>>> @attrs.define
... class C:
... x = attrs.field(validator=attrs.validators.deep_mapping(
... key_validator=attrs.validators.instance_of(str),
... value_validator=attrs.validators.instance_of(int),
... mapping_validator=attrs.validators.instance_of(dict)
... ))
>>> C(x={"a": 1, "b": 2})
C(x={'a': 1, 'b': 2})
>>> C(x=None)
Traceback (most recent call last):
...
TypeError: ("'x' must be <class 'dict'> (got None that is a <class 'NoneType'>).", Attribute(name='x', default=NOTHING, validator=<deep_mapping validator for objects mapping <instance_of validator for type <class 'str'>> to <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'dict'>, None)
>>> C(x={"a": 1.0, "b": 2})
Traceback (most recent call last):
...
TypeError: ("'x' must be <class 'int'> (got 1.0 that is a <class 'float'>).", Attribute(name='x', default=NOTHING, validator=<deep_mapping validator for objects mapping <instance_of validator for type <class 'str'>> to <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'int'>, 1.0)
>>> C(x={"a": 1, 7: 2})
Traceback (most recent call last):
...
TypeError: ("'x' must be <class 'str'> (got 7 that is a <class 'int'>).", Attribute(name='x', default=NOTHING, validator=<deep_mapping validator for objects mapping <instance_of validator for type <class 'str'>> to <instance_of validator for type <class 'int'>>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'str'>, 7)
Validators can be both globally and locally disabled:
.. autofunction:: attrs.validators.set_disabled
.. autofunction:: attrs.validators.get_disabled
.. autofunction:: attrs.validators.disabled
Converters
----------
All objects from ``attrs.converters`` are also available from ``attr.converters``.
.. autofunction:: attrs.converters.pipe
For convenience, it's also possible to pass a list to `attr.ib`'s converter argument.
Thus the following two statements are equivalent::
x = attr.ib(converter=attr.converter.pipe(c1, c2, c3))
x = attr.ib(converter=[c1, c2, c3])
.. autofunction:: attrs.converters.optional
For example:
.. doctest::
>>> @attr.s
... class C:
... x = attr.ib(converter=attr.converters.optional(int))
>>> C(None)
C(x=None)
>>> C(42)
C(x=42)
.. autofunction:: attrs.converters.default_if_none
For example:
.. doctest::
>>> @attr.s
... class C:
... x = attr.ib(
... converter=attr.converters.default_if_none("")
... )
>>> C(None)
C(x='')
.. autofunction:: attrs.converters.to_bool
For example:
.. doctest::
>>> @attr.s
... class C:
... x = attr.ib(
... converter=attr.converters.to_bool
... )
>>> C("yes")
C(x=True)
>>> C(0)
C(x=False)
>>> C("foo")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Cannot convert value to bool: foo
.. _api_setters:
Setters
-------
These are helpers that you can use together with `attrs.define`'s and `attrs.fields`'s ``on_setattr`` arguments.
All setters in ``attrs.setters`` are also available from ``attr.setters``.
.. autofunction:: attrs.setters.frozen
.. autofunction:: attrs.setters.validate
.. autofunction:: attrs.setters.convert
.. autofunction:: attrs.setters.pipe
.. data:: attrs.setters.NO_OP
Sentinel for disabling class-wide *on_setattr* hooks for certain attributes.
Does not work in `attrs.setters.pipe` or within lists.
.. versionadded:: 20.1.0
For example, only ``x`` is frozen here:
.. doctest::
>>> @attrs.define(on_setattr=attr.setters.frozen)
... class C:
... x = attr.field()
... y = attr.field(on_setattr=attr.setters.NO_OP)
>>> c = C(1, 2)
>>> c.y = 3
>>> c.y
3
>>> c.x = 4
Traceback (most recent call last):
...
attrs.exceptions.FrozenAttributeError: ()
N.B. Please use `attrs.define`'s *frozen* argument (or `attrs.frozen`) to freeze whole classes; it is more efficient.
Deprecated APIs
---------------
.. _version-info:
To help you write backward compatible code that doesn't throw warnings on modern releases, the ``attr`` module has an ``__version_info__`` attribute as of version 19.2.0.
It behaves similarly to `sys.version_info` and is an instance of `VersionInfo`:
.. autoclass:: VersionInfo
With its help you can write code like this:
>>> if getattr(attr, "__version_info__", (0,)) >= (19, 2):
... cmp_off = {"eq": False}
... else:
... cmp_off = {"cmp": False}
>>> cmp_off == {"eq": False}
True
>>> @attr.s(**cmp_off)
... class C:
... pass
----
The serious-business aliases used to be called ``attr.attributes`` and ``attr.attr``.
There are no plans to remove them but they shouldn't be used in new code.
.. autofunction:: assoc