-
-
Notifications
You must be signed in to change notification settings - Fork 364
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
Handling of None when assigned to non-optional attribute #655
Comments
Sounds like a case for a validator to me? |
@hynek That would solve one of the use cases but validators can't change the value, can they? And if so, wouldn't have access to what the default value should be. |
There's |
@hynek That's exactly what I'm looking for. Not sure how I missed that in the docs, thanks! |
Wait, I misread the description on that converter, and there are some issues with it. While that is the behavior I'm suggesting here, use of the
See: import attr
from attr.converters import default_if_none
@attr.s()
class Test:
i: str = attr.ib(default="init",converter=default_if_none(default="converter"))
# Sets via init: Test(i='init')
print(repr(Test()))
# Sets via converter: Test(i='converter')
print(repr(Test(i=None)))
# Doesn't trigger converter: Test(i=None)
t = Test(i="original")
t.i = None
print(repr(t)) |
The |
Hi, we have tried to utilize Shortly, the ideal solution I see is a way to specify default / factory / type once, an independent converter or validator, and a default_if_none thing, which uses what is defined (uses default, calls factory or type c-tor if none is passed, or calls the converter or type c-tor on the value passed). Maybe #709 could solve this. |
I would love having a more coherent story here, but what's wrong with: In [1]: import attr
In [2]: @attr.define
...: class C:
...: x: int = attr.field(default=None, converter=attr.converters.default_if_none(42))
...:
In [3]: C()
Out[3]: C(x=42)
In [4]: C(x=None)
Out[4]: C(x=42) |
It is a bit hard to recall all the details after a year, but I'll try 😄. I'm pretty sure I tried to simulate the C++ struct member definition behaviour. Let's say we declare a field and want it to always have a certain non-trivial type. We want to convert any other input to this type (maybe avoid copying if the input is of the right type), and also want to use a default constructor, if no value is provided: @attr.define
class C:
x : list = attr.field(factory=list, converter=attr.converters.default_if_none(factory=list))
>>> C()
C(x=[])
>>> C(None)
C(x=[])
>>> C({4})
C(x={4}) Here we're specifying the type 3 times, and the default value is specified twice. We haven't specified our conversion function yet, so let's do this: @attr.define
class C2:
x : list = attr.field(factory=list, converter=attr.converters.pipe(
attr.converters.default_if_none(factory=list),
list # or lambda x: isinstance(x, list): x else list(x)
# ^^ in a generic function we could use the attribute definition
# that's why 3-arg converters could be useful
))
>>> C2()
C2(x=[])
>>> C2(None)
C2(x=[])
>>> C2({4})
C2(x=[4]) As you can see, there is a lot of repetition, but the example is quite simple and basic. Maybe, we could just write: @attr.define
class C:
x : list # maybe with = [] (which is wrong, but anyway) or a factory
y: Optional[list] # allows None |
Hmm, I'm afraid you're asking for too much, your example looks good for simple cases but falls apart/becomes too unpredictable when you think it further – especially with more complex type annotations. Case in point, the fact that In the end, this is all about wanting three-arg converters again so you get to access attribute metadata. I've done some more thinking in #146 (comment) – I feel like that would at least give you the tools to achieve what you want? |
It would be nice to have control over what happens when someone attempts to set a non-optional attribute values explicitly to
None
: set default value, or raiseValueError
.The following example shows a rough example, including a possible use case dealing with deserialized data that might have inappropriate or missing values (yes, I'm aware of
cattrs
but that would require constructing a full object rather than simply applying partial updates).While I'm aware there may be some concerns about modifying existing behavior (and thus unintentionally breaking code and existing workarounds), these behaviors could initially be enabled via flags on
attr.ib
, e.g.attr.ib(default=3, default_if_none=True)
orattr.ib(default=3, error_if_none=True)
.This is sort of the inverse of #460
The text was updated successfully, but these errors were encountered: