Наследование класса данных Python с изменяемыми аргументами по умолчанию возвращает нулевое значение

Я пытаюсь вычислить радиусоподобную величину из 3 списков, содержащих декартовы координаты x, y и z. Ниже приведен мой минимальный пример кода для воспроизведения проблемы, с которой я столкнулся; Дочерний класс вычисляет величину радиуса, но возвращает нулевое значение. В чем причина этого, и как это можно исправить?

Скрипт:

# -*- coding: utf-8 -*-

from dataclasses import dataclass, field
from typing import List


@dataclass
class LoadHalo:
    x: List = field(default_factory=list)
    y: List = field(default_factory=list)
    z: List = field(default_factory=list)

    def __post_init__(self):
        self.x = [1, 2, 3]
        self.y = [1, 3, 5]
        self.z = [1, 4, 7]


@dataclass
class BinHalo(LoadHalo):
    r: List = field(default_factory=list)

    def __post_init__(self):
        self.r = self.modulus(self.x, self.y, self.z)

    def modulus(self, *args):
        """Modulus of vector of arbitrary size."""
        return sum([i ** 2 for containers in args for i in containers]) ** .5


halo = BinHalo()
print(f"halo.x: {halo.x}")
print(f"halo.r: {halo.r}")

Что выводит следующие значения для x и r:

halo.x: []
halo.r: 0.0

person Gustav Rasmussen    schedule 03.12.2019    source источник
comment
добавить super().__post_init__() перед self.r = self.modulus(self.x, self.y, self.z)   -  person eyllanesc    schedule 03.12.2019


Ответы (1)


Поскольку вы переопределяете __post_init__, списки по-прежнему остаются пустыми, поскольку они никогда не инициализировались ничем, кроме пустого списка по умолчанию. Вы должны вызвать метод суперкласса, который вы переопределяете, если вам нужно его поведение.

Вы хотите следующее:

def __post_init__(self):
    super().__post_init__()
    self.r = self.modulus(self.x, self.y, self.z)

Обратите внимание на пару моментов с вашими подсказками типа: вы, вероятно, хотели float для поля r в подклассе, а также для возвращаемого типа modulus:

def modulus(self, *args) -> float:
    ...

также вы должны инициализировать списки по умолчанию с float объектами, чтобы вы могли написать:

x: List[float]

Для ваших полей списка, поскольку, по-видимому, вы хотите использовать математику с плавающей запятой.

Итак, в целом, я бы определил все так:

from dataclasses import dataclass, field
from typing import List


@dataclass
class LoadHalo:
    x: List[float] = field(default_factory=list)
    y: List[float] = field(default_factory=list)
    z: List[float] = field(default_factory=list)

    def __post_init__(self) -> None:
        self.x = [1.0, 2.0, 3.0]
        self.y = [1.0, 3.0, 5.0]
        self.z = [1.0, 4.0, 7.0]


@dataclass
class BinHalo(LoadHalo):
    r: float = 0.0 # or whatever is suitable

    def __post_init__(self) -> None:
        super().__post_init__()
        self.r = self.modulus(self.x, self.y, self.z)

    def modulus(self, *args: List[float]) -> float:
        """Modulus of vector of arbitrary size."""
        return sum([i ** 2 for containers in args for i in containers]) ** .5
person juanpa.arrivillaga    schedule 03.12.2019
comment
Очень понятный рабочий пример. Как раз то, что я искал. Спасибо @juanpa.arrivillaga - person Gustav Rasmussen; 03.12.2019
comment
@GustavRasmussen не беспокойтесь, вам следует подумать об использовании mypy для проверки кода. - person juanpa.arrivillaga; 03.12.2019