2015-01-27 16:53:17 +00:00
.. _examples:
Examples
========
2015-01-28 15:28:47 +00:00
2015-02-05 10:04:25 +00:00
Basics
------
2015-01-28 15:28:47 +00:00
The simplest possible usage would be:
.. doctest ::
>>> import attr
>>> @attr.s
... class Empty(object):
... pass
>>> Empty()
Empty()
>>> Empty() == Empty()
True
>>> Empty() is Empty()
False
So in other words: `` attrs `` useful even without actual attributes!
But you'll usually want some data on your classes, so let's add some:
.. doctest ::
>>> @attr.s
... class Coordinates(object):
... x = attr.ib()
... y = attr.ib()
These by default, all features are added, so you have immediately a fully functional data class with a nice `` repr `` string and comparison methods.
.. 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
As shown, the generated `` __init__ `` method allows both for positional and keyword arguments.
2015-01-28 16:36:58 +00:00
2015-01-29 09:17:08 +00:00
If playful naming turns you off, `` attrs `` comes with no-nonsense aliases:
.. doctest ::
>>> @attr.attributes
... class SeriousCoordinates(object):
... x = attr.attr()
... y = attr.attr()
>>> SeriousCoordinates(1, 2)
SeriousCoordinates(x=1, y=2)
2015-01-29 20:55:25 +00:00
>>> attr.fields(Coordinates) == attr.fields(SeriousCoordinates)
2015-01-29 09:17:08 +00:00
True
2015-02-08 11:32:32 +00:00
For private attributes, `` attrs `` will strip the leading underscores for keyword arguments:
.. doctest ::
>>> @attr.s
... class C(object):
... _x = attr.ib()
>>> C(x=1)
C(_x=1)
An additional way (not unlike `` characteristic `` ) of defining attributes is supported too.
This is useful in times when you want to enhance classes that are not yours (nice `` __repr__ `` for Django models anyone?):
.. doctest ::
>>> class SomethingFromSomeoneElse(object):
... def __init__(self, x):
... self.x = x
2015-02-20 12:29:47 +00:00
>>> SomethingFromSomeoneElse = attr.s(these={"x": attr.ib()}, init=False)(SomethingFromSomeoneElse)
2015-02-08 11:32:32 +00:00
>>> SomethingFromSomeoneElse(1)
SomethingFromSomeoneElse(x=1)
Or if you want to use properties:
.. doctest ::
>>> @attr.s(these={"_x": attr.ib()})
... class ReadOnlyXSquared(object):
... @property
... def x(self):
... return self._x ** 2
>>> rox = ReadOnlyXSquared(x=5)
>>> rox
ReadOnlyXSquared(_x=5)
>>> rox.x
25
>>> rox.x = 6
Traceback (most recent call last):
...
AttributeError: can't set attribute
2015-02-20 08:44:50 +00:00
`Sub-classing <https://www.youtube.com/watch?v=3MNVP9-hglc> `_ is bad for you, but `` attrs `` will still do what you'd hope for:
2015-02-18 20:31:32 +00:00
.. doctest ::
>>> @attr.s
... class A(object):
... a = attr.ib()
... def get_a(self):
... return self.a
>>> @attr.s
... class B(object):
... b = attr.ib()
>>> @attr.s
... class C(B, A):
... c = attr.ib()
>>> i = C(1, 2, 3)
>>> i
C(a=1, b=2, c=3)
>>> i == C(1, 2, 3)
True
>>> i.get_a()
1
The order of the attributes is defined by the `MRO <https://www.python.org/download/releases/2.3/mro/> `_ .
2015-02-20 08:44:50 +00:00
In Python 3, classes defined within other classes are `detected <https://www.python.org/dev/peps/pep-3155/> `_ and reflected in the `` __repr__ `` .
In Python 2 though, it's impossible.
Therefore `` @attr.s `` comes with the `` repr_ns `` option to set it manually:
.. doctest ::
>>> @attr.s
... class C(object):
... @attr.s(repr_ns="C")
... class D(object):
... pass
>>> C.D()
C.D()
`` repr_ns `` works on both Python 2 and 3.
On Python 3 is overrides the implicit detection.
2015-01-29 09:17:08 +00:00
2015-02-05 10:04:25 +00:00
Converting to Dictionaries
--------------------------
When you have a class with data, it often is very convenient to transform that class into a :class: `dict` (for example if you want to serialize it to JSON):
.. doctest ::
>>> attr.asdict(Coordinates(x=1, y=2))
{'y': 2, 'x': 1}
Some fields cannot or should not be transformed.
2015-02-06 13:56:02 +00:00
For that, :func: `attr.asdict` offers a callback that decides whether an attribute should be included:
2015-02-05 10:04:25 +00:00
.. doctest ::
>>> @attr.s
... class UserList(object):
... users = attr.ib()
>>> @attr.s
... class User(object):
... email = attr.ib()
... password = attr.ib()
>>> attr.asdict(UserList([User("jane@doe.invalid", "s33kred"),
... User("joe@doe.invalid", "p4ssw0rd")]),
2015-02-06 13:56:02 +00:00
... filter=lambda attr, value: attr.name != "password")
2015-02-05 10:04:25 +00:00
{'users': [{'email': 'jane@doe.invalid'}, {'email': 'joe@doe.invalid'}]}
2015-02-20 15:34:21 +00:00
For the common case where you want to :func: `include <attr.filters.include>` or :func: `exclude <attr.filters.exclude>` certain types or attributes, `` attrs `` ships with a few helpers:
.. doctest ::
>>> @attr.s
... class User(object):
... login = attr.ib()
... password = attr.ib()
... id = attr.ib()
>>> attr.asdict(User("jane", "s33kred", 42), filter=attr.filters.exclude(User.password, int))
{'login': 'jane'}
>>> @attr.s
... class C(object):
... x = attr.ib()
... y = attr.ib()
... z = attr.ib()
>>> attr.asdict(C("foo", "2", 3), filter=attr.filters.include(int, C.x))
{'x': 'foo', 'z': 3}
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.
And sometimes you even want mutable objects as default values (ever used accidentally `` def f(arg=[]) `` ?).
`` attrs `` has you covered in both cases:
.. doctest ::
>>> import collections
>>> @attr.s
... class Connection(object):
... socket = attr.ib()
... @classmethod
... def connect(cl, db_string):
... # connect somehow to db_string
... return cl(socket=42)
>>> @attr.s
... class ConnectionPool(object):
... db_string = attr.ib()
2015-01-29 22:10:56 +00:00
... pool = attr.ib(default=attr.Factory(collections.deque))
2015-01-29 21:32:41 +00:00
... debug = attr.ib(default=False)
2015-01-28 16:36:58 +00:00
... def get_connection(self):
... try:
... return self.pool.pop()
... except IndexError:
... if self.debug:
... print "New connection!"
... return Connection.connect(self.db_string)
... def free_connection(self, conn):
... if self.debug:
... print "Connection returned!"
... 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)
More information on why class methods for constructing objects are awesome can be found in this insightful `blog post <http://as.ynchrono.us/2014/12/asynchronous-object-initialization.html> `_ .
2015-01-29 11:20:17 +00:00
2015-02-05 10:04:25 +00:00
Validators
----------
2015-04-26 14:25:56 +00:00
Although your initializers should be as dumb as possible, it can come handy to do some kind of validation on the arguments.
2015-01-29 11:20:17 +00:00
That's when :func: `attr.ib` \ ’ s `` validator `` argument comes into play.
2015-02-20 09:27:27 +00:00
A validator is simply a callable that takes three arguments:
2015-02-09 12:16:56 +00:00
#. The *instance* that's being validated.
#. The *attribute* that it's validating
#. and finally the *value* that is passed for it.
2015-02-09 12:30:29 +00:00
If the value does not pass the validator's standards, it just raises an appropriate exception.
Since the validator runs *after* the instance is initialized, you can refer to other attributes while validating :
2015-01-29 11:26:25 +00:00
.. doctest ::
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'!")
2015-01-29 11:26:25 +00:00
>>> @attr.s
... class C(object):
2015-02-09 12:30:29 +00:00
... x = attr.ib(validator=x_smaller_than_y)
... y = attr.ib()
>>> 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
2015-02-02 15:08:43 +00:00
`` attrs `` won't intercept your changes to those attributes but you can always call :func: `attr.validate` on any instance to verify, that it's still valid:
2015-02-02 11:13:11 +00:00
.. doctest ::
2015-02-09 12:30:29 +00:00
>>> i = C(4, 5)
2015-02-02 11:13:11 +00:00
>>> i.x = 5 # works, no magic here
2015-02-02 13:04:47 +00:00
>>> attr.validate(i)
2015-02-02 11:13:11 +00:00
Traceback (most recent call last):
...
2015-02-09 12:30:29 +00:00
ValueError: 'x' has to be smaller than 'y'!
2015-02-02 11:13:11 +00:00
2015-01-29 11:26:25 +00:00
`` attrs `` ships with a bunch of validators, make sure to :ref: `check them out <api_validators>` before writing your own:
2015-01-29 11:20:17 +00:00
.. doctest ::
>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.instance_of(int))
>>> C(42)
C(x=42)
>>> C("42")
Traceback (most recent call last):
...
2015-01-29 21:32:41 +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 'int'>, '42')
2015-01-29 11:20:17 +00:00
2015-01-29 18:04:23 +00:00
If you like `zope.interface <http://docs.zope.org/zope.interface/api.html#zope-interface-interface-specification> `_ , `` attrs `` also comes with a :func: `attr.validators.provides` validator:
2015-01-29 12:05:04 +00:00
2015-01-29 18:04:23 +00:00
.. doctest ::
>>> import zope.interface
>>> class IFoo(zope.interface.Interface):
... def f():
... """A function called f."""
>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.provides(IFoo))
>>> C(x=object())
Traceback (most recent call last):
...
2015-01-29 21:32:41 +00:00
TypeError: ("'x' must provide <InterfaceClass __builtin__.IFoo> which <object object at 0x10bafaaf0> doesn't.", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=<provides validator for interface <InterfaceClass __builtin__.IFoo>>), <InterfaceClass __builtin__.IFoo>, <object object at 0x10bafaaf0>)
2015-01-29 18:04:23 +00:00
>>> @zope.interface.implementer(IFoo)
... @attr.s
... class Foo(object):
... def f(self):
... print("hello, world")
>>> C(Foo())
C(x=Foo())
2015-01-29 12:05:04 +00:00
2015-02-20 10:30:46 +00:00
You can also disable them globally:
>>> attr.set_run_validators(False)
>>> C(42)
C(x=42)
>>> attr.set_run_validators(True)
>>> C(42)
Traceback (most recent call last):
...
2015-02-20 12:29:47 +00:00
TypeError: ("'x' must provide <InterfaceClass __builtin__.IFoo> which 42 doesn't.", Attribute(name='x', default=NOTHING, validator=<provides validator for interface <InterfaceClass __builtin__.IFoo>>, repr=True, cmp=True, hash=True, init=True), <InterfaceClass __builtin__.IFoo>, 42)
2015-02-20 10:30:46 +00:00
2015-02-05 10:04:25 +00:00
Other Goodies
-------------
2015-01-29 19:50:42 +00:00
Do you like Rich Hickey?
I'm glad to report that Clojure's core feature is part of `` attrs `` : `assoc <https://clojuredocs.org/clojure.core/assoc> `_ !
I guess that means Clojure can be shut down now, sorry Rich!
.. doctest ::
>>> @attr.s
... class C(object):
... x = attr.ib()
... y = attr.ib()
>>> i1 = C(1, 2)
>>> i1
C(x=1, y=2)
>>> i2 = attr.assoc(i1, y=3)
>>> i2
C(x=1, y=3)
>>> i1 == i2
False
2015-01-30 07:57:33 +00:00
Sometimes you may want to create a class programmatically.
`` attrs `` won't let you down:
.. doctest ::
>>> @attr.s
... class C1(object):
... x = attr.ib()
... y = attr.ib()
>>> C2 = attr.make_class("C2", ["x", "y"])
>>> attr.fields(C1) == attr.fields(C2)
True
You can still have power over the attributes if you pass a dictionary of name: `` attr.ib `` mappings and can pass arguments to `` @attr.s `` :
.. doctest ::
>>> C = attr.make_class("C", {"x": attr.ib(default=42),
... "y": attr.ib(default=attr.Factory(list))},
2015-02-20 12:29:47 +00:00
... repr=False)
2015-01-30 07:57:33 +00:00
>>> i = C()
>>> i # no repr added!
<attr._make.C object at ...>
>>> i.x
42
>>> i.y
[]
2015-01-30 16:48:34 +00:00
Finally, you can exclude single attributes from certain methods:
.. doctest ::
>>> @attr.s
... class C(object):
... user = attr.ib()
2015-02-20 12:29:47 +00:00
... password = attr.ib(repr=False)
2015-01-30 16:48:34 +00:00
>>> C("me", "s3kr3t")
C(user='me')