Attrs: attr default based on other attributes

Created on 8 Mar 2017  Â·  5Comments  Â·  Source: python-attrs/attrs

Hi!

I have unsucessfully tried to define a default value by referencing other attributes. I'm sure the code below doesnt' work for some obvious or fundamental reason, but I would be grateful for comments on how to do something like it:

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)
                   )

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

I included the .strftime() conversion to highlight 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, what would be the correct way to do this?

Feature

Most helpful comment

I have wanted to do this several times. For example, you have a test of a function which takes many inputs. You want to test this function with many different cases. So (of course) you create a quick e.g. @attr.s class TestInput:... for that. In many cases you want some attribute to have a default depending on other attributes, but still be able to override it. The existing options are not so good for this:

  • I think you can use these for this but it's cumbersome.
  • __attrs_post_init__ is not good because you can't override it.

What I'd want, following the ad-hoc validators example, is something like this:

@attr.s
class C:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()

    @z.default
    def z_default(self, attribute):
        return self.x + self.y

This is sensitive to the initialization order. However, if you have a stateful factory, the order is already important, so this is not new. And the natural order is the definition order which is intuitive.

Another issue is that this makes it possible to specify multiple defaults. I would just raise an error if this is detected.

Final issue I can think of is that an occasional user might confuse this with a property, while it does not behave like a property:

  • The value is persisted.
  • In mutable objects, the value doesn't change if the dependencies change, unlike a dynamically-computed property.

But I think "default" is clear on the behavior it has.

All 5 comments

Hi,

for now I suggest using __attrs_post_init__ (http://attrs.readthedocs.io/en/stable/examples.html?highlight=attrs_post_init#other-goodies). The linked example is basically what you want.

I have wanted to do this several times. For example, you have a test of a function which takes many inputs. You want to test this function with many different cases. So (of course) you create a quick e.g. @attr.s class TestInput:... for that. In many cases you want some attribute to have a default depending on other attributes, but still be able to override it. The existing options are not so good for this:

  • I think you can use these for this but it's cumbersome.
  • __attrs_post_init__ is not good because you can't override it.

What I'd want, following the ad-hoc validators example, is something like this:

@attr.s
class C:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()

    @z.default
    def z_default(self, attribute):
        return self.x + self.y

This is sensitive to the initialization order. However, if you have a stateful factory, the order is already important, so this is not new. And the natural order is the definition order which is intuitive.

Another issue is that this makes it possible to specify multiple defaults. I would just raise an error if this is detected.

Final issue I can think of is that an occasional user might confuse this with a property, while it does not behave like a property:

  • The value is persisted.
  • In mutable objects, the value doesn't change if the dependencies change, unlike a dynamically-computed property.

But I think "default" is clear on the behavior it has.

At first glance I like the proposed API :)

I guess we could have a lot fun with decorators based on _CountingAttr.

I think I want this in 17.1 but it entirely depends on the goodwill of reviewers (most likely @Tinche – I gotta shanghai some innocent souls at PyCon).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fhgd picture fhgd  Â·  6Comments

indigoviolet picture indigoviolet  Â·  8Comments

hynek picture hynek  Â·  9Comments

Nicholas-Mitchell picture Nicholas-Mitchell  Â·  15Comments

Tinche picture Tinche  Â·  15Comments