.. _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.fields(Coordinates) == attr.fields(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=attr.Factory(collections.deque)) ... debug = attr.ib(default=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 `_. 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 and the value that is passed for it. If the value does not pass the validator's standards, it just raises an appropriate exception: .. doctest:: >>> def smaller_than_5(attribute, value): ... if value >= 5: ... raise ValueError("'{name}' has to be smaller than 5!" ... .format(name=attribute.name)) >>> @attr.s ... class C(object): ... x = attr.ib(validator=smaller_than_5) >>> C(42) Traceback (most recent call last): ... ValueError: 'x' has to be smaller than 5! ``attrs`` won't intercept your changes to those attributes but you can always call :func:``attr.valid`` on any instance to verify, that it's still valid: .. doctest:: >>> i = C(4) >>> i.x = 5 # works, no magic here >>> attr.valid(i) Traceback (most recent call last): ... ValueError: 'x' has to be smaller than 5! ``attrs`` ships with a bunch of validators, make sure to :ref:`check them out ` before writing your own: .. 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 (got '42' that is a ).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=>), , '42') If you like `zope.interface `_, ``attrs`` also comes with a :func:`attr.validators.provides` validator: .. 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): ... TypeError: ("'x' must provide which doesn't.", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=>), , ) >>> @zope.interface.implementer(IFoo) ... @attr.s ... class Foo(object): ... def f(self): ... print("hello, world") >>> C(Foo()) C(x=Foo()) 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) Do you like Rich Hickey? I'm glad to report that Clojure's core feature is part of ``attrs``: `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 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))}, ... add_repr=False) >>> i = C() >>> i # no repr added! >>> i.x 42 >>> i.y [] Finally, you can exclude single attributes from certain methods: .. doctest:: >>> @attr.s ... class C(object): ... user = attr.ib() ... password = attr.ib(no_repr=True) >>> C("me", "s3kr3t") C(user='me')