This repository contains some cool python code examples and explanations that you can learn some cool features beginner to expect with the python language.
Hello Word!!
class Vector:
def __init__(self, x, y, z) -> None:
self.x = x
self.y = y
self.z = z
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y, z=self.z + other.z)
def __repr__(self) -> str:
return "{cn}<x={x}, y={y}, z={z}>".format(
x=self.x, y=self.y, z=self.z, cn=self.__class__.__name__
)
v1 = Vector(2, 3, 4)
v2 = Vector(5, 6, 7)
res = v1 + v2
print(res)
Let's talk about how the argument and keyword arguments works and how to use them. Let's say we don't know what arguments are going to be passed to a function hi
defining a function signature as def hi(*args, **kwargs):
helps us to get the arguments and keyword args that were passed to the function hi
def hi(*args, **kwargs):
"""
args: will be a tuple of arguments passed to a function
kwargs: a dictionary of key word arguments passed to the function
"""
return args, kwargs
args, kwargs = hi("Jonh", 10, name="Jonh", age=10)
print(args)
print(kwargs)
Result:
('Jonh', 10)
{'name': 'Jonh', 'age': 10}
In python we can get the arguments from the terminal when running python script using the sys
module. Let's have a look at how we can get the arguments parsed when running a python script called main.py
import sys
args = sys.argv
print(args)
Running the command
python main.py name hello there
Will result in the following arguments being retrieved. ['main.py', 'name', 'hello', 'there']
Note that the first argument will be the script name that you run and anything after that in the list will be strings that you parsed after the file name. All the arguments that you parse here will be returned as strings separated by a space. If you want to escape a space you will need to use quotes
""
:
python main.py message "hello there"
Result will be:
['main.py', 'message', 'hello there']
What if we want to use some optional arguments. We can do it as follows
import sys
import getopt
# python main.py -p 3001 -h 127.0.0.1 -o https
opts, args = getopt.getopt(sys.argv[1:], "p:h:o:", ["port=", "host=", "protocol="])
print(opts, args)
Now if we run
python main.py -p 3001 -h 127.0.0.1 -o https
We will get the following optional arguments and args that were parsed when running the script
[('-p', '3001'), ('-h', '127.0.0.1'), ('-p', 'https')] []
Note that you can also type required arguments in the script and run the command using long
options names but for that you will need to use the --
instead of a single -
.
import sys
import getopt
# python main.py --port 3001 --host 127.0.0.1 --protocol wss message "hello world"
opts, args = getopt.getopt(sys.argv[1:], "p:h:pt:", ["port=", "host=", "protocol="])
print(dict(opts), args)
Running the command:
python main.py --port 3001 --host 127.0.0.1 --protocol wss message "hello world"
You will get the following result: shell
{'--port': '3001', '--host': '127.0.0.1', '--protocol': 'wss'} ['message', 'hello world']
In python we can create interfaces for classes and implement them. For that we need to use the abc
package that is built in in python. Let's create an IUser
interface that will contain a class method called hi
from abc import abstractmethod, ABCMeta
class IUser(metaclass=ABCMeta):
@classmethod
@abstractmethod
def hi(self, name: str) -> str:
"""This is hi menthod"""
IUser().hi("Bob")
If you try to do that you will get an error saying
TypeError: Can't instantiate abstract class IUser without an implementation for abstract method 'hi'
So let's use this interface to create a class called Student
that will inhherit from IUser
class Student(IUser):
pass
Student().hi("Bob")
If i try to do this i will get an error telling me that i haven't implemented hi
TypeError: Can't instantiate abstract class Student without an implementation for abstract method 'hi'
To solve this error we are going to change our code to:
class Student(IUser):
def __init__(self) -> None:
super().__init__()
def hi(self, name):
print("Hello {name}".format(name=name))
Student().hi("Bob")
Note that we can also define some property staticmethod
and also property
here is san example:
from abc import abstractmethod, ABCMeta
class IUser(metaclass=ABCMeta):
@classmethod
@abstractmethod
def hi(self, name: str) -> str:
"""This is hi menthod"""
@staticmethod
@abstractmethod
def programmer(name):
""""""
@property
@abstractmethod
def Name(self):
"""Name of the user"""
class Student(IUser):
def __init__(self, name: str) -> None:
super().__init__()
self.__name = name
def hi(self, name):
print("Hello {name}".format(name=name))
@property
def Name(self):
return self.__name
@staticmethod
def programmer(name):
return name
stud = Student(name="Julie")
stud.hi("Bob")
print(stud.Name)
print(stud.programmer("Namo"))
We can create different classes that inherits from our interface
and then create a Factory class UserFactory
that helps us to return a factory object of different objects based on the type passed.
from abc import abstractmethod, ABCMeta
class IUser(metaclass=ABCMeta):
@classmethod
@abstractmethod
def hi(self, name: str) -> str:
"""This is hi menthod"""
class Student(IUser):
def hi(self, name):
print("Student {name}".format(name=name))
class Manager(IUser):
def hi(self, name):
print("Manager {name}".format(name=name))
class Other(IUser):
def hi(self, name):
print("Other {name}".format(name=name))
class UserFactory:
@staticmethod
def get_user(type: str):
if type.lower() == "student":
return Student()
if type.lower() == "manager":
return Manager()
return Other()
UserFactory.get_user("student").hi("Bob")
UserFactory.get_user("manager").hi("Bob")
UserFactory.get_user("haha").hi("Bob")
This design pattern allows us to wrap functionality among other classes in python.
Let's say we have a class called person and this class has a static method to be implemented from IUser
called hi
. We can create a ProxyUser
class and also make it to inherit from IUser
from abc import abstractmethod, ABCMeta
class IUser(metaclass=ABCMeta):
@classmethod
@abstractmethod
def hi(self, name: str) -> str:
"""This is hi menthod"""
class Student(IUser):
def hi(self, name):
print("Student {name}".format(name=name))
class ProxyUser(IUser):
def __init__(self) -> None:
super().__init__()
self.student = Student()
def hi(self, name: str):
self.student.hi(name)
print(f"Hi I am a proxy class: {name}")
ProxyUser().hi("Bob")
With this flexibility we can call the student
object in the Proxy
class when we call the method hi
on ProxyUser
we also trigger the hi
method in the student class.
The idea behind this is that we only have 1 class and this class will only have 1
instance. So let's go ahead and implement thats.
from abc import abstractmethod, ABCMeta
class ISession(metaclass=ABCMeta):
@staticmethod
@abstractmethod
def get_session():
"""To be implemented"""
class Session:
__instance = None
@staticmethod
def get_instance():
if Session.__instance is None:
Session("localhost", 5432)
return Session.__instance
def __init__(self, host: str, port: int) -> None:
if Session.__instance is not None:
pass
# raise Exception(f"{self.__class__.__name__} can only be initialized once.")
else:
self.host = host
self.port = port
Session.__instance = self
@staticmethod
def get_session():
return {"port": Session.__instance.port, "host": Session.__instance.host}
Now we can create a single instance of a Session
class because of this logic. If we try to check the instance of session
and session1
they will be located in the same memory:
session1 = Session("localhost", 5432)
print(session1.get_session())
print(session1.get_instance())
session = Session("localhost", 5432)
print(session.get_session())
print(session.get_instance())
Output:
{'port': 5432, 'host': 'localhost'}
<__main__.Session object at 0x0000018C15D39FA0>
{'port': 5432, 'host': 'localhost'}
<__main__.Session object at 0x0000018C15D39FA0>
Let's say we have IDepartment
which is the base interface for our departments Testing
and Deploying
. Each department has a method of printing the department employees. Then the base department class Development
can print all the information about other departments including it's information. This is how it can be done.
from abc import abstractmethod, ABCMeta
class IDepartment(metaclass=ABCMeta):
@abstractmethod
def __init__(self, employees):
"""To be implemented"""
@staticmethod
@abstractmethod
def print_department():
""""""
class Testing(IDepartment):
def __init__(self, employees):
self.employees = employees
def print_department(self):
print(f"Testing: {self.employees}")
class Deploying(IDepartment):
def __init__(self, employees):
self.employees = employees
def print_department(self):
print(f"Development: {self.employees}")
class Development(IDepartment):
def __init__(self, employees):
self.employees = employees
self.deps = list()
self.all_employees = employees
def add_department(self, dep):
self.deps.append(dep)
self.all_employees += dep.employees
def print_department(self):
print(f"Department Employees: {self.all_employees}")
for dep in self.deps:
dep.print_department()
print(f"Total Employees: {self.employees}")
These are a cool python feature that we can use on our python class so that we won't define some __method__
as they will be defined for us once we decorate the class with the dataclass
decorator that comes from dataclasses
. Let's consider the following example where we have a class called Person
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: str
gender: str
person = Person("bob", 5, 7)
print(person) # Person(name='bob', age=5, gender=7)
When defining the person class we might want to make the person
object immutable for that we can specify the argument when decorating our class called frozen
:
@dataclass(frozen=True)
class Person:
name: str
age: str
gender: str
person = Person("bob", 5, 7)
person.age = 18 # this will throw an error
print(person)
We can also specify our dataclass Person
that it only takes in keyword
arguments by passing the following options.
@dataclass(frozen=True, kw_only=True)
class Person:
name: str
age: str
gender: str
person = Person(name="bob", age=5, gender="M") # only kwargs will be allowed
The good thing with dataclasses is that you can overide the default __method__
that are in the dataclass for example let's overide the __repr__()
method we can do it as follows:
@dataclass(frozen=True, kw_only=True)
class Person:
name: str
age: str
gender: str
def __repr__(self) -> str:
return "Person {}".format(self.name)
person = Person(name="bob", age=5, gender="M")
print(person) # Person bob
We can also set default values to dataclases let's have a look at the following example
from dataclasses import dataclass, field
import random
import string
def uuid():
return "".join(random.choices(string.ascii_lowercase, k=5))
@dataclass(frozen=False, kw_only=True, slots=True)
class Person:
name: str
age: str
gender: str
id: str = field(default_factory=uuid, init=False)
online: bool = True
colors: list[str] = field(
default_factory=list,
)
_search_str: str = field(init=False, repr=False)
def __post_init__(self):
self._search_str = f"{self.name}%20{self.age}"
person = Person(name="bob", age=5, gender="M")
print(person)
Now we have introduced another function called field
. It takes in some number of arguments, for the default value of id
we want to generate it using a function called uuid
and for that we need to pass in an argument called default_factory
, if we don't want to see a property in the dander
method we can set repr
to False. We have a dander
method __post_init__
which allows us to post initilize the _search_str
when we create an instance of a Person
. You can pass a lot of arguments in this field
method.
In this section we are going to have a look at how we can work with generics in python
. Let's say we have a function that returns a first element in an array and we want to make this function generic. This is how we can achieve that befor and after python 3.12
Before:
from typing import TypeVar
T = TypeVar('T')
def get_first(ele:list[T])-> T:
return ele[0]
strs = ['hi', 'hey']
ints = [2, 4]
val = get_first(strs) # str
val = get_first(ints) # int
After:
def get_first[T](ele: list[T]) -> T:
return ele[0]
strs = ["hi", "hey"]
ints = [2, 4]
val = get_first(strs) # str
val = get_first(ints) # int
Let's have a look at an example where we can use generics in python classes
Before:
from typing import TypeVar, Generic
from dataclasses import dataclass
T = TypeVar("T")
class Car(Generic[T]):
def __init__(self, car: T) -> None:
self.car = car
def set_car(self, car: T) -> None:
self.car = car
def get_car(self) -> T:
return self.car
@dataclass(kw_only=True)
class Vehicle:
name: str
license_plate: str
car = Car(Vehicle(name="Audi", license_plate="DGB 56"))
my_car = car.get_car() # type Vehicle
After:
from dataclasses import dataclass
class Car[T]:
def __init__(self, car: T) -> None:
self.car = car
def set_car(self, car: T) -> None:
self.car = car
def get_car(self) -> T:
return self.car
@dataclass(kw_only=True)
class Vehicle:
name: str
license_plate: str
car = Car(Vehicle(name="Audi", license_plate="DGB 56"))
my_car = car.get_car() # type Vehicle
We can restrict the type of a generic by using bound
let's consider an example where we have a class Plane
, Car
and a Boat
and all these classes are inheriting from a dataclass Vehicle
.
from typing import TypeVar, Generic
from dataclasses import dataclass
@dataclass(kw_only=True)
class Vehicle:
name: str
def display(self) -> None:
print(f"Vehicle Name: {self.name}")
class Boat(Vehicle):
def display(self) -> None:
print(f"Boat Name: {self.name}")
class Plane(Vehicle):
def display(self) -> None:
print(f"Plane Name: {self.name}")
class Car(Vehicle):
def display(self) -> None:
print(f"Car Name: {self.name}")
We want to create a registry class that will set bound on a certain type of a vehicle. We can do it as follows:
Before:
V = TypeVar("V", bound=Vehicle)
class Registry(Generic[V]):
def __init__(self) -> None:
self.vehicles: list[V] = []
def add(self, v: V) -> None:
self.vehicles.append(v)
def display_all(self) -> None:
for v in self.vehicles:
v.display()
registry = Registry[Car]()
registry.add(Car(name="Jeep"))
registry.add(Car(name="Toyota"))
registry.add(Plane(name="Jet")) # this is a type error
registry.display_all()
If we want our registry
to accept all the vehicle types and the or it subclass we can then do it as follows:
registry = Registry[Vehicle]()
registry.add(Car(name="Jeep"))
registry.add(Boat(name="Yacht"))
registry.add(Plane(name="Jet"))
registry.display_all()
That was before now after python 3.12
we can do it as follows:
class Registry[T: Vehicle]:
def __init__(self) -> None:
self.vehicles: list[T] = []
def add(self, v: T) -> None:
self.vehicles.append(v)
def display_all(self) -> None:
for v in self.vehicles:
v.display()
registry = Registry[Car]()
registry.add(Car(name="Jeep"))
registry.add(Car(name="Toyota"))
registry.add(Plane(name="Jet")) # this is a type error
registry.display_all()
If we want our registry
to accept all the vehicle types and the or it subclass we can then do it as follows:
registry = Registry[Vehicle]()
registry.add(Car(name="Jeep"))
registry.add(Boat(name="Yacht"))
registry.add(Plane(name="Jet"))
registry.display_all()
We can also constrain our Registry
class so that it can take a Car
or a Boat
as follows:
class Registry[T: (Car, Boat)]:
def __init__(self) -> None:
self.vehicles: list[T] = []
def add(self, v: T) -> None:
self.vehicles.append(v)
def display_all(self) -> None:
for v in self.vehicles:
v.display()
registry = Registry[Car]()
registry.add(Car(name="Jeep"))
registry.add(Boat(name="Yacht"))
registry.display_all()
In this example we are going to have a look at a design patten called the Repository
Pattern. Suppose we are building a simple orm
database that does some simple CRUD
operations on a Post
we could do something that looks as follows:
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
@dataclass
class Post:
id: Optional[str]
title: str
@classmethod
def create_table(cls, db_name: str) -> None:
"""Table Created"""
@classmethod
def get_post(cls, id: int, db_name: str) -> "Post":
# get a post and return it
return Post(id=2, title="hello")
@classmethod
def get_posts(cls, db_name: str) -> ["Post"]:
# get all post and return it
return [Post(id=2, title="hello")]
@classmethod
def add_post(cls, title: str, db_name: str) -> "Post":
# add a post to a database
return Post(id=2, title=title)
@classmethod
def update_post(cls, id, title: str, db_name: str) -> "Post":
# update posts
return Post(id=2, title=title)
@classmethod
def delete_post(cls, id, db_name: str) -> None:
# delete post
pass
if __name__ == "__main__":
Post.create_table("hello.db")
Post.add_post(title="hey", db_name="hello.db")
Post.add_post(title="hey", db_name="hello.db")
for post in Post.get_posts(db_name="hello"):
print(Post)
That how we will do it before python 3.12
. The following example is how we will do it after this version of python.
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
import contextlib, sqlite3
@dataclass
class Post:
id: Optional[str]
title: str
class IModel[T](ABC):
@abstractmethod
def get(self, id: int) -> T:
raise NotImplemented
@abstractmethod
def get_all(self) -> list[T]:
raise NotImplemented
@abstractmethod
def add(self, **kwargs: object) -> None:
raise NotImplemented
@abstractmethod
def update(self, id, **kwargs: object) -> None:
raise NotImplemented
@abstractmethod
def delete(self, id) -> None:
raise NotImplemented
First things first we are creating our abstract class IModel
that will map all the required method to be implemented to by the child class Model
. The Model
class will take in a generic T
and in the init()
we will be taking an argument of a model called Post
class Model[T](IModel[T]):
def __init__(self, model: T) -> None:
super().__init__()
self._Table = model
def create_table(self) -> None:
"""Table Created"""
@contextlib.contextmanager
def connect(self):
with sqlite3.connect('hello.db') as conn:
yield conn.cursor()
def get(self, id: int) -> T:
with self.connect() as cursor:
cursor.execute("SELECT * FROM post WHERE id=2")
res = cursor.fetchone()
# get a model and return it
return self._Table(id=2, title="hello")
def get_all(self) -> list[T]:
# get all model and return it
return [self._Table(id=2, title="hello")]
def add(self, title: str) -> T:
# add a model to a database
return self._Table(id=2, title=title)
def update(self, id, title: str) -> T:
# update model
return self._Table(id=2, title=title)
def delete(self, id) -> None:
# delete model
pass
if __name__ == "__main__":
model = Model[Post](Post)
model.add(title="hey")
model.add(title="hey")
p = model.get(7)
for post in model.get_all():
print(post)
With this we will be type safe for getting and from the model. And we made our implementation so generic that it can work with any kind of a model.