Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32k
Description
Bug report
I believe there is an issue with the order in which@dataclass
fields are defined when some of those fields have a corresponding@property
definition.
MWE:
fromdataclassesimportdataclass,field@dataclassclassA:a:floatb:bool_a:float=field(init=False,repr=False,compare=False)@property# type: ignore[no-redef]defa(self)->float:returnself._a@a.setterdefa(self,new_a:float)->None:# Perform some operation on the new 'a' value (here we sum '100').self._a=new_a+100print(A(3,false))
Running the above example produces the following error:
TypeError: non-default argument 'b' follows default argument
The issue is that when a dataclass is formed it is assumed that any field has a default value if the name corresponding to the field is also an attribute.
Since properties are registered as attributes, this causes the above issue.
One could try to change the order of the fields, such that property fields come after non-property fields.
However, this means that the corresponding property fields do not need to be set when an instance of the class is initialized, which leads to errors later on.
Furthermore, a certain order for the fields might simply make more sense when creating an instance.
I think a simple solution would be to modify the_get_field()
function, from:
def_get_field(cls,a_name,a_type,default_kw_only):# Return a Field object for this field name and type. ClassVars and# InitVars are also returned, but marked as such (see f._field_type).# default_kw_only is the value of kw_only to use if there isn't a field()# that defines it.# If the default value isn't derived from Field, then it's only a# normal default value. Convert it to a Field().default=getattr(cls,a_name,MISSING)ifisinstance(default,Field):f=defaultelse:ifisinstance(default,types.MemberDescriptorType):# This is a field in __slots__, so it has no default value.default=MISSINGf=field(default=default) . . .
to (or a similar logic, if not this exact structure):
def_get_field(cls,a_name,a_type,default_kw_only):# Return a Field object for this field name and type. ClassVars and# InitVars are also returned, but marked as such (see f._field_type).# default_kw_only is the value of kw_only to use if there isn't a field()# that defines it.# If the default value isn't derived from Field, then it's only a# normal default value. Convert it to a Field().default=getattr(cls,a_name,MISSING)ifisinstance(default,Field):f=defaultelse:ifisinstance(default,property):# This field has an attribute, but that attribute is a# @property and not a default value.default=MISSINGelifisinstance(default,types.MemberDescriptorType):# This is a field in __slots__, so it has no default value.default=MISSINGf=field(default=default) . . .
However, I'm not sure if I am missing something that might cause problems.
Let me know if this approach would be good and if I should create a PR for it.