It is very rare that you learn something that completely changes how you program. Reading this post about the attrs package in Python was a revelation to me.
Coming from C++, I am not too big a fan on returning everything as lists and tuples. In many cases, you want to have structure and attributes and the class in Python is a good fit for this. However, creating a proper class with attributes that has all the necessary basic methods is a pain.
This is where attrs comes in. Add its decorator to the class and designate the attributes of the class using its methods and it will generate all the necessary dunder methods for you. You can also get some nice type checking and default values for the attributes too.
- First, let us get the biggest confusion about this package out of the way! It is called attrs when you install it cause there is already another existing package called attr (the singular). But when you import and use it, then it is called attr. I know it is irritating, but this is the way it is.
To install it:
$ sudo pip3 install attrs
- To decorate the class use
attr.s. I read it is as the plural attrs. And to declare the class attributes, use
attr.ibmethod. I read it as attribute.
@attr.s class Creature: eyes = attr.ib() legs = attr.ib()
- Once declared like this, the attributes can be provided while constructing an object of the class:
c = Creature(2, 4)
- Object of this class can be constructed using keywords too:
c = Creature(legs=6, eyes=1000)
- Notice that we have not specified any default value for the attributes. So, it will rightfully complain when constructing without values:
c = Creature() TypeError: __init__() missing 2 required positional arguments: 'eyes' and 'legs'
- Default values can be specified for attributes:
@attr.s class Creature: eyes = attr.ib(default=2) legs = attr.ib(default=6) c = Creature()
Note that if there are some rules you run up against if you provide default values for some attributes and not to others.
- A beautiful
__repr__dunder method is automatically generated for your class. So, you can print any object:
c = Creature(3, 6) print(c) Creature(eyes=3, legs=6)
This is for me the killer feature! This is far more informational than just looking at a bunch of list or dict values.
- Attributes can be get or set just like normal class attributes:
c = Creature(2, 4) c.eyes = 10 print(c.legs)
- Comparison methods are already generated for you, so you can go ahead and compare objects:
c1 = Creature(2, 4) c2 = Creature(3, 9) c1 == c2
- You can add some semblance of type checking to attributes by using the
instance_ofvalidators provided by the package:
@attr.s class Creature: eyes = attr.ib(validator=attr.validators.instance_of(int)) legs = attr.ib() c = Creature(3.14, 6) TypeError: ("'eyes' must be <class 'int'> (got 3.14 that is a <class 'float'>)."
- By default, class attributes are stored in a dictionary. You can switch this to use slots by changing the decorator:
@attr.s(slots=True) class Creature: eyes = attr.ib() legs = attr.ib()
- Are you curious to see the definition of the dunder methods it generates? You can do that using the inspect package:
import inspect print(inspect.getsource(Creature.__init__)) print(inspect.getsource(Creature.__eq__)) print(inspect.getsource(Creature.__gt__))
- Want to see what are all the methods and fields the package creates for a class?
print(attr.fields(Creature)) (Attribute(name='eyes', default=NOTHING, validator=<instance_of validator for type <class 'int'>>, repr=True, cmp=True, hash=True, init=True, convert=None), Attribute(name='legs', default=NOTHING, validator=None, repr=True, cmp=True, hash=True, init=True, convert=None))
There is a lot more stuff in this awesome must-use package that can be read here
Tried with: attrs 16.1.0, Python 3.5.2 and Ubuntu 16.04