attrs
attrs copied to clipboard
How to initialize an instance where all fields are default values?
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.
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
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)
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:
- This would also allow for assigning the same (more complex) field to multiple attributes.
- Better readability
- Reusability principle
@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.
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.
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.
@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:
- Read those articles, watch those videos.
- Apologize.
- Move on.