instant
instant

Reputation: 696

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?

Upvotes: 2

Views: 10670

Answers (2)

Peter
Peter

Reputation: 13505

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)

Upvotes: 1

instant
instant

Reputation: 696

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: http://attrs.readthedocs.io/en/stable/examples.html?highlight=attrs_post_init#other-goodies

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.

Upvotes: 2

Related Questions