Classes in python, the nuances
satya - 2/10/2024, 12:48:34 PM
Example of an abstract class
#
# abc is a module in python
# abstract base class (abc)
#
from abc import ABC, abstractmethod
#
# use an annotation
#
class MyBaseClass(ABC):
@abstractmethod
def my_abstract_method(self):
pass
#
# Implement it
# Type error if not implemented
#
class MySubclass(MyBaseClass):
def my_abstract_method(self):
# Implement the abstract method
print("Implemented abstract method in MySubclass")
#
# Call it
#
obj = MySubclass()
obj.my_abstract_method()
satya - 2/10/2024, 1:08:32 PM
Initializing class variables in python
import threading
def initialize_class_variables(cls):
if not hasattr(cls, 'class_variable'):
cls.class_variable = 10
return cls
@initialize_class_variables
class MyClass:
class_variable = None
def __init__(self):
pass
# Example usage
obj = MyClass()
print(obj.class_variable) # Output: 10
satya - 2/10/2024, 1:10:57 PM
Some essentials of class variables
satya - 2/10/2024, 1:16:15 PM
A chatgpt prompt for a sample python class
satya - 2/10/2024, 1:19:44 PM
Sample simple python class
class MyBaseClass:
name: str
age: int
def __init__(self, name: str, age: int):
self.name = name
self.age = age
# Private method (starts with _)
def _private_method(self):
print("This is a private method")
def public_method(self):
print("This is a public method")
# How to derive
class MyDerivedClass(MyBaseClass):
gender: str
def __init__(self, name: str, age: int, gender: str):
super().__init__(name, age)
self.gender = gender
def _private_method(self):
super()._private_method()
print("This is a private method in MyDerivedClass")
def public_method(self):
super().public_method()
print("This is a public method in MyDerivedClass")
#call private method. No self is needed when calling
self._private_method()
base_obj = MyBaseClass("John", 30)
derived_obj = MyDerivedClass("Alice", 25, "Female")
satya - 2/10/2024, 1:20:04 PM
Remember same named methods will be overridden.
Remember same named methods will be overridden.
satya - 2/10/2024, 1:23:28 PM
Strangeness of gtters and setters in python
class MyClass:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@name.deleter
def name(self):
del self._name
# Create an instance of MyClass
obj = MyClass("John")
# Access the name attribute using the getter method
print(obj.name) # Output: John
# Assign a new value to the name attribute using the setter method
obj.name = "Alice"
print(obj.name) # Output: Alice
# Delete the name attribute using the deleter method
del obj.name
# Accessing the attribute after deletion will raise an AttributeError
satya - 2/10/2024, 1:25:19 PM
You invoke the methods as
satya - 2/10/2024, 2:21:08 PM
Importance of "self"
satya - 2/14/2024, 5:52:49 PM
Abstract class with a pair return
from abc import ABC, abstractmethod
class Wizard(ABC):
@abstractmethod
def question(self):
"""
Abstract method that should be implemented by subclasses.
Returns:
tuple: A pair (question, answer).
"""
pass
# Example subclass implementing the abstract method
class ConcreteWizard(Wizard):
def question(self):
# Implementation of the abstract method
return ("What is your quest?", "To seek the Holy Grail.")
# Usage
wizard = ConcreteWizard()
q, a = wizard.question()
print(f"Question: {q}\nAnswer: {a}")
satya - 2/17/2024, 8:38:55 AM
Nuances of multiple inheritance: duplicate methods are allowed
class Base1:
def method(self):
print("Method of Base1")
class Base2:
def method(self):
print("Method of Base2")
class Derived(Base1, Base2):
def another_method(self):
print("Another method of Derived")
# Creating an instance of Derived
d = Derived()
# Calling methods: surprise
d.method()
# This will call the method from Base1,
due to the order in the inheritance list
d.another_method() # Calls method defined in Derived
satya - 2/17/2024, 8:50:42 AM
This material here is made into a formal article
satya - 2/17/2024, 8:51:53 AM
Context manager as a class (with)
class ManagedFile:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, 'r')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
# Usage
with ManagedFile('example.txt') as f:
content = f.read()
print(content)
satya - 2/17/2024, 8:52:16 AM
Context manager as a method
from contextlib import contextmanager
@contextmanager
def managed_file(filename):
try:
f = open(filename, 'r')
yield f
finally:
f.close()
# Usage
with managed_file('example.txt') as f:
content = f.read()
print(content)
satya - 2/17/2024, 8:53:04 AM
repr() and str() methods
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __repr__(self):
return f"Product({self.name!r}, {self.price!r})"
def __str__(self):
return f"{self.name} - ${self.price}"
# Creating an instance of Product
product = Product("Coffee Mug", 12.99)
# __repr__ is used when echoing in a console or using repr()
print(repr(product)) # Output: Product('Coffee Mug', 12.99)
# __str__ is used when printing or using str()
print(product) # Output: Coffee Mug - $12.99
satya - 2/17/2024, 8:53:30 AM
new method
class Singleton:
_instance = None # Keep instance reference
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
# Test the Singleton class
obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2) # Output: True
satya - 2/17/2024, 8:55:30 AM
Python special dunder methods
satya - 2/17/2024, 8:57:00 AM
Underscore behavior
satya - 2/17/2024, 8:57:32 AM
__call__ method as a functor
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return self.factor * x
# Create an instance of Multiplier that doubles the input
doubler = Multiplier(2)
# Use the instance as if it were a function
print(doubler(5)) # Output: 10
print(doubler(10)) # Output: 20
satya - 2/25/2024, 4:38:18 PM
Role of a class decorator function
satya - 2/25/2024, 4:41:40 PM
More on this singleton decorator
satya - 2/25/2024, 5:05:39 PM
An example of a decorartor
def add_greeting_method(cls):
# Define a new method
def greet(self):
print(f"Hello, my name is {self.name}.")
# Add the method to the class
cls.greet = greet
return cls
@add_greeting_method
class Person:
def __init__(self, name):
self.name = name
# Usage
person = Person("Alice")
person.greet() # Output: Hello, my name is Alice.
satya - 2/25/2024, 5:05:54 PM
There is also an example at the begining of this article
There is also an example at the begining of this article
satya - 2/25/2024, 5:08:15 PM
a better factory example
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Singleton:
pass
# Usage
singleton1 = Singleton()
singleton2 = Singleton()
assert singleton1 is singleton2 # True
satya - 2/25/2024, 5:29:22 PM
Here is another example
"""
*************************************************
* baselib related imports
*************************************************
"""
from baselib import baselog as log
from config.appconfig import AppConfig
from config import appconfig as appconfig
def _getAppConfigObject():
log.info("Reading Application configuration file")
return appconfig.readTOMLConfigFile()
def initAppServices(cls):
log.ph1("Initializing App Services")
cls.class_app_config = _getAppConfigObject()
return cls
@initAppServices
class AppServices:
class_app_config: AppConfig
@staticmethod
def config():
return AppServices.class_app_config
"""
*************************************************
* Testing
*************************************************
"""
def _testConfig1():
token = AppServices.config().api_token_name
log.ph("Token from config", token)
def _testConfig2():
a = AppServices.config().embedding_api
log.ph("embedding api", a)
def test():
_testConfig1()
_testConfig2()
def localTest():
log.ph1("Starting local test")
test()
log.ph1("End local test")
if __name__ == '__main__':
localTest()
satya - 2/25/2024, 5:35:00 PM
Know this: The order of functions
satya - 2/25/2024, 5:35:33 PM
However the following order will be a problem
test() #This will fail
def test():
log.info("test")
test() #This will succeed
satya - 3/2/2024, 5:20:12 PM
Ouch, the confusion of me, with class variables
satya - 4/11/2024, 3:07:16 PM
@dataclass decoration: example
from dataclasses import dataclass
@dataclass
class InventoryItem:
name: str
unit_price: float
quantity_on_hand: int = 0
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
# Example usage
item = InventoryItem('widget', unit_price=3.5, quantity_on_hand=20)
print(item) # Output will be nicely formatted thanks to the auto-generated __repr__
print(item.total_cost()) # Outputs: 70.0
satya - 4/11/2024, 3:10:55 PM
Essentials