Paul Pinterits <rawi...@gmail.com> added the comment:

> dataclasses doesn't know the signature of the base class's __init__, so it 
> can't know how to call it.

The dataclass doesn't need to know what arguments the parent __init__ accepts. 
It should consume the arguments it needs to initialize its instance attributes, 
and forward the rest to the parent __init__.

> The typical way to handle this is for the derived class to add a 
> __post_init__() that calls into the base class's __init__().

How is that supposed to work? Like you said, the class doesn't know what 
arguments its parent constructor requires. If the derived class doesn't 
implement a custom __init__, @dataclass will generate one with an (probably) 
incorrect signature. Changing the signature is pretty much the only reason why 
you would need to implement a custom __init__, after all. For example:

```
@dataclasses.dataclass
class Foo:
    foo: int
    
    def __init__(self):
        self.foo = 5
    
@dataclasses.dataclass
class Bar(Foo):
    bar: int
    
    def __post_init__(self):
        super().__init__()

bar = Bar(1)  # TypeError: __init__() missing 1 required positional argument: 
'bar'
```

And even if this workaround actually worked, it would only be applicable if you 
*know* you're dealing with a dataclass and you'll have this problem. If you're 
writing something like a class decorator (similar to @dataclass), you don't 
know if the decorated class will be a dataclass or not. If it's a regular 
class, you can rely on the __init__s being chain-called. If it's a dataclass, 
you can't. Therefore, a class decorator that generates/replaces the __init__ 
method would need to take special care to be compatible with dataclasses, just 
because dataclasses don't follow basic OOP design principles.

Consider this trivial class decorator that generates an __init__ method:

```
def log_init(cls):
    try:
        original_init = vars(cls)['__init__']
    except KeyError:
        def original_init(self, *args, **kwargs):
            super(cls, self).__init__(*args, **kwargs)
    
    def __init__(self, *args, **kwargs):
        print(f'{cls.__name__}.__init__ was called')
        original_init(self, *args, **kwargs)
    
    cls.__init__ = __init__
    return cls

@log_init
@dataclasses.dataclass
class Foo:
    foo: int
    
@dataclasses.dataclass
class Bar(Foo):
    bar: int
    
Foo(1)  # Prints "Foo.__init__ was called"
Bar(1, 2)  # Prints nothing
```

How do you implement this in a way that is compatible with @dataclass?

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue43835>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to