Python Attrs: Advanced Data Classes, With Example Code

The package Python Attrs allows you to create advanced data classes using simple annotations. Of course, python has its own native data class module as well, but the Python attrs package offers a couple of extra features you might like!

Install attrs

The attrs package is not part of the base library, so you will need to install it with the pip install command or something similar, like Pipenv. You probably also want to create a virtual environment, so it won’t interfere with other projects you have. The package is called attrs, so installing it will look like this:

$ pip install attrs

# or with pipenv:
$ pipenv install attrs

Attrs vs data classes

The authors of attrs have, in fact, worked on the PEP that introduced data classes into Python. Python’s native data classes are intentionally kept simpler and easier to understand while attrs offering the full range of features you might want!

Some of the reasons to choose Python attrs over the built-in data classes are:

  • You are using a Python version from before 3.7. Attrs has you covered since it supports all mainstream Python versions, including CPython 2.7 and PyPy.
  • You want more features: attrs offers validators and converters
  • You want optimal performance and minimal memory usage using attrs slotted classes

A basic Python attrs example

Let’s look at a very basic example first:

import attr

@attr.s
class Person(object):
    name = attr.ib(default='John')
    surname = attr.ib(default='Doe')
    age = attr.ib(init=False)
    
p = Person()
print(p)
p = Person('Bill', 'Gates')
p.age = 60
print(p)

# Output: 
#   Person(name='John', surname='Doe', age=NOTHING)
#   Person(name='Bill', surname='Gates', age=60)

A couple of observations:

  • The syntax is less elegant and more verbose than that of data classes, but you get extra features in return.
  • Similar to data classes, you get a nicely formatted representation of your data when you print it.
  • The attrs package uses smartly picked names like attr.ib, so you only need to import attr. You can, alternatively, import the full names. For example with from attr import attrib, attrs, and use those names instead. The functionality is the same.

Next let’s look at the most important features this package offers over regular data classes: validators and converters.

Python attrs validator example

You can add validators to you attrs data class in two ways:

  1. Using a decorator
  2. By providing a callable function

I’ll demonstrate the callable function method here first. Attrs offers several validators out of the box, of which we’ll use the instance_of validator in the following example:

>>> @attr.s
... class C(object):
...     x = attr.ib(validator=attr.validators.instance_of(int))
>>> C(42)
C(x=42)
>>> C("a string")

Traceback (most recent call last):
   ...
TypeError: ("'x' must be <type 'int'> (got 'a string' that is a <type 'str'>).", ...

Since we tried to create an object C with a string value for x, the instance_of validator throws an error because it requires an int type instead of a string.

Let’s now define our own validator:

import attr

@attr.s
class DividableByTwo(object):
    x = attr.ib()

    @x.validator
    def check(self, attribute, value):
        if value % 2 != 0:
            raise ValueError(f'{value} is not dividable by 2')

print (DividableByTwo(60))
print (DividableByTwo(11))

# Output will be something like:
# DividableByTwo(x=60)
# ...
# ValueError: 11 is not dividable by 2

Python attrs converter example

A converter takes the value that is set and converts it automatically. You can use this for all kinds of purposes. One example is to automatically convert a value to an int. Again, let’s start with using a callable function, in this case we simply use Python’s int() function:

import attr

@attr.s
class C(object):
    x = attr.ib(converter=int)

c = C("1")
print(c)
# Output:
# C(x=1)

Our input (the string “1”) was converted to an integer automatically. Because converters are run before validators, you can validate the final value after conversion. E.g., you could come the above two examples to first convert any input to int, and then check if the value is dividable by two.

Using slots with Python attrs

Finally, you can tell attrs to use slotted classes. Slotted classes have some advantages over regular classes:

  • They have a small memory footprint
  • They are faster

In short, with a slotted class you explicitly state which instance attributes you expect your object instances to have. This way, Python can leave out some checks and such, resulting in less memory usage and slight speed increases. You can find more details in the attrs documentation here.

However, slotted classes come with caveats too, especially when you manually create them. Luckily, attrs offers us a simple way to enable the feature:

import attr

@attr.s(slots=True)
class YourClassName:
    ...

Keep learning

About the author

Erik is the owner of Python Land and the author of many of the articles and tutorials on this website. He's been working as a professional software developer for 25 years, and he holds a Master of Science degree in computer science. His favorite language of choice: Python!

Leave a Comment