API Reference
=============

.. module:: attrs

*attrs* works by decorating a class using `attrs.define` or `attr.s` and then defining attributes on the class using `attrs.field`, `attr.ib`, or type annotations.

What follows is the dry API explanation for people who understand how *attrs* works.
If you'd like a hands-on tutorial, have a look at `examples`.

If you're confused by the many names, please check out `names` for clarification, but the `TL;DR <https://en.wikipedia.org/wiki/TL;DR>`_ is that as of version 21.3.0, *attrs* consists of **two** top-level package names:

- The classic ``attr`` that powers the venerable `attr.s` and `attr.ib`.
- The newer ``attrs`` that only contains most modern APIs and relies on `attrs.define` and `attrs.field` to define your classes.
  Additionally, some of the APIs that also exist in ``attr`` have nicer defaults (for example, `attrs.asdict`).

The ``attrs`` namespace is built *on top of* ``attr`` -- which will *never* go away -- and is just as stable, since it doesn't constitute a rewrite.
To keep repetition low and this document at a reasonable size, the ``attr`` namespace is `documented on a separate page <api-attr>`.


Core
----

.. autofunction:: attrs.define

.. function:: mutable(same_as_define)

   Same as `attrs.define`.

   .. versionadded:: 20.1.0

.. function:: frozen(same_as_define)

   Behaves the same as `attrs.define` but sets *frozen=True* and *on_setattr=None*.

   .. versionadded:: 20.1.0

.. autofunction:: field

.. autoclass:: Attribute
   :members: evolve

   For example:

   .. doctest::

      >>> import attrs
      >>> from attrs import define, field

      >>> @define
      ... class C:
      ...     x = field()
      >>> attrs.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:: make_class

   This is handy if you want to programmatically create classes.

   For example:

   .. doctest::

      >>> C1 = attrs.make_class("C1", ["x", "y"])
      >>> C1(1, 2)
      C1(x=1, y=2)
      >>> C2 = attrs.make_class("C2", {
      ...     "x": field(default=42),
      ...     "y": field(factory=list)
      ... })
      >>> C2()
      C2(x=42, y=[])


.. autoclass:: Factory

   For example:

   .. doctest::

      >>> @define
      ... class C:
      ...     x = field(default=attrs.Factory(list))
      ...     y = field(default=attrs.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})


.. autodata:: attrs.NOTHING
   :no-value:


Exceptions
----------

.. module:: attrs.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:: PythonTooOldError
.. autoexception:: FrozenError
.. autoexception:: FrozenInstanceError
.. autoexception:: FrozenAttributeError
.. autoexception:: AttrsAttributeNotFoundError
.. autoexception:: NotAnAttrsClassError
.. autoexception:: DefaultAlreadySetError
.. autoexception:: NotCallableError
.. autoexception:: UnannotatedAttributeError

   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:

.. currentmodule:: attrs

.. autofunction:: attrs.cmp_using

.. autofunction:: attrs.fields

   For example:

   .. doctest::

      >>> @define
      ... class C:
      ...     x = field()
      ...     y = field()
      >>> 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, 2)) is attrs.fields(C)
      True
      >>> 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

.. 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

.. autofunction:: attrs.has

   For example:

   .. doctest::

      >>> @define
      ... class C:
      ...     pass
      >>> attrs.has(C)
      True
      >>> attrs.has(object)
      False

.. autofunction:: attrs.inspect

   For example:

   .. doctest::

      >>> @define
      ... class CInspect:
      ...     pass
      >>> attrs.inspect(CInspect)  # doctest: +ELLIPSIS
      ClassProps(is_exception=False, is_slotted=True, has_weakref_slot=True, is_frozen=False, kw_only=<KeywordOnly.NO: 'no'>, collected_fields_by_mro=True, added_init=True, added_repr=True, added_eq=True, added_ordering=False, hashability=<Hashability.UNHASHABLE: 'unhashable'>, added_match_args=True, added_str=False, added_pickling=True, on_setattr_hook=<function pipe.<locals>.wrapped_pipe at ...>, field_transformer=None)

.. autoclass:: attrs.ClassProps
.. autoclass:: attrs.ClassProps.Hashability
   :members: HASHABLE, HASHABLE_CACHED, UNHASHABLE, LEAVE_ALONE
.. autoclass:: attrs.ClassProps.KeywordOnly
   :members: NO, YES, FORCE

.. autofunction:: attrs.resolve_types

    For example:

    .. doctest::

        >>> import typing
        >>> @define
        ... class A:
        ...     a: typing.List['A']
        ...     b: 'B'
        ...
        >>> @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'>

.. autofunction:: attrs.asdict

   For example:

   .. doctest::

      >>> @define
      ... class C:
      ...     x: int
      ...     y: int
      >>> attrs.asdict(C(1, C(2, 3)))
      {'x': 1, 'y': {'x': 2, 'y': 3}}

.. autofunction:: attrs.astuple

   For example:

   .. doctest::

      >>> @define
      ... class C:
      ...     x = field()
      ...     y = field()
      >>> attrs.astuple(C(1,2))
      (1, 2)

.. module:: attrs.filters

*attrs* includes helpers for filtering the attributes in `attrs.asdict` and `attrs.astuple`:

.. autofunction:: include

.. autofunction:: exclude

See :func:`attrs.asdict` for examples.

All objects from ``attrs.filters`` are also available from ``attr.filters`` (it's the same module in a different namespace).

----

.. currentmodule:: attrs

.. autofunction:: attrs.evolve

   For example:

   .. doctest::

      >>> @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.

.. autofunction:: attrs.validate

   For example:

   .. doctest::

      >>> @define(on_setattr=attrs.setters.NO_OP)
      ... class C:
      ...     x = 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'>).", ...)


.. _api-validators:

Validators
----------

.. module:: attrs.validators

*attrs* comes with some common validators in the ``attrs.validators`` module.
All objects from ``attrs.validators`` are also available from ``attr.validators`` (it's the same module in a different namespace).

.. autofunction:: attrs.validators.lt

   For example:

   .. doctest::

      >>> @define
      ... class C:
      ...     x = 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::

      >>> @define
      ... class C:
      ...     x = field(validator=attrs.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::

      >>> @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::

      >>> @define
      ... class C:
      ...     x = 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::

      >>> @define
      ... class C:
      ...     x = 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::

      >>> @define
      ... class C:
      ...     x = 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::

      >>> @define
      ... class C:
      ...     x = 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"
      >>> @define
      ... class C:
      ...     state = field(validator=attrs.validators.in_(State))
      ...     val = 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.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 = field(validator=attrs.validators.and_(v1, v2, v3))
      x = field(validator=[v1, v2, v3])

.. autofunction:: attrs.validators.or_

   For example:

   .. doctest::

      >>> @define
      ... class C:
      ...     val: int | list[int] = field(
      ...         validator=attrs.validators.or_(
      ...             attrs.validators.instance_of(int),
      ...             attrs.validators.deep_iterable(attrs.validators.instance_of(int)),
      ...         )
      ...     )
      >>> C(42)
      C(val=42)
      >>> C([1, 2, 3])
      C(val=[1, 2, 3])
      >>> C(val='42')
      Traceback (most recent call last):
         ...
      ValueError: None of (<instance_of validator for type <class 'int'>>, <deep_iterable validator for iterables of <instance_of validator for type <class 'int'>>>) satisfied for value '42'

.. autofunction:: attrs.validators.not_

   For example:

   .. doctest::

      >>> reserved_names = {"id", "time", "source"}
      >>> @define
      ... class Measurement:
      ...     tags = 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::

      >>> @define
      ... class C:
      ...     x = field(
      ...         validator=attrs.validators.optional(
      ...             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)
      C(x=None)


.. autofunction:: attrs.validators.is_callable

    For example:

    .. doctest::

        >>> @define
        ... class C:
        ...     x = 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::

        >>> @define
        ... class User:
        ...     email = field(validator=attrs.validators.matches_re(
        ...         r"(^[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::

        >>> @define
        ... class C:
        ...     x = 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::

        >>> @define
        ... class C:
        ...     x = 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
----------

.. autoclass:: attrs.Converter

   For example:

   .. doctest::

      >>> def complicated(value, self_, field):
      ...     return int(value) * self_.factor + field.metadata["offset"]
      >>> @define
      ... class C:
      ...     factor = 5  # not an *attrs* field
      ...     x = field(
      ...         metadata={"offset": 200},
      ...         converter=attrs.Converter(
      ...             complicated,
      ...             takes_self=True, takes_field=True
      ...     ))
      >>> C("42")
      C(x=410)


.. module:: attrs.converters

All objects from ``attrs.converters`` are also available from ``attr.converters`` (it's the same module in a different namespace).

.. autofunction:: attrs.converters.pipe

   For convenience, it's also possible to pass a list to `attrs.field` / `attr.ib`'s converter arguments.

   Thus the following two statements are equivalent::

      x = attrs.field(converter=attrs.converter.pipe(c1, c2, c3))
      x = attrs.field(converter=[c1, c2, c3])

.. autofunction:: attrs.converters.optional

   For example:

   .. doctest::

      >>> @define
      ... class C:
      ...     x = field(converter=attrs.converters.optional(int))
      >>> C(None)
      C(x=None)
      >>> C(42)
      C(x=42)


.. autofunction:: attrs.converters.default_if_none

   For example:

   .. doctest::

      >>> @define
      ... class C:
      ...     x = field(
      ...         converter=attrs.converters.default_if_none("")
      ...     )
      >>> C(None)
      C(x='')


.. autofunction:: attrs.converters.to_bool(val)

   For example:

   .. doctest::

      >>> @define
      ... class C:
      ...     x = field(
      ...         converter=attrs.converters.to_bool
      ...     )
      >>> C("yes")
      C(x=True)
      >>> C(0)
      C(x=False)
      >>> C("norway")
      Traceback (most recent call last):
         File "<stdin>", line 1, in <module>
      ValueError: Cannot convert value to bool: norway




.. _api_setters:

Setters
-------

.. module:: attrs.setters

These are helpers that you can use together with `attrs.define`'s and `attrs.field`'s ``on_setattr`` arguments.
All setters in ``attrs.setters`` are also available from ``attr.setters`` (it's the same module in a different namespace).

.. autofunction:: frozen
.. autofunction:: validate
.. autofunction:: convert
.. autofunction:: pipe

.. data:: 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::

     >>> @define(on_setattr=attr.setters.frozen)
     ... class C:
     ...     x = field()
     ...     y = 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: ()

   .. tip::
      Use `attrs.define`'s *frozen* argument (or `attrs.frozen`) to freeze whole classes; it is more efficient.
