classDiagram class my_ns my_ns : my_data_1 my_ns : my_data_2 my_ns : my_data_3 my_ns : some_function()
The Class on Classes
What is object oriented programming?
Start with a dictionary
Now switch it for a simple name space
Why not attach a function too?
classDiagram class my_ns my_ns : my_data_1 my_ns : my_data_2 my_ns : my_data_3 my_ns : some_function()
classDiagram class Class Class : class_data = 12 class instance instance : instance_data=42 instance --> Class
classDiagram class Class1 Class1 : class_1_data = 12 class instance instance : instance_data=42 class Class2 Class2 : class_2_data = 13 instance --> Class1 Class1 --> Class2
classDiagram class Class1 Class1 : class_1_data = 12 class instance_1 instance_1 : instance_data=42 class instance_2 instance_2 : instance_data= 30 class Class2 Class2 : class_2_data = 13 instance_1 --> Class1 instance_2 --> Class1 Class1 --> Class2
classDiagram class Class1 Class1 : data = 42 class instance instance : data = 42 class Class2 Class2 : data = 13 instance --> Class1 Class1 --> Class2
classDiagram class Class1 Class1 : class_data = 10 Class1 : some_func(param1, param2) class instance instance : data = 42 instance --> Class1
Instance methods created from class functions
classDiagram class Class1 Class1 : class_data = 10 Class1 : some_func(param1, param2) class instance instance : data = 42 instance --> Class1
Dog()
)from pathlib import Path
class Dog:
def __init__(self, name):
self.name = name
@classmethod
def from_file(cls, path):
with Path(path).open('r') as fi:
name = fi.read()
return cls(name=name)
class Dog:
def __init__(self, name):
self.name = name
@staticmethod
def add(arg1, arg2):
return arg1 + arg2
Dunders
Names with double leading and trailing underscores. Many are part of the python language.
pyfile.py
Python has an object creation step:
__new__
Which creates an empty object (sets up space for data)
and initialization step:
__init__
Which fills in data
Usually, avoid __new__
get_
/set_
.get_
/set_
methods
getter and setter
Inheritance is used to extend or modify classes
super
lets you access the immediate parent class’ namespace
Python supports multiple inheritance, which can get confusing
MRO (__mro__
) defines lookups (instance) -> (class) -> (parent) -> (grandparent) -> …
super
is used to skip the current class in the mro.
You can check the MRO via the __mro__
dunder class attribute.
(<class 'pandas.core.frame.DataFrame'>, <class 'pandas.core.generic.NDFrame'>, <class 'pandas.core.base.PandasObject'>, <class 'pandas.core.accessor.DirNamesMixin'>, <class 'pandas.core.indexing.IndexingMixin'>, <class 'pandas.core.arraylike.OpsMixin'>, <class 'object'>)
Checking class relations is done with isinstance
and issubclass
the dataclasses module makes class definitions more ergonomic.
It can:
__init__
, __equal__
, order dunders, __hash__
The dataclasses module (of the standard library) was inspired by attrs. It also serves similar purposes as pydantic but does not perform any runtime type checking. If you want typehints to be enforced, use pydantic.
Traditional class definition
Equivalent dataclass
Protocols (sometimes called interfaces):
Common Protocols (most found in types or typing module):
- Sequence
- Mapping
- Collection
- Hashable
- Reversible
- Sized
- Iterable
- ...
A class which implements the Sized protocol
A class which implements the Hashable protocol
Defining protocols
abc
module and typing.Protocol
isinstance
checks required.This is a great article on the difference between ABC and typing.Protocol. See ABC module and typing.Protocol docs for more details.
ABC example
ABC use
from random import choice, sample
class MyDeck(DeckABC):
"""A deck with cards 1-10"""
def __init__(self, cards=None):
if cards is None:
cards = list(range(1,11))
self.cards = cards
def shuffle(self):
cards = sample(self.cards, len(self.cards))
return MyDeck(cards)
def draw(self):
card = choice(self.cards)
self.cards.pop(card)
return card
mydeck = MyDeck()
typing.Protocol example
typing.Protocol use
from typing import Self
from random import choice, sample
class MyDeck:
"""A deck with cards 1-10"""
def __init__(self, cards=None):
if cards is None:
cards = list(range(1,11))
self.cards = cards
def shuffle(self) -> Self:
cards = sample(self.cards, len(self.cards))
return MyDeck(cards)
def draw(self) -> str:
card = choice(self.cards)
self.cards.pop(card)
return card
deck = MyDeck()
assert isinstance(deck, DeckProtocol)
Possible Advantages
Possible Disadvantages
Others?
A few well-loved python classes:
before pathlib
With pathlib
A few dreaded python OO libraries:
Most are wrappers around languages with less flexible type systems or overly dynamic.