Как перезаписать атрибуты в наследовании ООП с помощью класса данных Python

#python #oop #python-dataclasses

Вопрос:

В настоящее время у меня есть код, который выглядит примерно так, с удалением нерелевантных методов.

 import math
import numpy as np
from decimal import Decimal
from dataclasses import dataclass, field
from typing import Optional, List

@dataclass
class A:
    S0: int
    K: int
    r: float = 0.05
    T: int = 1
    N: int = 2
    StockTrees: List[float] = field(init=False, default_factory=list)
    pu: Optional[float] = 0
    pd: Optional[float] = 0
    div: Optional[float] = 0
    sigma: Optional[float] = 0
    is_put: Optional[bool] = field(default=False)
    is_american: Optional[bool] = field(default=False)
    is_call: Optional[bool] = field(init=False)
    is_european: Optional[bool] = field(init=False)
    
    def __post_init__(self):
        self.is_call = not self.is_put
        self.is_european = not self.is_american
        
    @property
    def dt(self):
        return self.T/float(self.N)
    
    @property
    def df(self):
        return math.exp(-(self.r - self.div) * self.dt)

@dataclass
class B(A):

    u: float = field(init=False)
    d: float = field(init=False)
    qu: float = field(init=False)
    qd: float = field(init=False)
    
    def __post_init__(self):
        super().__post_init__()
        self.u = 1   self.pu
        self.d = 1 - self.pd
        self.qu = (math.exp((self.r - self.div) * self.dt) - self.d)/(self.u - self.d)
        self.qd = 1 - self.qu
    
    
@dataclass
class C(B):
    def __post_init__(self):
        super().__post_init__()
        self.u = math.exp(self.sigma * math.sqrt(self.dt))
        self.d = 1/self.u
        self.qu = (math.exp((self.r - self.div)*self.dt) - self.d)/(self.u - self.d)
        self.qd = 1 - self.qu

 

В принципе, у меня есть класс A , в котором он определяет некоторые атрибуты, которыми будут делиться все его дочерние классы, поэтому на самом деле он предназначен только для инициализации с помощью создания экземпляров его дочерних классов, а его атрибуты должны наследоваться его дочерними классами. Дочерний класс B предназначен для процесса, который выполняет некоторые вычисления, которые наследуются, с помощью C которых выполняется вариация одного и того же вычисления. C в основном наследует все методы, B и его единственное отличие заключается в том, что его расчет self.u и self.d разные.

Можно запустить код либо с помощью B вычисления , для которого требуются аргументы pu , pd либо C с помощью вычисления, для которого требуются аргументы sigma , как показано ниже

 if __name__ == "__main__":
    
    am_option = B(50, 52, r=0.05, T=2, N=2, pu=0.2, pd=0.2, is_put=True, is_american=True)
    print(f"{am_option.sigma = }")
    print(f"{am_option.pu = }")
    print(f"{am_option.pd = }")
    print(f"{am_option.qu = }")
    print(f"{am_option.qd = }")
    
    eu_option2 = C(50, 52, r=0.05, T=2, N=2, sigma=0.3, is_put=True)
    print(f"{am_option.sigma = }")
    print(f"{am_option.pu = }")
    print(f"{am_option.pd = }")
    print(f"{am_option.qu = }")
    print(f"{am_option.qd = }")
 

что дает результат

 am_option.pu = 0.2
am_option.pd = 0.2
am_option.qu = 0.6281777409400603
am_option.qd = 0.3718222590599397
Traceback (most recent call last):
  File "/home/dazza/option_pricer/test.py", line 136, in <module>
    eu_option2 = C(50, 52, r=0.05, T=2, N=2, sigma=0.3, is_put=True)
  File "<string>", line 15, in __init__
  File "/home/dazza/option_pricer/test.py", line 109, in __post_init__
    super().__post_init__()
  File "/home/dazza/option_pricer/test.py", line 55, in __post_init__
    self.qu = (math.exp((self.r - self.div) * self.dt) - self.d)/(self.u - self.d)
ZeroDivisionError: float division by zero
 

Таким образом, создание экземпляра B работает нормально, так как оно успешно рассчитало значения pu , pd , qu и qd . Однако моя проблема возникает, когда экземпляр C не может вычислить qu с тех пор pu и pd по умолчанию равен нулю, что заставляет его делиться на 0.

Мой вопрос: Как я могу исправить это так , чтобы C наследовались все атрибуты инициализации (в том числе __post_init__ ) A и все методы B , и в то же время выполнялись ее вычисления self.u = math.exp(self.sigma * math.sqrt(self.dt)) и self.d = 1/self.u перезапись self.u = 1 self.pu self.d = 1 - self.pd B , а также сохранение self.qu и self.qd то же самое?(они одинаковы для B и C )

Комментарии:

1. Вы действительно хотели бы позвонить super().__post_init__() в класс C, если расчет отличается от расчета в классе B?

2. Какова была бы здесь альтернатива? A().__post_init__() Вместо этого звонишь? Если я это сделаю, я получу A().__post_init__() TypeError: __init__() missing 2 required positional arguments: 'S0' and 'K' , и я не знаю, куда идти дальше? Я все еще учусь разбираться в ООП.

3. Когда вы создаете экземпляр C, вы не получаете новый экземпляр B и A — просто у C также есть атрибуты B и A. Поэтому нет способа перезаписать себя. ку на Б, потому что этого не существует. сам. qu и self.qd находятся на C, и когда вы запускаете метод, унаследованный от B, он использует атрибуты вашего экземпляра C.

4. @TonySuffolk66 Создание экземпляра C унаследовало бы атрибуты от A и B , включая u и d B , верно?. Разве нет способа написать его так, чтобы создание экземпляра C имело свое u значение и d заменяло унаследованное u и d от B ?

5. Можете ли вы установить свои атрибуты, не связанные с инициализацией, независимо от других значений? Я задаюсь вопросом, должны ли какие- либо из них быть атрибутами экземпляра, а не свойствами (только для чтения).

Ответ №1:

Определите другой метод для инициализации u и d , чтобы вы могли переопределить эту часть B , не переопределяя, как qu и qd определены.

 @dataclass
class B(A):

    u: float = field(init=False)
    d: float = field(init=False)
    qu: float = field(init=False)
    qd: float = field(init=False)
    
    def __post_init__(self):
        super().__post_init__()
        self._define_u_and_d()
        self.qu = (math.exp((self.r - self.div) * self.dt) - self.d)/(self.u - self.d)
        self.qd = 1 - self.qu

    def _define_u_and_d(self):
        self.u = 1   self.pu
        self.d = 1 - self.pd



@dataclass
class C(B):
    def _define_u_and_d(self):
        self.u = math.exp(self.sigma * math.sqrt(self.dt))
        self.d = 1/self.u
 

Комментарии:

1. Это почти похоже на то, что мне нужно. Однако я не думаю, что C это наследуется self.is_call от __post_init__ of A , так как в моем фактическом коде, где self.is_call используется один из методов B (который наследуется C ), говорится AttributeError: 'C' object has no attribute 'is_call'

2. Невозможно диагностировать, учитывая код, который вы показали. C наследуется __post_init__ от B , который вызывает A.__post_init__ через super .

Ответ №2:

Python поддерживает множественное наследование. Вы можете наследовать от A предыдущих B , что означает, что любые перекрывающиеся методы будут взяты из A (например __post_init__ ). Любой код, который вы напишете в классе C , перезапишет то, что унаследовано от A и B . Если вам нужно больше контролировать, какие методы исходят из какого класса, вы всегда можете определить метод C и выполнить вызов функции A или B (например A.dt(self) ).

 class C(A, B):
    ...
 

ЕЩЕ ОДНА ПРАВКА:
Я только что видел, что A это инициализирует некоторые вещи, которые вы хотите C включить . Поскольку C родитель теперь A (если вы использовали мой код выше), вы можете добавить обратно в super().__post_init__() строку C «s __post_init__ «, чтобы он вызывал A «s __post_init__ «.
Если это не сработает, вы всегда можете просто вставить A.__post_init__(self) __post_init__ «о C «.

Комментарии:

1. Деление на 0 не должно быть проблемой , если C бы можно было перезаписать self.u и self.d B , поэтому я спрашиваю, есть ли способ это сделать.

2. Если B уже наследует от A, то, честно говоря, делать так, чтобы C также наследовал напрямую от A, кажется бессмысленным.

3. Спасибо 🙂 . Это, однако, сейчас дает AttributeError: 'C' object has no attribute 'is_call' . Я думаю, что проблема здесь в том, что is_call определено в __post_init__ A том, что как-то не инициировано. Должно ли это быть?

4. Также is_call используется в одном из методов B , но я просто не описал методы выше.

5. Я C унаследовал от A , потому что они хотят получить все , от всех методов A , но также и некоторые методы B , поэтому A их нужно сначала унаследовать, чтобы B не переопределять их C .