Python Data Classes-Notes
Python Data Classes-Notes
1 Introduction
Data classes in Python simplify class definitions by automatically generating special methods. This document covers the syntax and
features of data classes, with a focus on finance-related examples and concepts such as encapsulation, inheritance, and polymorphism.
2.1 Syntax
1 from dataclasses import dataclass
2
3 @dataclass
4 class ClassName:
5 attribute1: type
6 attribute2: type
2.1.1 Analogy
Think of data classes as a template for a form you fill out in a job application. The form has fields for your name, age, and address,
just as a data class has fields for its attributes. The @dataclass decorator is like having a pre-filled form that automatically handles
your personal details (__init__), displaying them (__repr__), and checking if two forms are the same (__eq__).
2.1.2 Tip
To remember the syntax, think of it as filling out a simple template:
• Import the dataclass decorator.
• Use @dataclass above your class definition.
1
3.1 Data Class Definition
1 from dataclasses import dataclass
2
3 @dataclass
4 class Bond:
5 face_value: float
6 coupon_rate: float
7 years_to_maturity: int
8 market_rate: float
9
10 def price(self) -> float:
11 coupon_payment = self.face_value * self.coupon_rate
12 present_value_coupons = sum(
13 coupon_payment / (1 + self.market_rate) ** t
14 for t in range(1, self.years_to_maturity + 1)
15 )
16 present_value_face_value = self.face_value / (1 + self.market_rate) ** self.years_to_maturity
17 return present_value_coupons + present_value_face_value
4.1.1 Analogy
Encapsulation in data classes is like locking a file drawer. Once it’s locked (frozen=True), no one can change the contents inside.
This ensures that the data remains consistent and unaltered.
4.1.2 Tip
Use frozen=True when you want to ensure the integrity of your data by preventing modifications after creation.
4.2 Inheritance
Data classes can inherit from other data classes, allowing for extension and customization of class behavior.
1 @dataclass
2 class Bond:
3 face_value: float
4 coupon_rate: float
5 years_to_maturity: int
6 market_rate: float
7
8 @dataclass
9 class CallableBond(Bond):
10 call_price: float
11 call_date: int
12
13 def call_value(self) -> float:
14 return min(self.price(), self.call_price)
2
4.2.1 Analogy
Inheritance in data classes is like a specialized job title that extends a general job role. For example, a ”Manager” can be extended
to a ”Senior Manager,” who has all the responsibilities of a Manager plus additional ones. Similarly, CallableBond extends Bond
with extra features.
4.2.2 Tip
When designing classes with inheritance, keep the base class focused on common features and use derived classes for specialized
behaviors.
• Extend functionality
4.3 Polymorphism
Data classes support polymorphism, where a method in a base class can be overridden in a derived class.
1 @dataclass
2 class Bond:
3 face_value: float
4 coupon_rate: float
5 years_to_maturity: int
6 market_rate: float
7
8 def price(self) -> float:
9 # Base implementation
10 pass
11
12 @dataclass
13 class ZeroCouponBond(Bond):
14 def price(self) -> float:
15 return self.face_value / (1 + self.market_rate) ** self.years_to_maturity
4.3.1 Analogy
Polymorphism is like having different ways to calculate a bonus in different departments. Each department (derived class) has its
own method to compute bonuses, even though they all follow the same general principle.
4.3.2 Tip
Use polymorphism to handle variations in method behavior while keeping a consistent interface. This is especially useful when
dealing with different types of objects that share common functionality.
3
5 Best Practices
• Use data classes for simple data containers.
• Avoid complex logic in data classes; use methods or separate classes for that.
• Use inheritance to extend functionality but avoid deep inheritance hierarchies.
• Employ encapsulation to maintain data integrity.
4
59 def normal_cdf(x: float) -> float:
60 """Helper method to calculate the cumulative distribution function of the standard normal distribution."""
61 return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0
62
63 @dataclass
64 class EuropeanPutOption(Option):
65 """
66 European Put Option class, inheriting from Option.
67 Further demonstrates polymorphism with a different implementation of calculate_value.
68 """
69 def calculate_value(self) -> float:
70 """
71 Calculates the put option value using put-call parity.
72 Another example of polymorphism.
73 """
74 call_option = EuropeanCallOption(
75 self.symbol, self.current_price, self.strike_price,
76 self.time_to_expiry, self.risk_free_rate, self.volatility
77 )
78 call_value = call_option.calculate_value()
79 put_value = call_value + self.strike_price * math.exp(-self.risk_free_rate * self.time_to_expiry) -
,→ self.current_price
80 return put_value
81
82 @dataclass(frozen=True)
83 class OptionPortfolio:
84 """
85 A class to represent a portfolio of options.
86 Demonstrates the use of a frozen dataclass for immutability.
87 """
88 options: tuple[Option, ...]
89
90 def total_value(self) -> float:
91 """Calculates the total value of the portfolio."""
92 return sum(option.calculate_value() for option in self.options)
93
94 # Example usage
95 if __name__ == "__main__":
96 # Creating instances of options
97 call_option = EuropeanCallOption("AAPL", 100, 110, 1, 0.05, 0.2)
98 put_option = EuropeanPutOption("AAPL", 100, 90, 1, 0.05, 0.2)
99
100 # Demonstrating encapsulation
101 call_option.set_implied_volatility(0.22)
102
103 # Calculating and printing option values (demonstrating polymorphism)
104 print(f"Call Option Value: { call_option.calculate_value():.2f}")
105 print(f"Put Option Value: { put_option.calculate_value():.2f}")
106
107 # Creating an immutable portfolio
108 portfolio = OptionPortfolio((call_option, put_option))
109 print(f"Total Portfolio Value: { portfolio.total_value():.2f}")
110
111 # Attempting to modify the frozen OptionPortfolio (this will raise an error)
112 try:
113 portfolio.options = (call_option,)
114 except AttributeError as e:
115 print(f"Error: { e}")
6.2.2 Encapsulation
Encapsulation is shown in the Option class:
• The implied_volatility attribute is initialized with a default value and not settable through the constructor.
• A setter method set_implied_volatility is provided to control access to this attribute.
5
6.2.3 Polymorphism
Polymorphism is demonstrated through the calculate_value method:
• It’s an abstract method in FinancialInstrument.
• EuropeanCallOption implements it using the Black-Scholes formula.
6.2.4 Immutability
The OptionPortfolio class is defined as immutable:
• It uses @dataclass(frozen=True) to prevent modifications after creation.
• Attempts to modify the portfolio will raise an AttributeError.
• Immutability: The frozen=True parameter allows for creating immutable data structures.
6.4 Conclusion
This example demonstrates how data classes can be effectively used in financial modeling, providing a clean and intuitive way to
represent complex financial instruments and their behaviors.
• Thank You !!