Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wencan opened this issue Aug 1, 2023 · 3 comments
Open

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

wencan opened this issue Aug 1, 2023 · 3 comments

Comments

@wencan
Copy link

wencan commented Aug 1, 2023

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.

@hynek
Copy link
Member

hynek commented Aug 1, 2023

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?

@wencan
Copy link
Author

wencan commented Aug 2, 2023

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

@klezm
Copy link

klezm commented Aug 8, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants