Search code examples
pythonpython-dataclasses

Store the order of arguments given to dataclass initializer


Using the Python dataclass decorator generates signatures with arguments in a particular order:

from dataclasses import dataclass
from inspect import signature

@dataclass
class Person:
    age: int
    name: str = 'John'

print(signature(Person))

Gives (age: int, name: str = 'John') -> None.

Is there a way to capture the order of arguments given when Person is instantiated? That is: Person(name='Jack', age=10) -> ('name', 'age'). I'm at a loss because writing an __init__ method on Person defeats most reasons for using the dataclass decorator. I don't want to lose the type hints you get when creating a Person, but I need to serialize the instance to JSON with keys in the order used at initialization.


Solution

  • I think this is a good use case for writing your own __init__, and I don't think that defeats the point of using a dataclass at all. You still get a nice __str__, __repr__, __eq__, and (if frozen) __hash__. You also get pattern matching for free.

    A double-star kwargs argument is a Python dictionary, and Python dictionaries remember the order in which fields are inserted. Consider something like this.

    from __future__ import annotations
    
    from dataclasses import dataclass, InitVar
    from inspect import signature
    from typing import Any
    
    @dataclass(init=False)
    class Person:
        age: int
        name: str = 'John'
    
        original_ctor_args: InitVar[dict[str, Any]]
    
        def __init__(self, **kwargs):
            self.age = kwargs['age']
            self.name = kwargs.get('name', 'John')
    
            self.original_ctor_args = list(kwargs)
    
    print(Person(age=10, name = 'Joe').original_ctor_args) # Prints ['age', 'name']
    print(Person(name = 'Alice', age=15).original_ctor_args) # Prints ['name', 'age']
    print(Person(age=20).original_ctor_args) # Prints ['age']