attrs icon indicating copy to clipboard operation
attrs copied to clipboard

How to initialize an instance where all fields are default values?

Open wencan opened this issue 2 years ago • 7 comments

How to initialize an instance where all fields are default values?

I don't want to add a default value to each field, and I want each field to automatically get a zero value by default.

This would make dataclass much like struct in the go language.

wencan avatar Aug 01 '23 09:08 wencan

Python doesn't have the concept of zero values, so this is icky.

But I suspect you could get this done with field hooks @sscherfke?

hynek avatar Aug 01 '23 09:08 hynek

@hynek

I have implemented it via field hooks.

def field_transformer(cls, fields):
    new_fields = []
    for field in fields:
        if field.default is attrs.NOTHING:
            new_fields.append(field.evolve(default=attrs.Factory(field.type)))
        else:
            new_fields.append(field)
    return new_fields

@attrs.define(kw_only=True, field_transformer=field_transformer)
class Name:
    first_name: str = attrs.field(default='')
    last_name: str 

However, in the above example, I had to specify kw_only as true, otherwise I would get the following error:

ValueError: No mandatory attributes allowed after an attribute with a default value or factory.  Attribute in question: Attribute(name='city', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=<class 'str'>, converter=None, kw_only=False, inherited=False, on_setattr=None, alias=None)

I read the attrs code and found that the field_transformer is executed later than the default value checks.

    # Mandatory vs non-mandatory attr order only matters when they are part of
    # the __init__ signature and when they aren't kw_only (which are moved to
    # the end and can be mandatory or non-mandatory in any order, as they will
    # be specified as keyword args anyway). Check the order of those attrs:
    had_default = False
    for a in (a for a in attrs if a.init is not False and a.kw_only is False):
        if had_default is True and a.default is NOTHING:
            raise ValueError(
                "No mandatory attributes allowed after an attribute with a "
                f"default value or factory.  Attribute in question: {a!r}"
            )

        if had_default is False and a.default is not NOTHING:
            had_default = True

    if field_transformer is not None:
        attrs = field_transformer(cls, attrs)

wencan avatar Aug 02 '23 02:08 wencan

Running over a similar issue. Would it be possible to use attrs.field() with a context manager? Something like that:

import typing
import attrs

@attrs.define
class MyClass:
    a: int = 1
    with attrs.field(converter = typing.Self):
        b: str
        c: list

typing.Self should indicate to use field.type. Alternatively one could use a lambda expression: lambda f: f.type.

The benefits I see:

  1. This would also allow for assigning the same (more complex) field to multiple attributes.
  2. Better readability
  3. Reusability principle

klezm avatar Aug 08 '23 09:08 klezm

@hynek How about applying the field transformer before the order check, so embedders would have a chance of rearranging the fields? I find it's quite limiting that the fields of a subclass just get pasted below the parent fields, which is less flexible than we are with raw classes. Before I used attrs, I commonly did things like this:

class AbsPage:
    def __init__(self, size, rotation, overlays):
        self.size = size
        self.rotation = rotation
        self.overlays = overlays
        # ...

class BlankPage (AbsPage):
    pass

class Page (AbsPage):
    def __init__(doc, index, *args, **kwargs):
        self.doc = doc
        self.index = index
        super().__init__(*args, **kwargs)

But attrs doesn't allow the custom placement of the *args/**kwargs as you could normally do in a constructor, and instead forces keyword-only subclass fields on the caller, which is a counter-intuitive annoyance in many simple cases. There's no appealing reason why you should thus restrict downstreams' freedom of design.

mara004 avatar Jul 19 '24 22:07 mara004

Side-note: Please remove the ridiculous potshots against inheritance ("subclassing is bad for you"). Any experienced application data model author would put you back in kindergarten for suggesting that. ;) Except perhaps some very few oddballs coming from use cases where it isn't needed. All these anti-subclassing statements achieve is a) annoy application authors; b) embarrass yourself by showing how far away your mind is from development practice.

The reality I've seen is that inheritance is highly useful, and often unavoidable. Wildly claiming "something is bad" is easy, but you fail to point out what is supposed to be the better alternative.

Overall, I feel like attrs could be much better these days if you hadn't neglected inheritance, and not taken the ill-advised decision of dictating keyword-only subclasses.

mara004 avatar Jul 20 '24 10:07 mara004

The reality I've seen is that inheritance is highly useful, and often unavoidable. Wildly claiming "something is bad" is easy, but you fail to point what is supposed to be the better alternative.

Before this topic escalates:

  • There are valid reasons for and against subclassing. Hyneks statements have objective reasons, you can read about them here: https://hynek.me/articles/python-subclassing-redux/
  • I suggest to slightly rephrase the docs like "You can also create subclasses of attrs classes like this:" and then adding a "tip" admonition box with a more nuanced advice against subclassing which also containts links to the youtube talk and the artikle.

sscherfke avatar Jul 20 '24 11:07 sscherfke

@mara004

Any experienced application data model author would put you back in kindergarten for suggesting that.

You choose your language really poorly if your goal is to convince anybody of anything. Hynek is an expert in this field and has written extensively about this topic. He's given talks at international conferences about this topic. His link to "subclassing is bad for you" leads to a legendary talk by a different pair of experts in the field.

My advice to you @mara004 is to:

  1. Read those articles, watch those videos.
  2. Apologize.
  3. Move on.

ambv avatar Jul 20 '24 11:07 ambv