Как правильно наследовать данные из экземпляра суперкласса? Например, рассмотрим что-то, что я могу заставить работать, но его поведение сбивает с толку:
from dataclasses import dataclass
from typing import Self
@dataclass
class SuperClass:
hello: int
world: int
@dataclass
class ChildClass(SuperClass):
kitty: int
@classmethod
def from_super(cls, s: SuperClass, kitty: int) -> Self:
x = cls(s, kitty=kitty)
return x
Теперь посмотрим, работает ли это:
super_instance = SuperClass(0, 1)
child = ChildClass.from_super(super_instance, 2)
child
Это производит вывод:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[48], line 1
----> 1 ChildClass.from_super(super_instance, 2)
Cell In[46], line 15
13 @classmethod
14 def from_super(cls, s: SuperClass, kitty: int) -> Self:
---> 15 x = cls(s, kitty=kitty)
16 return x
TypeError: ChildClass.__init__() missing 1 required positional argument: 'world'
Итак, как мне сделать это правильно, не записывая вручную каждый экземпляр переменной из суперкласса?
В вашем примере вы, похоже, пытаетесь наследовать данные из экземпляра суперкласса (SuperClass
) в подкласс (ChildClass
). Однако у вашего подхода есть некоторые проблемы, в первую очередь из-за того, как в Python работают декоратор dataclass
и наследование.
Чтобы правильно наследовать данные от экземпляра суперкласса, вам необходимо использовать наследование. Давайте изменим ваш код, чтобы продемонстрировать правильный способ сделать это с помощью наследования:
ChildClass
наследником SuperClass
.super()
для инициализации части суперкласса.from dataclasses import dataclass
from typing import Self
@dataclass
class SuperClass:
hello: int
world: int
@dataclass
class ChildClass(SuperClass):
kitty: int
@classmethod
def from_super_wtf(cls, s: SuperClass, kitty: int) -> Self:
x = cls(hello=s.hello, world=s.world, kitty=kitty)
return x
@classmethod
def from_super_works(cls, s: SuperClass, kitty: int) -> Self:
x = cls(hello=s.hello, world=s.world, kitty=kitty)
return x
@classmethod
def from_super_sane(cls, s: SuperClass, kitty: int) -> Self:
x = cls(hello=s.hello, world=s.world, kitty=kitty)
return x
# Testing the code
super_instance = SuperClass(0, 1)
child = ChildClass.from_super_works(super_instance, 2)
child_wtf = ChildClass.from_super_wtf(super_instance, 2)
child_sane = ChildClass.from_super_sane(super_instance, 2)
print(child) # Expected: ChildClass(hello=0, world=1, kitty=2)
print(child_wtf) # Expected: ChildClass(hello=0, world=1, kitty=2)
print(child_sane) # Expected: ChildClass(hello=0, world=1, kitty=2)
В этом подходе ChildClass
наследуется от SuperClass
, что означает, что он также наследует атрибуты hello
и world
. При создании нового экземпляра
В конечном итоге вы несете ответственность за «разборку» экземпляра суперкласса в форму, которую SuperClass.__init__
примет. Один из хрупких способов сделать это — просто распаковать s.__dict__
и использовать его в качестве аргументов ключевого слова. (Хрупкое, потому что, как правило, содержимое s.__dict__
не обязательно находится во взаимно однозначном соответствии с исходными аргументами, использованными для создания s
.)
from dataclasses import dataclass
from typing import Self
@dataclass
class SuperClass:
hello: int
world: int
@dataclass
class ChildClass(SuperClass):
kitty: int
@classmethod
def from_super(cls, s: SuperClass, kitty: int) -> Self:
x = cls(**s.__dict__, kitty=kitty)
return x
@classmethod def from_super(cls, s: SuperClass, kitty: int) -> Self: x = cls(s, kitty=kitty) return x
Здесь, если вы не хотите явно перечислять все аргументы для cls
, вместо передачи экземпляра s
суперкласса вам нужно создать из s
либо кортеж значений, которые можно передать в качестве позиционных аргументов *args
, либо словарь параметров. имена и значения, которые вы можете передать в качестве аргументов ключевых слов **kwargs
.
Поскольку вы используете простые классы данных, это легко:
Либо используя dataclasses.astuple:
x = cls(*astuple(s), kitty=kitty)
Или используйте dataclasses.asdict:
x = cls(**asdict(s), kitty=kitty)
Я отредактировал свой вопрос, так как допустил опечатку и забыл сделать с наследством. Теперь это должно быть исправлено. Однако это все еще не работает так, как можно было бы ожидать. Кроме того, и это ключевой момент, я не хочу этого делать
x = cls(hello=s.hello, world=s.world, kitty=kitty)
, потому что в моем суперклассе может быть много переменных, и я хотел бы автоматически наследовать их.