122 lines
3.7 KiB
ReStructuredText
122 lines
3.7 KiB
ReStructuredText
.. _examples:
|
||
|
||
Examples
|
||
========
|
||
|
||
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.
|
||
|
||
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)
|
||
>>> attr.ls(Coordinates) == attr.ls(SeriousCoordinates)
|
||
True
|
||
|
||
|
||
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()
|
||
... pool = attr.ib(default_factory=collections.deque)
|
||
... debug = attr.ib(default_value=False)
|
||
... 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>`_.
|
||
|
||
|
||
Although your initializers should be a dumb as possible, it can come handy to do some kind of validation on the arguments.
|
||
That's when :func:`attr.ib`\ ’s ``validator`` argument comes into play.
|
||
A validator is simply a callable that takes two arguments: the attribute that it's validating
|
||
|
||
.. 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):
|
||
...
|
||
TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default_value=NOTHING, default_factory=NOTHING, validator=<instance_of validator for type <type 'int'>>), <type 'int'>, '42')
|
||
|
||
``attrs`` ships with a bunch of validators, make sure to :ref:`check them out <api_validators>` before writing your own!
|