Skip to content

Python dataclasses __post_init__ order of parameters is fragile #91507

Closed
@agronskiy

Description

@agronskiy

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

# Does this class have a post-init function?
(in Python 3.10) where the __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?

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibPython modules in the Lib dirtype-featureA feature request or enhancement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions