EDDYMENS

Published a month ago

Back To Python (series): A Quick Dive Into Classes

Table of contents

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