Table of contents
- The backstory
- A sample class
- Python compatiblity (The past and the present)
- Metaclass (`PortfolioMeta`)
- Abstract Base Class (`Investment`)
- `@total_ordering` (Automatic Comparisons)
- Private (`_, __`) Attributes
- Class Method (`@classmethod`)
- Static Method (`@staticmethod`)
- `@property` Decorator
- Magic Methods (`dunder methods`)
The backstory
Basic and Perl were the first programming languages I tried then later on landed on Python, this was somewhere in 2015. There was no clear winner in the Python wen framework landscape back then , maybe Django?
Anyways I found myself driffting more and more towards PHP as I built more and more web app. and I didn't need a web framework going into it ( I eventually did).
This caused me to slowly move away from Python and as they say "use it or loose it". So I forgot most of what I knew
But in the next couple of months I will be working on a project where I will be crunching a lot of numbers and the final deliverable is a report , the tool I will use and how I end up with that report means very little to the client. This makes it a good time to get back to Python :).
With multithreading baked-in, simple syntax and douzens of number crunching libraries and no need to serve up my results over the web, Python is the best man... or woman for the this job.
I will documenting some of my brush ups in this Python series.
A sample class
Python classes are not too different from what you get with other programming languages. I won't be documenting things such as what polymorphism is as I am already familiar with that concept and can just google "How to do X in Python", my goal is to capture and document Python-specific features that I can use to enhance my work.
01: from __future__ import annotations # Enables return type hints for the same class
02: from decimal import Decimal
03: from functools import total_ordering
04: from abc import ABC, abstractmethod
05: import json
06: import uuid
07:
08: class PortfolioMeta(type):
09: def __new__(cls, name, bases, class_dict):
10: if 'calculate_net_worth' not in class_dict:
11: raise TypeError(f"Class {name} must define calculate_net_worth() method")
12: return super().__new__(cls, name, bases, class_dict)
13:
14: # Abstract Base Class for Investments
15: class Investment(ABC):
16: def __init__(self, symbol: str, quantity: int, price: Decimal):
17: self.symbol = symbol
18: self.quantity = quantity
19: self.price = price
20:
21: @abstractmethod
22: def get_value(self) -> Decimal:
23: pass
24:
25: # Stock Class
26: @total_ordering # Enables <, >, <=, >=, == by defining only __eq__ and one comparison
27: class Stock(Investment):
28: def __init__(self, symbol: str, quantity: int, price: Decimal):
29: super().__init__(symbol, quantity, price)
30:
31: def get_value(self) -> Decimal:
32: return self.quantity * self.price
33:
34: def __eq__(self, other: Stock) -> bool:
35: return self.get_value() == other.get_value()
36:
37: def __lt__(self, other: Stock) -> bool:
38: return self.get_value() < other.get_value()
39:
40: # Portfolio Class
41: class Portfolio(metaclass=PortfolioMeta): # Uses a metaclass
42: _transaction_fee: Decimal = Decimal("0.01") # Private class-level attribute
43:
44: def __init__(self, owner: str):
45: self.owner = owner
46: self._investments = [] # Private list to store investments
47: self.__id = uuid.uuid4() # Private, name-mangled attribute
48:
49: def add_investment(self, investment: Investment) -> None:
50: """Adds an investment to the portfolio"""
51: self._investments.append(investment)
52:
53: def calculate_net_worth(self) -> Decimal:
54: """Calculates the net worth of the portfolio"""
55: return sum(inv.get_value() for inv in self._investments)
56:
57: @property
58: def id(self) -> str:
59: """Getter for ID (name mangled)"""
60: return str(self.__id)
61:
62: @classmethod
63: def set_transaction_fee(cls, fee: Decimal) -> None:
64: """Class method to change transaction fees"""
65: cls._transaction_fee = fee
66:
67: @staticmethod
68: def format_currency(amount: Decimal) -> str:
69: """Formats a Decimal amount into a currency string"""
70: return f"${amount:,.2f}"
71:
72: def __len__(self) -> int:
73: """Returns number of investments"""
74: return len(self._investments)
75:
76: def __getitem__(self, index: int) -> Investment:
77: """Allows index-based access"""
78: return self._investments[index]
79:
80: def __iter__(self):
81: """Makes the class iterable"""
82: return iter(self._investments)
83:
84: def __repr__(self) -> str:
85: return f"Portfolio(owner={self.owner}, net_worth={self.calculate_net_worth()})"
86:
87: def __enter__(self) -> Portfolio:
88: """Enables use as a context manager"""
89: print(f"Opening portfolio for {self.owner}")
90: return self
91:
92: def __exit__(self, exc_type, exc_value, traceback) -> None:
93: """Cleans up resources when context ends"""
94: print(f"Closing portfolio for {self.owner}")
95:
96: def to_json(self) -> str:
97: """Serializes portfolio data into JSON format"""
98: return json.dumps(
99: {
100: "owner": self.owner,
101: "net_worth": str(self.calculate_net_worth()),
102: "investments": [
103: {"symbol": inv.symbol, "quantity": inv.quantity, "value": str(inv.get_value())}
104: for inv in self._investments
105: ],
106: },
107: indent=2
108: )
109:
110: # Usage Example
111: portfolio = Portfolio("Alice")
112: portfolio.add_investment(Stock("AAPL", 10, Decimal("145.30")))
113: portfolio.add_investment(Stock("GOOGL", 5, Decimal("2800.50")))
114:
115: print(portfolio) # Calls __repr__()
116: print(portfolio.calculate_net_worth()) # Calculates portfolio value
117: print(portfolio.to_json()) # Serializes to JSON
118:
119: with portfolio: # Uses context manager
120: print("Managing investments...")
Python compatiblity (The past and the present)
As I already shared in my backstory, I used Python a long time ago so that would be Python 2 days. And now I am coming to Python 3.
Lets start with __future__
Metaclass (PortfolioMeta
)
Abstract Base Class (Investment
)
@total_ordering
(Automatic Comparisons)
Private (_, __
) Attributes
Class Method (@classmethod
)
Static Method (@staticmethod
)
@property
Decorator
Magic Methods (dunder methods
)
Here is another article you might like 😊 How To Access A Windows Localhost App From WSL