Recap for advanced Python training from David Beazley
Back to 2022 August, I have attended one extensive training from Python expert David Beazley, I didn’t really pick up and review what I have learnt. Now sitting in the quiet coffee shop in Helsinki, I restructure this article. This was I wrote previously, I like words in a depth, which builds with curiosity, so I like use words when understanding the universe, for its the language with the creator. Maybe this is also the reason why I like programming, for it keeps me wonders the beauty of the maths, philosophy and logics. I still keep the wonder of this matter.
- Abstraction, this is the fundamental of the programming to practice you to think abstractly.
- Make use of Python built-in methods.
- magic method
- not very known methods (but useful)
- some tricks maybe
- fp –> functools/curry/monad/lambda
- Design layers
- Concurrency
- OOP one step more
I have divided these into 5 sub-lists, before starting write some key points. What I learnt is the experience from the teacher how he approaches given problem and what the thinking process to design the code base, and how to apply TDD in the whole development lifecycle. What one techniques I used later in the code interview is about “invariants”, which means the functionality you design will never cause this happen, so you need write the code to prevent this or add logic pre check before execute the operations in the normal standards.
Built in methods
In object lifecycle, it starts with __new__() when the object is created, then for inistance apply __init__(), which is the one we use universally often.
Python magic method, one of the recommended resources guide.
__repr__
__str__
__new__
__len__
__call__
more can refer to the guide.
class C:
def __init__(self, name, any_val):
self.name = name
self._any_val= any_val
def __repr__(self):
return f"C({self.name}, {self._any_val})" #good for logging/debugging
def __call__(self, val): #let an instance to be called as func
return self._any_val + val
def __len__(self):
return len(self._any_val)
if __name__ =="__main__":
c= C("chloe", "biker.")
c1= C("doesn't matter", 1)
print(c)
print(c1(2)) #should display 3;
print(len(c))
For this one __init_subclass__
, which supported by Python 3.8+. I never saw this one before, but it looks surprising neat.
After reading PEP487 and the course, below are the example by my understanding (from the course), for __init_subclass__
which will initializes all subclass of give class, upon class created, the __set_name__
hook is called on all the attributes defined in the class.
It starts with a classmethod.
Use case if for adapters for registering subclass or set default attributes value for the subclass.
class Message:
registry = { }
def __init_subclass__(cls, *a, **kw):
Message.registry[cls.__name__] = cls
def __init__(self, sequence):
self.sequence = sequence
def __eq__(self, other):
return type(self) == type(other) and vars(self) == vars(other)
class ChatMessage(Message):
def __init__(self, sequence, playerid, text):
super().__init__(sequence)
self.playerid = playerid
self.text = text
class PlayerUpdate(Message):
def __init__(self, sequence, playerid, x, y):
super().__init__(sequence)
self.playerid = playerid
self.x = x
self.y = y
if __name__ == "__main__":
sequence= 1
m= Message(sequence)
chat= ChatMessage(sequence, 1,2)
print(chat.text)
other= 2
print(chat==2)
How to use arguments, by using *, /
The usage to use this, one reason is for arguments interference, to avoid the keywords parameters has the same name from **kwargs kind of parameters.
# passing tuple as arguments value
def func(x, y, z):
pass
tuple_val= (1,2,3)
func(*tuple_val)
# or dict.
# if arguments has * in the any pos of func.
def func(*, x, y):
return x+y
func(x=1,y=2)
def func(x, *, y):
return x+ y
func(1, y=2) # a must.
func(x, y, /, z): # works on latest version of Python.
return x+y+z
func(1,2,3)
func(1,2,z=3)
Then FP. FP is maths style to write code and no side effect, good is Python is a language FP married with OOP. Also it provides functools
package which is useful to avoid “reinvent the wheel”. functools
is a useful package, you will use often when you need implement the customized decorator function.
#lambda can define a function, like a function def do.
#For function inside function will invoke earlier,
#which will cause issue. So either define a helper function to call this function,
#or use lambda/partial module from functools.
from functools import partial
x,y, z= 1,2,3
def sum_it(x, y, z):
return x+y+z
partial_sum= partial(sum_it, 1,2)
sum_again= partial_sum(3)
lambda_sum= lambda: sum_it(x, y,z)
assert sum_it(1,2,3) == sum_again == lambda_sum()
# monad, maybe I should skip this, for I totally forget Monad concept.
OOP.
Object oriented programming, as a programmer we all use it daily no matter which languages you use, for most of modern languages can use OOP design structure to implement. Object is the starting point, it starts with the class, then multi-classes, to say we have base class A, subclass or childclass class B, class C, so what’s the good way to implement this concept. Class is a way to define data, or define behaviors and programming interfaces, also it expressed one core tenets of OOP is the concept of “encapsulation”.
<1.> Inheritance: Define Interfaces: For the case that having multiple classes with identical functionalities, good practice is to group them into the top-level class that defines a programming interfaces; it brings a main benefit for error checking(missing operations) happens earlier.
from abc import ABC, abstractmethod
from dataclasses import dataclass
class A(ABC): #define an interface;
@abstractmethod
def func(self):
raise NotImplementedError()
@abstractmethod
def func2():
pass
class B(A):
def func(self):
print("todo implementation")
def func2(self):
print("todo implementation")
<2.> Composition, from principle “favor composition over inheritance”, for inheritance makes code tightly couple and not flexible.
class E:
pass
class D:
# def __init__(self, ele= None):
# if ele is None:
# ele= E
# self.ele= ele()
def __init__(self, ele:E= None):
if ele is None:
ele= E() # create E instance
assert isinstance(ele, E), "not the same type"
self.ele= ele
<3.> So composition is a good way to combine different method in a class but deign it in a flexible way, which will apply double composition
class H:
def __init__(self, val, val2):
self.val= val
self.val2= val2
class I:
def __init__(self, x, y):
self.x= x
self.y= y
class J:
def __init__(self, h, i):
self.h= h # H object
self.i= i # I object
def __repr__(self):
return f"h is {self.h} and i is {self.i}"
def func(self):
pass
<4.> How to construct a class not using init, but new() inside a class
class F:
def __init__(self, val, val2, val3= None):
self.val= val
self.val2= val2
if val is None:
...
self.val3= val3
@classmethod
def new_F(cls, val, val2, val3): #bypass a new
self= cls.__new__(cls) #create a class F without calling __init__
self.val= val
self.val2= val2
self.val3= val3
return self
if __name__== "__main__":
f= F(1,2,3)
new= f.new_F(20,30,40)
print(new.val) #display 20;
<5.> For testing purpose, write repr, I like this point very much, one benefit is easy for debug usage.
class G:
def __init__(self, name: str):
self.name= name
def __repr__(self):
return f'name is {self.name}'
if __name__== "__main__":
print(G("Chloe Ji"))
<6.> Multiple inheritance, concept for the “Mixin” and “MRO”. class.__mro__
(The example is from the course.)
class StackInterface:
pass
class NumericStack(StackInterface):
def __init__(self):
self._items = []
def push(self, item):
assert isinstance(item, (int, float)), "Number is required"
self._items.append(item)
class Stack(StackInterface):
def __init__(self):
self._items = [ ] # _variable meant to be "private" (to the outside)
def push(self, item):
self._items.append(item)
class NumericCheck:
def push(self, item):
assert isinstance(item, (int, float)), "Number is required"
super().push(item)
class NumericStack(NumericCheck, Stack):
pass
class NumericImmutableStack(NumericCheck, ImmutableStack):
pass
class DebugPush:
def push(self, item):
print("PUSH:", item)
super().push(item)
class MyStack(DebugPush, NumericCheck, Stack):
pass
<7.> Using dataclass as object value.
from dataclasses import dataclass
class M:
pass
@dataclass
class K(M):
val: str
val2: str
val3: int
@dataclass
class L(M):
x: int
y: int
def process_m(m: M):
assert isinstance(m, M), "m should be identical type as M" #type checking
if isinstance(m, K):
# do something
elif isinstance(m, L):
# do something
...
<8.> Property to use for redefine attributes access. Alternative way is using magic method
- obj.getattribute(name) # Implements: obj.name
- obj.setattr(name, value) # Implements: obj.name = value
class C:
def __init__(self, name, any_val):
self.name = name
self._any_val= any_val
@property
def _name(self):
return self.name
@_name.setter
def _name(self, new_name):
self.name= new_name
return self.name
c= C("chloe", 1)
print(c._name)
c._name= "chloejay"
print(c._name)
<9.> Other decorator method, the most used often one are @staticmethod
, the reason to use this is to group the same logic or similar methods in a same class, more for code organizational management.
In the end, I keep walking in this wonder space.