Under “Impact on Typing”,
A converter
must be a callable that accepts a single positional argument, and the parameter type corresponding to this positional argument provides the type of the the synthesized __init__
parameter associated with the field.
What if the converter’s positional argument is not typed? That is,
In other words, the argument provided for the converter parameter must be compatible with Callable[[T], X]
where T
is the input type for the converter and X
is the output type of the converter.
can T
be specified without providing a typed function (something that can’t be done with a lambda expression)?
The PEP does not explicitly address the interaction of a converter
and a default_factory
; I assume they should be composable, such that
x: Foo = field(default_factory=df, converter=cv)
should be equivalent to
x: Foo = field(default_factory=lambda a: cv(df(a)))
I’m still not convinced, though, that the converter should be applied to either the default or the default factory’s return value. Doing so changes the semantics of default
and default_factory
from providing field values to providing function arguments when a converter is present.
Having the converter applied to a default is a convenience that one could live without, but also something that could be difficult to work around. Consider existing code like
start: datetime = field(default=datetime.now())
stop: datetime = field(default=datetime.fromisoformat("2023-12-31")
If the converter is not applied to the default, the class designer can still easily write, at the cost of a slight repetition,
cv = datetime.fromisoformat
start: datetime = field(default=datetime.now(), converter=cv)
stop: datetime = field(default=cv("2023-12-31"), converter=cv)
but if the converter is applied, there’s a problem finding a suitable default for start
, and one has to write a more complicated converter.
cv = datetime.fromisoformat
# start: datetime = field(default="???", converter=cv)
start: datetime = field(default=None, converter=lambda x: datetime.now() if x is None else cv(x))
stop: datetime = field(default="2023-05-03", converter=cv)
Under the “Rejected Ideas” section,
- Compatibility with attrs. Attrs unconditionally uses the converter to convert the default value.
As someone who doesn’t use the attrs
package, I don’t find this a strong argument against leaving default values unconverted.
- Simpler defaults. Allowing the default value to have the same type as user-provided values means dataclass authors get the same conveniences as their callers.
As demonstrated in my example above, I don’t think the convenience of having a converter automatically applied when feasible outweighs the consequences of being unable to avoid the converter.