Closed
Description
When using __post_init__
hook,a fragility related to the order of InitVar
arguments arises. Below are two examples an a suggestion:
Example 1:
from dataclasses import InitVar, dataclass
@dataclass
class A:
arg2: InitVar[str]
arg1: InitVar[str] # <-- imagine somebody reversed arguments here
def __post_init__(self, arg1: str, arg2: str):
print(f'A: arg1={arg1}, arg2={arg2}')
A(arg1="arg1", arg2="arg2")
prints reversed order A: arg1=arg2, arg2=arg1
Example 2 (more complex with inheritance):
from dataclasses import InitVar, dataclass
@dataclass
class A:
arg1: InitVar[str]
arg2: InitVar[str]
def __post_init__(self, arg1: str, arg2: str):
print(f'A: arg1={arg1}, arg2={arg2}')
@dataclass
class B(A):
arg3: InitVar[str]
def __post_init__(self, arg3: str, arg1: str, arg2: str): # <-- note somebody shuffled args here
print(f'B: arg1={arg1} arg2={arg2} arg3={arg3}')
super().__post_init__(arg1=arg1, arg2=arg2)
B(arg1="arg1", arg2="arg2", arg3="arg3")
prints
B: arg1=arg2 arg2=arg3 arg3=arg1
A: arg1=arg2, arg2=arg3
The root of the problem is dataclass.py:564
Line 564 in 37a53fb
__post_init__
hook is constructed by just placing positional arguments:
# Does this class have a post-init function?
if has_post_init:
params_str = ','.join(f.name for f in fields
if f._field_type is _FIELD_INITVAR)
body_lines.append(f'{self_name}.{_POST_INIT_NAME}({params_str})')
suggestion is to change it to a call with kw-arguments which does not hurt at all but removes the problems with potential change of order of fields:
# Does this class have a post-init function?
if has_post_init:
params_str = ','.join(f"{f.name}={f.name}" for f in fields
if f._field_type is _FIELD_INITVAR)
body_lines.append(f'{self_name}.{_POST_INIT_NAME}({params_str})')
If one changes it, the problems related to ordering are gone. Is there anything I miss?