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 someattr
APIs with nicer defaults (e.g. attrs.asdict).
The attrs
namespace is built on top of attr
which will never go away.
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.
attrs.NOTHING
attrs.define
attrs.mutable(same_as_define)
Alias for attrs.define.
20.1.0
attrs.frozen(same_as_define)
Behaves the same as attrs.define but sets frozen=True and on_setattr=None.
20.1.0
attrs.field
define
Old import path for attrs.define.
mutable
Old import path for attrs.mutable.
frozen
Old import path for attrs.frozen.
field
Old import path for attrs.field.
attrs.Attribute
For example:
>>> 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')
attrs.make_class
This is handy if you want to programmatically create classes.
For example:
>>> 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=[])
attrs.Factory
For example:
>>> @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})
attr.NOTHING
Same as attrs.NOTHING.
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:
>>> 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)
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:
>>> @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
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:
>>> import attrs, attr >>> try: ... raise attrs.exceptions.FrozenError() ... except attr.exceptions.FrozenError: ... print("this works!") this works!
attrs.exceptions.PythonTooOldError
attrs.exceptions.FrozenError
attrs.exceptions.FrozenInstanceError
attrs.exceptions.FrozenAttributeError
attrs.exceptions.AttrsAttributeNotFoundError
attrs.exceptions.NotAnAttrsClassError
attrs.exceptions.DefaultAlreadySetError
attrs.exceptions.UnannotatedAttributeError
attrs.exceptions.NotCallableError
For example:
@attr.s(auto_attribs=True)
class C:
x: int
y = attr.ib() # <- ERROR!
attrs
comes with a bunch of helper methods that make working with it easier:
attrs.cmp_using
attr.cmp_using
Same as attrs.cmp_using.
attrs.fields
For example:
>>> @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
attr.fields
Same as attrs.fields.
attrs.fields_dict
For example:
>>> @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
attr.fields_dict
Same as attrs.fields_dict.
attrs.has
For example:
>>> @attr.s ... class C: ... pass >>> attr.has(C) True >>> attr.has(object) False
attr.has
Same as attrs.has.
attrs.resolve_types
For example:
>>> 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'>
attr.resolve_types
Same as attrs.resolve_types.
attrs.asdict
For example:
>>> @attrs.define ... class C: ... x: int ... y: int >>> attrs.asdict(C(1, C(2, 3))) {'x': 1, 'y': {'x': 2, 'y': 3}}
attr.asdict
attrs.astuple
For example:
>>> @attrs.define ... class C: ... x = attr.field() ... y = attr.field() >>> attrs.astuple(C(1,2)) (1, 2)
attr.astuple
attrs
includes some handy helpers for filtering the attributes in attrs.asdict and `attrs.astuple`:
attrs.filters.include
attrs.filters.exclude
attr.filters.include
Same as attrs.filters.include.
attr.filters.exclude
Same as attrs.filters.exclude.
See attrs.asdict
for examples.
All objects from attrs.filters
are also available from attr.filters
.
attrs.evolve
For example:
>>> @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 withevolve
. - the usual
__init__
validators will validate the new values.
attr.evolve
Same as attrs.evolve.
attrs.validate
For example:
>>> @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'>).", ...)
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:
set_run_validators
get_run_validators
attrs
comes with some common validators in the attrs.validators
module. All objects from attrs.validators
are also available from attr.validators
.
attrs.validators.lt
For example:
>>> @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")
attrs.validators.le
For example:
>>> @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")
attrs.validators.ge
For example:
>>> @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")
attrs.validators.gt
For example:
>>> @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")
attrs.validators.max_len
For example:
>>> @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")
attrs.validators.min_len
For example:
>>> @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")
attrs.validators.instance_of
For example:
>>> @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)
For example:
>>> 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)
attrs.validators.provides
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])
For example:
>>> 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'})
attrs.validators.optional
For example:
>>> @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)
attrs.validators.is_callable
For example:
>>> @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'>).
attrs.validators.matches_re
For example:
>>> @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')
attrs.validators.deep_iterable
For example:
>>> @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')
attrs.validators.deep_mapping
For example:
>>> @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:
attrs.validators.set_disabled
attrs.validators.get_disabled
attrs.validators.disabled
All objects from attrs.converters
are also available from attr.converters
.
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])
attrs.converters.optional
For example:
>>> @attr.s ... class C: ... x = attr.ib(converter=attr.converters.optional(int)) >>> C(None) C(x=None) >>> C(42) C(x=42)
attrs.converters.default_if_none
For example:
>>> @attr.s ... class C: ... x = attr.ib( ... converter=attr.converters.default_if_none("") ... ) >>> C(None) C(x='')
attrs.converters.to_bool
For example:
>>> @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
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
.
attrs.setters.frozen
attrs.setters.validate
attrs.setters.convert
attrs.setters.pipe
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.
20.1.0
For example, only x
is frozen here:
>>> @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.
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`:
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.
assoc