Python Attrs Library: Define Attr. Ib(Default=) Using Other Attributes

Python Attrs Library: Define Attr. Ib(Default=) Using Other Attributes

I am using the amazing attrs library to define a lot of object attributes in a very elegant way and it has been working like a charm so far.

The only problem that I am currently having is that I sometimes want to define default values by referencing other attr.ib() attributes. Here is some code that would run if the default for name were a static string:

import attr
from attr.validators import instance_of
import datetime

@attr.s
class Something:
    some_date = attr.ib(validator=instance_of(datetime.date))
    some_number = attr.ib(convert=float)
    name = attr.ib(validator=instance_of(str),
                   default="Generic Name {0} - {1}%".format(
                       some_date.strftime("%d-%b-%Y"),
                       some_number * 100)
                   )

something_instance = Something(some_date=datetime.date.today(), some_number=0.375)

The problem is that name doesn't see a float and a date, but a _CountingAttr object, hence I get an AttributeError (and a TypeError for some_number * 100). Since I can't reference self either, how do I do this?

2 Answers

So this seems not possible with the default keyword at the moment. However, to achieve the same effect, it's possible to use the __attrs_post_init__ method, which can used to execute arbitrary calculations after instance initialization:

In my example it would basically come down to adding

def __attrs_post_init__(self):
    if self.name is None:
        self.name = "Generic Name {0} - {1}%".format(
            self.some_date.strftime("%d-%b-%Y"),
            self.some_number * 100)

Credit goes to the attrs github issue tracker for pointing me in the right direction.

You can also do it without __attrs_post_init__.

Just use default = attr.Factory(lambda self: ..., takes_self=True)

import attr
from attr.validators import instance_of
import datetime

@attr.s
class Something:
    some_date = attr.ib(validator=instance_of(datetime.date))
    some_number = attr.ib(convert=float)
    name = attr.ib(validator=instance_of(str),
                   default=attr.Factory(lambda self: "Generic Name {0} - {1}%".format(
                       self.some_date.strftime("%d-%b-%Y"),
                       self.some_number * 100)
                   ), takes_self=True)

something_instance = Something(some_date=datetime.date.today(), some_number=0.375)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Alexander Ross
Author

Alexander Ross

Alexander Ross has covered the video game industry for a decade, writing deep dives on game design, esports tournaments, VR developments, and gaming culture.