├── requirements.txt ├── 01_OOP_in_python ├── 07 - OOP Principles │ ├── items.csv │ ├── main.py │ ├── poly_explain.py │ ├── keyboard.py │ ├── phone.py │ └── item.py ├── 04 - Class vs Static Methods │ ├── items.csv │ ├── helper.py │ └── main.py ├── 06 - Getters and Setters │ ├── items.csv │ ├── main.py │ ├── phone.py │ └── item.py ├── 02 - Constructor & __init__ │ └── main.py ├── 01_introduction_to_oop │ └── main.py ├── 03 - Class Attributes │ └── main.py ├── 05 - Class Inheritance │ └── main.py └── README.md ├── .github └── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md ├── README.md ├── SECURITY.md ├── 04_design patterns in python ├── 06_adapter.py ├── 08_facade.py ├── 17_strategy.py ├── 04_prototype.py ├── 09_proxy.py ├── 16_template method.py ├── 01_singleton.py ├── 07_decorator.py ├── 11_bridge.py ├── 20_visitor.py ├── 18_state.py ├── 02_factory.py ├── 14_observer.py ├── 12_chain of responsibility.py ├── 10_composite.py ├── 13_command.py ├── 03_abstract factory.py ├── 15_mediator.py ├── README.md ├── 05_builder.py └── 19_memento.py ├── 02_multi threading in python ├── 04_current_thread_and_enumerate.py ├── 01_first_method.py ├── 05_thread_pool_executer.py ├── 02_secode_method.py ├── 03_daemon.py ├── 09_timer.py ├── 06_race_condition_and_lock.py ├── 10_event.py ├── 08_semaphore_and_boundedsemaphore.py ├── 07_dead_lock_and_Rlock.py └── README.md ├── LICENSE ├── CONTRIBUTING.md ├── 03_asyncronous in python (asyncio) └── README.md └── CODE_OF_CONDUCT.md /requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /01_OOP_in_python/07 - OOP Principles/items.csv: -------------------------------------------------------------------------------- 1 | name,price,quantity 2 | "Phone",100,1 3 | "Laptop",1000,3 4 | "Cable",10,5 5 | "Mouse",50,5 6 | "Keyboard",74.90,5 7 | -------------------------------------------------------------------------------- /01_OOP_in_python/04 - Class vs Static Methods/items.csv: -------------------------------------------------------------------------------- 1 | name,price,quantity 2 | "Phone",100,1 3 | "Laptop",1000,3 4 | "Cable",10,5 5 | "Mouse",50,5 6 | "Keyboard",75,5 7 | -------------------------------------------------------------------------------- /01_OOP_in_python/06 - Getters and Setters/items.csv: -------------------------------------------------------------------------------- 1 | name,price,quantity 2 | "Phone",100,1 3 | "Laptop",1000,3 4 | "Cable",10,5 5 | "Mouse",50,5 6 | "Keyboard",74.90,5 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /01_OOP_in_python/07 - OOP Principles/main.py: -------------------------------------------------------------------------------- 1 | from phone import Phone 2 | from keyboard import Keyboard 3 | 4 | item1 = Keyboard("jscKeyboard", 1000, 3) 5 | 6 | item1.apply_discount() 7 | 8 | print(item1.price) -------------------------------------------------------------------------------- /01_OOP_in_python/06 - Getters and Setters/main.py: -------------------------------------------------------------------------------- 1 | from item import Item 2 | 3 | item1 = Item("MyItem", 750) 4 | 5 | # Setting an Attribute 6 | item1.name = "OtherItem" 7 | 8 | # Getting an Attribute 9 | print(item1.name) -------------------------------------------------------------------------------- /01_OOP_in_python/07 - OOP Principles/poly_explain.py: -------------------------------------------------------------------------------- 1 | name = "Jim" # str 2 | print(len(name)) 3 | 4 | some_list = ["some","name"] # list 5 | print(len(some_list)) 6 | # That's polymorphism in action, a single function does now 7 | # how to handle different kinds of objects as expected! -------------------------------------------------------------------------------- /01_OOP_in_python/07 - OOP Principles/keyboard.py: -------------------------------------------------------------------------------- 1 | from item import Item 2 | 3 | 4 | class Keyboard(Item): 5 | pay_rate = 0.7 6 | def __init__(self, name: str, price: float, quantity=0): 7 | # Call to super function to have access to all attributes / methods 8 | super().__init__( 9 | name, price, quantity 10 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced topics in python 2 | 3 | - This repository contains advanced topics in python 4 | - I am going to keep this repository updated and add new topics 5 | 6 | ### Please review the README.md file inside of each folder: 7 | 8 | - [OOP in python](/01_OOP_in_python/README.md) 9 | - [multi-threading](/02_multi%20threading%20in%20python/README.md) 10 | - [asynchronous (asyncio)](/03_asyncronous%20in%20python%20(asyncio)/README.md) 11 | - [design patterns](/04_design%20patterns%20in%20python/README.md) 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /01_OOP_in_python/06 - Getters and Setters/phone.py: -------------------------------------------------------------------------------- 1 | from item import Item 2 | 3 | 4 | class Phone(Item): 5 | def __init__(self, name: str, price: float, quantity=0, broken_phones=0): 6 | # Call to super function to have access to all attributes / methods 7 | super().__init__( 8 | name, price, quantity 9 | ) 10 | 11 | # Run validations to the received arguments 12 | assert broken_phones >= 0, f"Broken Phones {broken_phones} is not greater or equal to zero!" 13 | 14 | # Assign to self object 15 | self.broken_phones = broken_phones -------------------------------------------------------------------------------- /01_OOP_in_python/07 - OOP Principles/phone.py: -------------------------------------------------------------------------------- 1 | from item import Item 2 | 3 | 4 | class Phone(Item): 5 | pay_rate = 0.5 6 | def __init__(self, name: str, price: float, quantity=0, broken_phones=0): 7 | # Call to super function to have access to all attributes / methods 8 | super().__init__( 9 | name, price, quantity 10 | ) 11 | 12 | # Run validations to the received arguments 13 | assert broken_phones >= 0, f"Broken Phones {broken_phones} is not greater or equal to zero!" 14 | 15 | # Assign to self object 16 | self.broken_phones = broken_phones -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /01_OOP_in_python/02 - Constructor & __init__/main.py: -------------------------------------------------------------------------------- 1 | class Item: 2 | def __init__(self, name: str, price: float, quantity=0): 3 | # Run validations to the received arguments 4 | assert price >= 0, f"Price {price} is not greater than or equal to zero!" 5 | assert quantity >= 0, f"Quantity {quantity} is not greater or equal to zero!" 6 | 7 | # Assign to self object 8 | self.name = name 9 | self.price = price 10 | self.quantity = quantity 11 | 12 | def calculate_total_price(self): 13 | return self.price * self.quantity 14 | 15 | item1 = Item("Phone", 100, 1) 16 | item2 = Item("Laptop", 1000, 3) 17 | 18 | print(item1.calculate_total_price()) 19 | print(item2.calculate_total_price()) -------------------------------------------------------------------------------- /04_design patterns in python/06_adapter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Adapter 3 | - a structural design pattern that allows objects with incompatible interfaces to collaborate. 4 | """ 5 | import xmltodict 6 | 7 | 8 | class Application: 9 | def send_request(self): 10 | return 'data.xml' 11 | 12 | 13 | class Analytic: 14 | def receive_request(self, json): 15 | return json 16 | 17 | 18 | class Adapter: 19 | def convert_xml_json(self, file): 20 | with open(file, 'r') as myfile: 21 | obj = xmltodict.parse(myfile.read()) 22 | return obj 23 | 24 | 25 | def client_adapter(): 26 | sender = Application().send_request() 27 | converted_data = Adapter().convert_xml_json(sender) 28 | receiver = Analytic().receive_request(converted_data) 29 | print(receiver) 30 | 31 | 32 | client_adapter() 33 | -------------------------------------------------------------------------------- /01_OOP_in_python/01_introduction_to_oop/main.py: -------------------------------------------------------------------------------- 1 | # How to create a class: 2 | class Item: 3 | def calculate_total_price(self, x, y): 4 | return x * y 5 | 6 | # How to create an instance of a class 7 | item1 = Item() 8 | 9 | # Assign attributes: 10 | item1.name = "Phone" 11 | item1.price = 100 12 | item1.quantity = 5 13 | 14 | # Calling methods from instances of a class: 15 | print(item1.calculate_total_price(item1.price, item1.quantity)) 16 | 17 | # How to create an instance of a class (We could create as much as instances we'd like to) 18 | item2 = Item() 19 | 20 | # Assign attributes 21 | item2.name = "Laptop" 22 | item2.price = 1000 23 | item2.quantity = 3 24 | 25 | # Calling methods from instances of a class: 26 | print(item2.calculate_total_price(item2.price, item2.quantity)) -------------------------------------------------------------------------------- /02_multi threading in python/04_current_thread_and_enumerate.py: -------------------------------------------------------------------------------- 1 | from time import sleep, perf_counter 2 | from threading import Thread, current_thread, enumerate 3 | 4 | 5 | # enumerate returns a list of all Thread objects alive. 6 | 7 | start = perf_counter() 8 | 9 | def show(name): 10 | print(f'{name} started.') 11 | print(current_thread().name, ' is running.') 12 | sleep(3) 13 | print(f'{name} finished.') 14 | 15 | 16 | t1 = Thread(target=show, args=('One',), name= 'First') 17 | t2 = Thread(target=show, args=('Two',), name= 'Second') 18 | t1.start() 19 | t2.start() 20 | 21 | print('current threads alive: ', enumerate()) 22 | 23 | t1.join() # wait until thread t1 is finished 24 | t2.join() # wait until thread t2 is finished 25 | 26 | end = perf_counter() 27 | 28 | print(end - start) 29 | 30 | 31 | -------------------------------------------------------------------------------- /04_design patterns in python/08_facade.py: -------------------------------------------------------------------------------- 1 | """ 2 | Facade 3 | - a structural design pattern that provides a simplified interface to a library, 4 | a framework, or any other complex set of classes. 5 | """ 6 | 7 | class CPU: # Subsystem 1 8 | def execute(self): 9 | print('Executing') 10 | 11 | 12 | class Memory: # Subsystem 2 13 | def load(self): 14 | print('Loading data.') 15 | 16 | 17 | class SSD: # Subsystem 3 18 | def read(self): 19 | print('Some data from ssd') 20 | 21 | 22 | class Computer: # Facade 23 | def __init__(self, sub1, sub2): 24 | self.cpu = sub1() 25 | self.memory = sub2() 26 | 27 | def start(self): 28 | self.memory.load() 29 | self.cpu.execute() 30 | 31 | 32 | def client_facade(): 33 | computer_facade = Computer(CPU, Memory) 34 | computer_facade.start() 35 | 36 | 37 | client_facade() 38 | -------------------------------------------------------------------------------- /02_multi threading in python/01_first_method.py: -------------------------------------------------------------------------------- 1 | from time import sleep, perf_counter 2 | from threading import Thread 3 | 4 | print('*** normal execution ***') 5 | 6 | start = perf_counter() 7 | 8 | def show(name): 9 | print(f'{name} started.') 10 | sleep(3) 11 | print(f'{name} finished.') 12 | 13 | show('One') 14 | show('Two') 15 | end = perf_counter() 16 | print(end - start) 17 | 18 | ######################## multi-threading ######################### 19 | 20 | print('*** using multi-threading ***') 21 | start = perf_counter() 22 | t1 = Thread(target=show, args=('One',)) 23 | t2 = Thread(target=show, args=('Two',)) 24 | t1.start() 25 | t2.start() 26 | 27 | t1.join() # wait until thread t1 is finished 28 | t2.join() # wait until thread t2 is finished 29 | 30 | end = perf_counter() 31 | 32 | print('using multi-threading: ', end - start) 33 | 34 | 35 | -------------------------------------------------------------------------------- /01_OOP_in_python/04 - Class vs Static Methods/helper.py: -------------------------------------------------------------------------------- 1 | # When to use class methods and when to use static methods ? 2 | 3 | class Item: 4 | @staticmethod 5 | def is_integer(): 6 | ''' 7 | This should do something that has a relationship 8 | with the class, but not something that must be unique 9 | per instance! 10 | ''' 11 | @classmethod 12 | def instantiate_from_something(cls): 13 | ''' 14 | This should also do something that has a relationship 15 | with the class, but usually, those are used to 16 | manipulate different structures of data to instantiate 17 | objects, like we have done with CSV. 18 | ''' 19 | 20 | # THE ONLY DIFFERENCE BETWEEN THOSE: 21 | # Static methods are not passing the object reference as the first argument in the background! 22 | 23 | 24 | # NOTE: However, those could be also called from instances. 25 | 26 | item1 = Item() 27 | item1.is_integer() 28 | item1.instantiate_from_something() 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /02_multi threading in python/05_thread_pool_executer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | In Python, you can use the concurrent.futures module to implement a thread pool executor. 3 | The ThreadPoolExecutor class provides an easy-to-use interface for managing a pool of worker threads. 4 | Here's an example of how you can use it: 5 | ''' 6 | 7 | from time import sleep 8 | from concurrent.futures import ThreadPoolExecutor 9 | 10 | def show(name): 11 | print(f'{name} started.') 12 | sleep(3) 13 | print(f'{name} finished.') 14 | 15 | # the best way of using ThreadPoolExecutor is to use as a context manager: 16 | 17 | with ThreadPoolExecutor() as executer: 18 | names = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight'] 19 | executer.map(show, names) 20 | 21 | # if we want to execute the threads two by two: 22 | 23 | # with ThreadPoolExecutor(max_workers=2) as executer: 24 | # names = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight'] 25 | # executer.map(show, names) 26 | 27 | print('Done ...') 28 | 29 | 30 | -------------------------------------------------------------------------------- /02_multi threading in python/02_secode_method.py: -------------------------------------------------------------------------------- 1 | from time import sleep, perf_counter 2 | from threading import Thread 3 | 4 | print('*** normal execution ***') 5 | 6 | start = perf_counter() 7 | 8 | def show(name, delay): 9 | print(f'{name} started.') 10 | sleep(delay) 11 | print(f'{name} finished.') 12 | 13 | show('One', 3) 14 | show('Two', 3) 15 | end = perf_counter() 16 | print(end - start) 17 | 18 | ######################## multi-threading ######################### 19 | 20 | print('*** using multi-threading ***') 21 | 22 | start = perf_counter() 23 | 24 | class ShowThread(Thread): 25 | def __init__(self, name, delay): 26 | super().__init__() 27 | self.name = name 28 | self.delay = delay 29 | 30 | def run(self): 31 | show(self.name, self.delay) 32 | 33 | t1 = ShowThread('One', 3) 34 | t2 = ShowThread('Two', 3) 35 | 36 | t1.start() 37 | t2.start() 38 | 39 | t1.join() 40 | t2.join() 41 | 42 | end = perf_counter() 43 | 44 | print('using multi-threading: ', end - start) 45 | 46 | 47 | -------------------------------------------------------------------------------- /04_design patterns in python/17_strategy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Strategy 3 | - a behavioral design pattern that lets you define a family of algorithms, 4 | put each of them into a separate class, and make their objects interchangeable. 5 | """ 6 | import abc 7 | 8 | 9 | class Read: # Context 10 | def __init__(self, sentence): 11 | self.sentence = sentence 12 | self._direction = None # strategy instance 13 | 14 | def set_direction(self, direction): # set_strategy 15 | self._direction = direction 16 | 17 | def read(self): 18 | return self._direction.direct(self.sentence) 19 | 20 | 21 | class Direction(abc.ABC): # Abstract Strategy 22 | 23 | @abc.abstractmethod 24 | def direct(self, data): 25 | pass 26 | 27 | 28 | class Right(Direction): # Concrete Strategy 29 | def direct(self, data): 30 | print(data[::-1]) 31 | 32 | 33 | class Left(Direction): # Concrete Strategy 34 | def direct(self, data): 35 | print(data[::1]) 36 | 37 | 38 | c = Read('Hello world') 39 | c.set_direction(Right()) 40 | c.read() 41 | 42 | c.set_direction(Left()) 43 | c.read() 44 | 45 | -------------------------------------------------------------------------------- /04_design patterns in python/04_prototype.py: -------------------------------------------------------------------------------- 1 | """ 2 | Prototype 3 | - Prototype is a creational design pattern that lets you copy existing objects 4 | without making your code dependent on their classes. 5 | """ 6 | import copy 7 | 8 | 9 | class Prototype: 10 | def __init__(self): 11 | self._objects = {} 12 | 13 | def register(self, name, obj): 14 | self._objects[name] = obj 15 | 16 | def unregister(self, name): 17 | del self._objects[name] 18 | 19 | def clone(self, name, **kwargs): 20 | cloned_obj = copy.deepcopy(self._objects.get(name)) 21 | cloned_obj.__dict__.update(kwargs) 22 | return cloned_obj 23 | 24 | 25 | def client_prototype(name, obj, **kwargs): 26 | prototype = Prototype() 27 | prototype.register(name, obj) 28 | return prototype.clone(name, **kwargs) 29 | 30 | 31 | class Person: 32 | def __init__(self, name, age): 33 | self.name = name 34 | self.age = age 35 | 36 | 37 | p = Person('amir', 34) 38 | 39 | p_clone = client_prototype('p1', p, age=20) 40 | p_clone2 = client_prototype('p2', p_clone, age=19) 41 | 42 | print(p_clone2.__dict__) -------------------------------------------------------------------------------- /04_design patterns in python/09_proxy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Proxy 3 | - a structural design pattern that lets you provide a substitute or placeholder 4 | for another object. A proxy controls access to the original object, allowing you 5 | to perform something either before or after the request gets through to the original object. 6 | """ 7 | import abc 8 | import time 9 | import datetime 10 | 11 | class AbstractServer(abc.ABC): 12 | 13 | @abc.abstractmethod 14 | def receive(self): 15 | pass 16 | 17 | 18 | class Server(AbstractServer): 19 | def receive(self): 20 | print('Processing your request...') 21 | time.sleep(1) 22 | print('Done...') 23 | 24 | 25 | class LogProxy(AbstractServer): 26 | def __init__(self, server): 27 | self._server = server 28 | 29 | def receive(self): 30 | self.logging() 31 | # ... 32 | self._server.receive() 33 | 34 | def logging(self): 35 | with open('log.log', 'a') as log: 36 | log.write(f'Request {datetime.datetime.now()} \n') 37 | 38 | 39 | def client_server(server, proxy): 40 | s = server() 41 | p = proxy(s) 42 | p.receive() 43 | 44 | 45 | client_server(Server, LogProxy) 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Barzan Saeedpour 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /01_OOP_in_python/03 - Class Attributes/main.py: -------------------------------------------------------------------------------- 1 | class Item: 2 | pay_rate = 0.8 # The pay rate after 20% discount 3 | all = [] 4 | def __init__(self, name: str, price: float, quantity=0): 5 | # Run validations to the received arguments 6 | assert price >= 0, f"Price {price} is not greater than or equal to zero!" 7 | assert quantity >= 0, f"Quantity {quantity} is not greater or equal to zero!" 8 | 9 | # Assign to self object 10 | self.name = name 11 | self.price = price 12 | self.quantity = quantity 13 | 14 | # Actions to execute 15 | Item.all.append(self) 16 | 17 | def calculate_total_price(self): 18 | return self.price * self.quantity 19 | 20 | def apply_discount(self): 21 | self.price = self.price * self.pay_rate 22 | 23 | # A magic method for representing the object 24 | def __repr__(self): 25 | return f"Item('{self.name}', {self.price}, {self.quantity})" 26 | 27 | item1 = Item("Phone", 100, 1) 28 | item2 = Item("Laptop", 1000, 3) 29 | item3 = Item("Cable", 10, 5) 30 | item4 = Item("Mouse", 50, 5) 31 | item5 = Item("Keyboard", 75, 5) 32 | 33 | print(Item.all) -------------------------------------------------------------------------------- /04_design patterns in python/16_template method.py: -------------------------------------------------------------------------------- 1 | """ 2 | Template Method 3 | - a behavioral design pattern that defines the skeleton of an algorithm in the superclass 4 | but lets subclasses override specific steps of the algorithm without changing its structure. 5 | """ 6 | import abc 7 | 8 | 9 | class Top(abc.ABC): 10 | def template_method(self): 11 | self.first_common() 12 | self.second_common() 13 | self.third_require() 14 | self.fourth_require() 15 | 16 | def first_common(self): 17 | print('This is first common...') 18 | 19 | def second_common(self): 20 | print('This is second common...') 21 | 22 | @abc.abstractmethod 23 | def third_require(self): 24 | pass 25 | 26 | @abc.abstractmethod 27 | def fourth_require(self): 28 | pass 29 | 30 | 31 | class One(Top): 32 | def third_require(self): 33 | print('This is third require from One') 34 | 35 | def fourth_require(self): 36 | print('This is fourth require from One') 37 | 38 | 39 | class Two(Top): 40 | def third_require(self): 41 | print('This is third require from Two') 42 | 43 | def fourth_require(self): 44 | print('This is fourth require from Two') 45 | 46 | 47 | def client(klass): 48 | klass.template_method() 49 | 50 | client(Two()) 51 | -------------------------------------------------------------------------------- /02_multi threading in python/03_daemon.py: -------------------------------------------------------------------------------- 1 | from time import sleep, perf_counter 2 | from threading import Thread 3 | import sys 4 | 5 | # When daemon is False, the program does not exit until the thread is finished. 6 | # But, when daemon is True, the program exits whether the thread is finished or not. 7 | # In the example below, daemon is True. Therefore, the program exits on sys.exit() command even though the 8 | # threads are not finished yet. 9 | 10 | 11 | start = perf_counter() 12 | 13 | def show(name): 14 | print(f'{name} started.') 15 | sleep(3) 16 | print(f'{name} finished.') 17 | 18 | # daemon is False by default 19 | t1 = Thread(target=show, args=('One',), daemon= True) 20 | t2 = Thread(target=show, args=('Two',), daemon= True) 21 | 22 | # another way of setting daemon value is to use the setter: 23 | # t1.setDaemon(True) 24 | # t2.setDaemon(True) 25 | 26 | t1.start() 27 | t2.start() 28 | 29 | print(t1.isDaemon()) 30 | print(t2.isDaemon()) 31 | 32 | # When we use join, deamon value does not matter. 33 | 34 | # t1.join() # wait until thread t1 is finished 35 | # t2.join() # wait until thread t2 is finished 36 | 37 | end = perf_counter() 38 | 39 | print('using multi-threading: ', end - start) 40 | sys.exit() 41 | 42 | 43 | -------------------------------------------------------------------------------- /04_design patterns in python/01_singleton.py: -------------------------------------------------------------------------------- 1 | """ 2 | Singleton 3 | - Ensure a class only has one instance, and provide a global point of access to it. 4 | """ 5 | 6 | # first method: 7 | 8 | from typing import Any 9 | 10 | 11 | class A: 12 | _instance = None 13 | 14 | def __init__(self): 15 | raise RuntimeError('call get_instance() instead') 16 | 17 | @classmethod 18 | def get_instance(cls): 19 | if cls._instance is None: 20 | cls._instance = cls.__new__(cls) 21 | 22 | return cls._instance 23 | 24 | 25 | # to test: 26 | try: 27 | one = A() 28 | except: 29 | pass 30 | 31 | one = A.get_instance() 32 | two = A.get_instance() 33 | 34 | # the ids should be the same 35 | 36 | print(id(one)) 37 | print(id(two)) 38 | 39 | # ######################################################### 40 | 41 | # second method (using meta classes): 42 | 43 | class Singleton(type): 44 | _instance = None 45 | 46 | def __call__(self, *args: Any, **kwds: Any): 47 | if self._instance is None: 48 | self._instance = super().__call__() 49 | return self._instance 50 | 51 | class A(metaclass= Singleton): 52 | pass 53 | 54 | one = A() 55 | two = A() 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /04_design patterns in python/07_decorator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Decorator 3 | - a structural pattern that allows adding new behaviors to objects dynamically 4 | by placing them inside special wrapper objects, called decorators. 5 | """ 6 | import abc 7 | 8 | 9 | class Page(abc.ABC): # Abstract Component 10 | 11 | @abc.abstractmethod 12 | def show(self): 13 | pass 14 | 15 | 16 | class AuthPage(Page): # Concrete Component 1 17 | def show(self): 18 | print('Welcome to authenticated page') 19 | 20 | 21 | class AnonPage(Page): # Concrete Component 2 22 | def show(self): 23 | print('Welcome to anonymous page') 24 | 25 | 26 | 27 | class PageDecorator(Page, abc.ABC): # Abstract Decorator 28 | def __init__(self, component): 29 | self._component = component 30 | 31 | @abc.abstractmethod 32 | def show(self): 33 | pass 34 | 35 | 36 | class PageAuthDecorator(PageDecorator): # Concrete Decorator 37 | def show(self): 38 | username = input('Enter your username... ') 39 | password = input('Enter your password... ') 40 | if username == 'admin' and password == 'secret': 41 | self._component.show() 42 | else: 43 | print('you are not authenticated') 44 | 45 | 46 | def client_decorator(): 47 | page = AuthPage() 48 | authenticated = PageAuthDecorator(page) 49 | authenticated.show() 50 | 51 | client_decorator() 52 | -------------------------------------------------------------------------------- /04_design patterns in python/11_bridge.py: -------------------------------------------------------------------------------- 1 | """ 2 | Bridge 3 | - a structural design pattern that lets you split a large class into two separate 4 | hierarchies — abstraction and implementation — which can be developed independently of each other. 5 | """ 6 | import abc 7 | 8 | class Shape(abc.ABC): # Abstraction 9 | def __init__(self, color): 10 | self.color = color 11 | 12 | def show(self): 13 | pass 14 | 15 | 16 | class Circle(Shape): # Refined Abstraction 17 | def show(self): 18 | self.color.paint(self.__class__.__name__) 19 | 20 | 21 | class Square(Shape): # Refined Abstraction 22 | def show(self): 23 | self.color.paint(self.__class__.__name__) 24 | 25 | 26 | class Triangle(Shape): # Refined Abstraction 27 | def show(self): 28 | self.color.paint(self.__class__.__name__) 29 | 30 | 31 | class Color(abc.ABC): # Implementation 32 | def paint(self, name): 33 | pass 34 | 35 | 36 | class Blue(Color): # Concrete Implementation 37 | def paint(self, name): 38 | print(f'this is a blue {name}') 39 | 40 | 41 | class Red(Color): # Concrete Implementation 42 | def paint(self, name): 43 | print(f'this is a red {name}') 44 | 45 | 46 | class Yellow(Color): # Concrete Implementation 47 | def paint(self, name): 48 | print(f'this is a yellow {name}') 49 | 50 | 51 | ylw = Yellow() 52 | circle = Circle(ylw) 53 | circle.show() 54 | -------------------------------------------------------------------------------- /02_multi threading in python/09_timer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | To create a timer using the threading module in Python, you can utilize the Timer class. 3 | The Timer class allows you to execute a function after a specified delay. 4 | Here's an example: 5 | ''' 6 | 7 | import threading 8 | 9 | def timer_callback(): 10 | print("Timer complete!") 11 | 12 | # Create a timer that will execute the callback after 5 seconds 13 | timer = threading.Timer(5, timer_callback) 14 | timer.start() 15 | 16 | # Do other tasks while the timer is running 17 | print("Doing other tasks...") 18 | 19 | # Wait for the timer to complete 20 | timer.join() 21 | 22 | print("Timer finished.") 23 | 24 | ''' 25 | In this example, a timer is created using threading.Timer(5, timer_callback), 26 | where 5 represents the delay in seconds and timer_callback is the function that will be 27 | executed when the timer completes. The start() method starts the timer, and the main 28 | thread continues to execute other tasks while the timer is running. 29 | 30 | The timer.join() call is used to block the main thread until the timer completes. 31 | This is optional but can be useful if you want to wait for the timer to finish before 32 | proceeding with other actions. 33 | 34 | When the timer completes, the timer_callback() function is executed, and "Timer complete!" i 35 | s printed. Finally, "Timer finished." is printed to indicate that the timer has finished execution. 36 | ''' -------------------------------------------------------------------------------- /02_multi threading in python/06_race_condition_and_lock.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Race condition 3 | Thread Safe 4 | Dead lock 5 | 6 | A race condition is a situation that can occur in multithreaded or multiprocessing programs 7 | where the behavior of the program depends on the relative timing or interleaving of the execution 8 | of concurrent operations. In simpler terms, it refers to a scenario where the output or behavior 9 | of a program becomes unpredictable because multiple threads or processes are accessing 10 | and modifying shared resources concurrently without proper synchronization. 11 | 12 | ''' 13 | 14 | 15 | # notice that sometimes it returns 0 without lock but it's not reliable 16 | 17 | from threading import Thread, Lock 18 | 19 | num = 0 # this variable is a shared resource between the Threads 20 | lock = Lock() 21 | 22 | def add(): 23 | global num 24 | 25 | # first method of using lock: 26 | # lock.acquire() 27 | # for _ in range(100000): 28 | # num += 1 29 | # lock.release() 30 | 31 | # second method of using lock: 32 | with lock: 33 | for _ in range(100000): 34 | num += 1 35 | 36 | def subtract(): 37 | global num 38 | with lock: 39 | for _ in range(100000): 40 | num -= 1 41 | 42 | 43 | t1 = Thread(target=add) 44 | t2 = Thread(target=subtract) 45 | 46 | t1.start() 47 | t2.start() 48 | 49 | t1.join() 50 | t2.join() 51 | 52 | print(num) 53 | print('Done...') -------------------------------------------------------------------------------- /04_design patterns in python/20_visitor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Visitor 3 | - a behavioral design pattern that allows adding new behaviors to existing class hierarchy 4 | without altering any existing code. 5 | """ 6 | import abc 7 | 8 | 9 | class PublicVehicle(abc.ABC): # Abstract Element 10 | def __init__(self, dest): 11 | self.dest = dest 12 | 13 | @abc.abstractmethod 14 | def order_ticket(self, ordering): 15 | pass 16 | 17 | 18 | class Train(PublicVehicle): # Concrete Element 19 | def order_ticket(self, ordering): 20 | ordering.train_ticket(self) 21 | 22 | 23 | class Bus(PublicVehicle): # Concrete Element 24 | def order_ticket(self, ordering): 25 | ordering.bus_ticket(self) 26 | 27 | 28 | class Plane(PublicVehicle): # Concrete Element 29 | def order_ticket(self, ordering): 30 | ordering.plane_ticket(self) 31 | 32 | 33 | class Ticket(abc.ABC): # Abstract Visitor 34 | 35 | @abc.abstractmethod 36 | def train_ticket(self, train): 37 | pass 38 | 39 | @abc.abstractmethod 40 | def bus_ticket(self, bus): 41 | pass 42 | 43 | @abc.abstractmethod 44 | def plane_ticket(self, plane): 45 | pass 46 | 47 | 48 | class Order(Ticket): # Concrete Visitor 49 | def train_ticket(self, train): 50 | print(f'This is a train ticket to {train.dest}') 51 | 52 | def bus_ticket(self, bus): 53 | print(f'This is a bus ticket to {bus.dest}') 54 | 55 | def plane_ticket(self, plane): 56 | print(f'This is a plane ticket to {plane.dest}') 57 | 58 | 59 | o = Order() 60 | Train('Tehran').order_ticket(o) 61 | -------------------------------------------------------------------------------- /04_design patterns in python/18_state.py: -------------------------------------------------------------------------------- 1 | """ 2 | State 3 | - a behavioral design pattern that lets an object alter its behavior when its internal state changes. 4 | It appears as if the object changed its class. 5 | """ 6 | import abc 7 | 8 | 9 | class Elevator: # Context 10 | _state = None 11 | 12 | def __init__(self, state): 13 | self.set_state(state) 14 | 15 | def set_state(self, state): 16 | self._state = state 17 | self._state.set_elevator(self) 18 | 19 | def show_state(self): 20 | print(f'Elevator is in {self._state.__class__.__name__}') 21 | 22 | def go_up(self): 23 | self._state.push_up_btn() 24 | 25 | def go_down(self): 26 | self._state.push_down_btn() 27 | 28 | 29 | 30 | class Floor(abc.ABC): # Abstract State 31 | _elevator = None 32 | 33 | def set_elevator(self, elevator): 34 | self._elevator = elevator 35 | 36 | @abc.abstractmethod 37 | def push_down_btn(self): 38 | pass 39 | 40 | @abc.abstractmethod 41 | def push_up_btn(self): 42 | pass 43 | 44 | 45 | class FirstFloor(Floor): # Concrete State 46 | def push_down_btn(self): 47 | print('Already in the bottom floor') 48 | 49 | def push_up_btn(self): 50 | print('Elevator moving upward one floor') 51 | self._elevator.set_state(SecondFloor()) 52 | 53 | 54 | class SecondFloor(Floor): # Concrete State 55 | def push_down_btn(self): 56 | print('Elevator moving down a floor') 57 | self._elevator.set_state(FirstFloor()) 58 | 59 | def push_up_btn(self): 60 | print('Already in the top floor') 61 | 62 | 63 | e = Elevator(FirstFloor()) 64 | e.show_state() 65 | e.go_up() 66 | e.show_state() 67 | e.go_up() 68 | e.go_down() 69 | e.show_state() 70 | e.go_down() 71 | -------------------------------------------------------------------------------- /04_design patterns in python/02_factory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Factory 3 | - Factory is a creational design pattern that provides an interface for creating objects 4 | in a superclass, but allows subclasses to alter the type of objects that will be created. 5 | 6 | 3 Components => 1. creator 2. product 3. client 7 | 8 | creators are deciding what kind of objects should be created. (File, JsonFile, XmlFile) 9 | products are doing the actual functioality. In the example below, Json ans Xml classes are products. 10 | client is dealing with client request. (like client function below) 11 | """ 12 | 13 | from abc import ABC, abstractmethod 14 | 15 | class File(ABC): # creator 16 | def __init__(self, file): 17 | self.file = file 18 | 19 | @abstractmethod 20 | def make(self): 21 | pass 22 | 23 | def call_edit(self): 24 | product = self.make() 25 | result = product.edit(self.file) 26 | return result 27 | 28 | 29 | # JsonFile and XmlFile classes are changing the Type of File Object 30 | 31 | class JsonFile(File): # creator 32 | def make(self,): 33 | return Json() 34 | 35 | class XmlFile(File): # creator 36 | def make(self,): 37 | return Xml() 38 | 39 | class Json: # product 40 | def edit(self, file): 41 | return f'Working on {file} json...' 42 | 43 | class Xml: # product 44 | def edit(self, file): 45 | return f'Working on {file} xml...' 46 | 47 | 48 | def client(file, format): # client 49 | formats = { 50 | 'json': JsonFile, 51 | 'xml': XmlFile, 52 | } 53 | result = formats[format](file) 54 | return result.call_edit() 55 | 56 | print(client('filename', 'json')) 57 | 58 | -------------------------------------------------------------------------------- /04_design patterns in python/14_observer.py: -------------------------------------------------------------------------------- 1 | """ 2 | Observer 3 | - behavioral design pattern that lets you define a subscription mechanism to notify 4 | multiple objects about any events that happen to the object they’re observing 5 | """ 6 | import abc 7 | from random import randrange 8 | 9 | 10 | class Publisher(abc.ABC): 11 | 12 | @abc.abstractmethod 13 | def subscribe(self, observer): 14 | pass 15 | 16 | @abc.abstractmethod 17 | def unsubscribe(self, observer): 18 | pass 19 | 20 | @abc.abstractmethod 21 | def notify(self): 22 | pass 23 | 24 | 25 | class ConcretePublisher(Publisher): 26 | _observers = [] 27 | _state = None 28 | 29 | def subscribe(self, observer): 30 | self._observers.append(observer) 31 | 32 | def unsubscribe(self, observer): 33 | self._observers.remove(observer) 34 | 35 | def notify(self): 36 | print('Notifying observers...') 37 | 38 | for observer in self._observers: 39 | observer.update(self) 40 | 41 | def operation(self): 42 | self._state = randrange(0, 10) 43 | print(f'state changed to {self._state}') 44 | self.notify() 45 | 46 | 47 | class Observer(abc.ABC): 48 | 49 | @abc.abstractmethod 50 | def update(self, publisher): 51 | pass 52 | 53 | 54 | class ObserverA(Observer): 55 | def update(self, publisher): 56 | if publisher._state <= 5: 57 | print('ObserverA reacted to the event') 58 | 59 | 60 | class ObserverB(Observer): 61 | def update(self, publisher): 62 | if publisher._state > 5: 63 | print('ObserverB reacted to the event') 64 | 65 | 66 | publisher = ConcretePublisher() 67 | 68 | observer_a = ObserverA() 69 | observer_b = ObserverB() 70 | 71 | publisher.subscribe(observer_a) 72 | publisher.subscribe(observer_b) 73 | 74 | publisher.operation() 75 | -------------------------------------------------------------------------------- /04_design patterns in python/12_chain of responsibility.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chain of Responsibility 3 | - a behavioral design pattern that lets you pass requests along a chain of handlers. 4 | Upon receiving a request, each handler decides either to process the request or to pass 5 | it to the next handler in the chain. 6 | """ 7 | import abc 8 | 9 | 10 | class AbstractHandler(abc.ABC): 11 | @abc.abstractmethod 12 | def set_next(self, handler): 13 | pass 14 | 15 | @abc.abstractmethod 16 | def handle(self, request): 17 | pass 18 | 19 | 20 | class BaseHandler(AbstractHandler): 21 | _next_handler = None 22 | 23 | def set_next(self, handler): 24 | self._next_handler = handler 25 | return handler 26 | 27 | @abc.abstractmethod 28 | def handle(self, request): 29 | if self._next_handler: 30 | return self._next_handler.handle(request) 31 | return None 32 | 33 | 34 | class HandlerOne(BaseHandler): 35 | def handle(self, request): 36 | if 0 <= request <= 10: 37 | print(f'HandlerOne is processing this request {request}') 38 | else: 39 | return super().handle(request) 40 | 41 | 42 | class HandlerTwo(BaseHandler): 43 | def handle(self, request): 44 | if 10 < request <= 20: 45 | print(f'HandlerTwo is processing this request {request}') 46 | else: 47 | return super().handle(request) 48 | 49 | 50 | class DefaultHandler(BaseHandler): 51 | def handle(self, request): 52 | print(f'DefaultHandler is processing this request {request}') 53 | 54 | 55 | def client(handler, request): 56 | for num in request: 57 | handler.handle(num) 58 | 59 | 60 | nums = [3, 14, 31, 9] 61 | 62 | h_one = HandlerOne() 63 | h_two = HandlerTwo() 64 | h_default = DefaultHandler() 65 | 66 | h_one.set_next(h_two).set_next(h_default) 67 | 68 | client(h_two, nums) 69 | 70 | -------------------------------------------------------------------------------- /04_design patterns in python/10_composite.py: -------------------------------------------------------------------------------- 1 | """ 2 | Composite 3 | - a structural design pattern that lets you compose objects into tree structures 4 | and then work with these structures as if they were individual objects. 5 | """ 6 | import abc 7 | 8 | class Being(abc.ABC): # Abstract Component 9 | def add(self, child): 10 | pass 11 | 12 | def remove(self, child): 13 | pass 14 | 15 | def is_composite(self): 16 | return False 17 | 18 | @abc.abstractmethod 19 | def execute(self): 20 | pass 21 | 22 | 23 | class Animal(Being): # Leaf 24 | def __init__(self, name): 25 | self.name = name 26 | 27 | def execute(self): 28 | print(f'Animal {self.name}') 29 | 30 | 31 | class Human(Being): # Concrete Composite 32 | def __init__(self): 33 | self._children = [] 34 | 35 | def add(self, child): 36 | self._children.append(child) 37 | 38 | def remove(self, child): 39 | self._children.remove(child) 40 | 41 | def is_composite(self): 42 | return True 43 | 44 | def execute(self): 45 | print('Human Composite') 46 | 47 | for child in self._children: 48 | child.execute() 49 | 50 | 51 | class Male(Human): # Leaf 52 | def __init__(self, name): 53 | self.name = name 54 | 55 | def is_composite(self): 56 | return False 57 | 58 | def execute(self): 59 | print(f'\tMale {self.name}') 60 | 61 | 62 | class Female(Human): # Leaf 63 | def __init__(self, name): 64 | self.name = name 65 | 66 | def is_composite(self): 67 | return False 68 | 69 | def execute(self): 70 | print(f'\tFemale {self.name}') 71 | 72 | 73 | def client_composite(): 74 | f1 = Female('jane') 75 | f2 = Female('katty') 76 | m1 = Male('brad') 77 | 78 | h1 = Human() 79 | h1.add(f1) 80 | h1.add(f2) 81 | h1.add(m1) 82 | 83 | h1.execute() 84 | 85 | 86 | client_composite() 87 | -------------------------------------------------------------------------------- /04_design patterns in python/13_command.py: -------------------------------------------------------------------------------- 1 | """ 2 | Command 3 | - a behavioral design pattern that turns a request into a stand-alone object that 4 | contains all information about the request. This transformation lets you pass requests 5 | as a method arguments, delay or queue a request’s execution, and support undoable operations. 6 | """ 7 | import abc 8 | 9 | 10 | class Command(abc.ABC): 11 | 12 | @abc.abstractmethod 13 | def execute(self): 14 | pass 15 | 16 | 17 | class SimpleCommand(Command): 18 | def __init__(self, payload): 19 | self._payload = payload 20 | 21 | def execute(self): 22 | print(f'SimpleCommand: I can do simple things like printing ({self._payload})') 23 | 24 | 25 | class ComplexCommand(Command): 26 | def __init__(self, receiver, a, b): 27 | self._receiver = receiver 28 | self._a = a 29 | self._b = b 30 | 31 | def execute(self): 32 | print('ComplexCommand: Complex stuff should be done a receiver object', end='') 33 | self._receiver.do_something(self._a) 34 | self._receiver.do_something_else(self._b) 35 | 36 | 37 | class Receiver: 38 | def do_something(self, a): 39 | print(f'\nReceiver: working on ({a}.)', end="") 40 | 41 | def do_something_else(self, b): 42 | print(f'\nReceiver: working on ({b}.)', end="") 43 | 44 | 45 | class Invoker: 46 | _on_start = None 47 | _on_finish = None 48 | 49 | def set_on_start(self, command): 50 | self._on_start = command 51 | 52 | def set_on_finish(self, command): 53 | self._on_finish = command 54 | 55 | def operation(self): 56 | self._on_start.execute() 57 | self._on_finish.execute() 58 | 59 | 60 | invoker = Invoker() 61 | invoker.set_on_start(SimpleCommand('Say Hi!')) 62 | 63 | receiver = Receiver() 64 | invoker.set_on_finish(ComplexCommand(receiver, 'Send email', 'Save report')) 65 | 66 | invoker.operation() 67 | 68 | -------------------------------------------------------------------------------- /04_design patterns in python/03_abstract factory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Abstract Factory 3 | - Abstract Factory Pattern serves to provide an interface for creating related/dependent 4 | objects without need to specify their actual class. 5 | 6 | Car => Benz, Bmw => Suv, Coupe 7 | benz suv => gla 8 | bmw suv => x1 9 | benz coupe => cls 10 | bmw coupe => m2 11 | """ 12 | from abc import ABC, abstractmethod 13 | 14 | 15 | class Car(ABC): # Abstract Factory 16 | @abstractmethod 17 | def call_suv(self): 18 | pass 19 | 20 | @abstractmethod 21 | def call_coupe(self): 22 | pass 23 | 24 | 25 | class Benz(Car): # Concrete Factory1 26 | def call_suv(self): 27 | return Gla() 28 | 29 | def call_coupe(self): 30 | return Cls() 31 | 32 | 33 | class Bmw(Car): # Concrete Factory2 34 | def call_suv(self): 35 | return X1() 36 | 37 | def call_coupe(self): 38 | return M2() 39 | 40 | 41 | class Suv(ABC): # Abstract Product A 42 | @abstractmethod 43 | def create_suv(self): 44 | pass 45 | 46 | 47 | class Coupe(ABC): # Abstract Product B 48 | @abstractmethod 49 | def create_coupe(self): 50 | pass 51 | 52 | 53 | class Gla(Suv): # Concrete Product A1 54 | def create_suv(self): 55 | print('This is your suv benz gla...') 56 | 57 | 58 | class X1(Suv): # Concrete Product A2 59 | def create_suv(self): 60 | print('This is your suv bmw x1...') 61 | 62 | 63 | class Cls(Coupe): # Concrete Product B1 64 | def create_coupe(self): 65 | print('This is your coupe benz cls...') 66 | 67 | 68 | class M2(Coupe): # Concrete Product B2 69 | def create_coupe(self): 70 | print('This is your coupe bmw m2...') 71 | 72 | 73 | def client_suv(order): # Client 74 | brands = { 75 | 'benz': Benz, 76 | 'bmw': Bmw 77 | } 78 | suv = brands[order]().call_suv() 79 | suv.create_suv() 80 | 81 | def client_coupe(order): # Client 82 | brands = { 83 | 'benz': Benz, 84 | 'bmw': Bmw 85 | } 86 | coupe = brands[order]().call_coupe() 87 | coupe.create_coupe() 88 | 89 | 90 | client_coupe('benz') 91 | -------------------------------------------------------------------------------- /01_OOP_in_python/04 - Class vs Static Methods/main.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | 4 | class Item: 5 | pay_rate = 0.8 # The pay rate after 20% discount 6 | all = [] 7 | def __init__(self, name: str, price: float, quantity=0): 8 | # Run validations to the received arguments 9 | assert price >= 0, f"Price {price} is not greater than or equal to zero!" 10 | assert quantity >= 0, f"Quantity {quantity} is not greater or equal to zero!" 11 | 12 | # Assign to self object 13 | self.name = name 14 | self.price = price 15 | self.quantity = quantity 16 | 17 | # Actions to execute 18 | Item.all.append(self) 19 | 20 | def calculate_total_price(self): 21 | return self.price * self.quantity 22 | 23 | def apply_discount(self): 24 | self.price = self.price * self.pay_rate 25 | 26 | # class method coluld be accessed only from class level 27 | # As we want to call this method to create an instace, we need to 28 | # define it as a class method. 29 | @classmethod 30 | def instantiate_from_csv(cls): 31 | # NOTE: change the directory to yours 32 | with open('items.csv', 'r') as f: 33 | reader = csv.DictReader(f) 34 | items = list(reader) 35 | 36 | for item in items: 37 | Item( 38 | name=item.get('name'), 39 | price=float(item.get('price')), 40 | quantity=int(item.get('quantity')), 41 | ) 42 | 43 | @staticmethod 44 | def is_integer(num): 45 | # We will count out the floats that are point zero 46 | # For i.e: 5.0, 10.0 47 | if isinstance(num, float): 48 | # Count out the floats that are point zero 49 | return num.is_integer() 50 | elif isinstance(num, int): 51 | return True 52 | else: 53 | return False 54 | 55 | def __repr__(self): 56 | return f"Item('{self.name}', {self.price}, {self.quantity})" -------------------------------------------------------------------------------- /01_OOP_in_python/06 - Getters and Setters/item.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | 4 | class Item: 5 | pay_rate = 0.8 # The pay rate after 20% discount 6 | all = [] 7 | def __init__(self, name: str, price: float, quantity=0): 8 | # Run validations to the received arguments 9 | assert price >= 0, f"Price {price} is not greater than or equal to zero!" 10 | assert quantity >= 0, f"Quantity {quantity} is not greater or equal to zero!" 11 | 12 | # Assign to self object 13 | self.__name = name 14 | self.price = price 15 | self.quantity = quantity 16 | 17 | # Actions to execute 18 | Item.all.append(self) 19 | 20 | @property 21 | # Property Decorator = Read-Only Attribute 22 | def name(self): 23 | return self.__name 24 | 25 | @name.setter 26 | def name(self, value): 27 | if len(value) > 10: 28 | raise Exception("The name is too long!") 29 | else: 30 | self.__name = value 31 | 32 | def calculate_total_price(self): 33 | return self.price * self.quantity 34 | 35 | def apply_discount(self): 36 | self.price = self.price * self.pay_rate 37 | 38 | @classmethod 39 | def instantiate_from_csv(cls): 40 | with open('items.csv', 'r') as f: 41 | reader = csv.DictReader(f) 42 | items = list(reader) 43 | 44 | for item in items: 45 | Item( 46 | name=item.get('name'), 47 | price=float(item.get('price')), 48 | quantity=int(item.get('quantity')), 49 | ) 50 | 51 | @staticmethod 52 | def is_integer(num): 53 | # We will count out the floats that are point zero 54 | # For i.e: 5.0, 10.0 55 | if isinstance(num, float): 56 | # Count out the floats that are point zero 57 | return num.is_integer() 58 | elif isinstance(num, int): 59 | return True 60 | else: 61 | return False 62 | 63 | def __repr__(self): 64 | return f"{self.__class__.__name__}('{self.name}', {self.price}, {self.quantity})" -------------------------------------------------------------------------------- /04_design patterns in python/15_mediator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mediator 3 | - a behavioral design pattern that lets you reduce chaotic dependencies between objects. 4 | The pattern restricts direct communications between the objects and forces them to collaborate 5 | only via a mediator object. 6 | """ 7 | import abc 8 | 9 | 10 | class AbstractMediator(abc.ABC): 11 | 12 | @abc.abstractmethod 13 | def notify(self, sender, event): 14 | pass 15 | 16 | 17 | class ConcreteMediator(AbstractMediator): 18 | def __init__(self, comp_a, comp_b): 19 | self.comp_a = comp_a 20 | self.comp_a.set_mediator(self) 21 | self.comp_b = comp_b 22 | self.comp_b.set_mediator(self) 23 | 24 | def notify(self, sender, event): 25 | self.comp_b.receive(sender, event) 26 | 27 | 28 | class AbstractComponent(abc.ABC): 29 | def __init__(self, mediator = None): 30 | self._mediator = mediator 31 | 32 | def set_mediator(self, mediator): 33 | self._mediator = mediator 34 | 35 | @abc.abstractmethod 36 | def receive(self, sender, event): 37 | pass 38 | 39 | @abc.abstractmethod 40 | def notify(self, event): 41 | pass 42 | 43 | 44 | class Component1(AbstractComponent): 45 | def receive(self, sender, event): 46 | print(f'Component 1 received event ({sender.__class__.__name__} {event})') 47 | 48 | def notify(self, event): 49 | self._mediator.notify(self, event) 50 | 51 | def do_a(self): 52 | print('Component 1 does A.') 53 | self.notify('A') 54 | 55 | 56 | class Component2(AbstractComponent): 57 | def receive(self, sender, event): 58 | print(f'Component 2 received event ({sender.__class__.__name__} {event})') 59 | 60 | def notify(self, event): 61 | self._mediator.notify(self, event) 62 | 63 | def do_b(self): 64 | print('Component 2 does B.') 65 | self.notify('B') 66 | 67 | 68 | class Component3(AbstractComponent): 69 | def receive(self, sender, event): 70 | print(f'Component 3 received event ({sender.__class__.__name__} {event})') 71 | 72 | def notify(self, event): 73 | self._mediator.notify(self, event) 74 | 75 | def do_c(self): 76 | print('Component 3 does C.') 77 | self.notify('C') 78 | 79 | 80 | c1 = Component1() 81 | c2 = Component2() 82 | c3 = Component3() 83 | 84 | mediator = ConcreteMediator(c1, c2) 85 | 86 | c1.do_a() 87 | -------------------------------------------------------------------------------- /04_design patterns in python/README.md: -------------------------------------------------------------------------------- 1 | ## Design Patterns in Python 2 | 3 | This GitHub repository contains examples of various design patterns implemented in Python. Design patterns are proven solutions to commonly occurring problems in software design. They provide a reusable template to solve specific issues and can help developers avoid common pitfalls and improve the maintainability of their code. 4 | 5 | The patterns included in this repository are: 6 | 7 | - [**Singleton Pattern**](./01_singleton.py): Ensures that only one instance of a class is created and provides a global point of access to that instance. 8 | 9 | - [**Factory Pattern**](./02_factory.py): Provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. 10 | 11 | - [**Observer Pattern**](./14_observer.py): Allows objects to be notified when a change occurs in another object. 12 | 13 | - [**Decorator Pattern**](./07_decorator.py): Allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. 14 | 15 | - [**Strategy Pattern**](./17_strategy.py): Defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime. 16 | 17 | - [**Command Pattern**](./02_factory.py): Encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. 18 | 19 | - [**Facade Pattern**](./08_facade.py): Provides a simplified interface to a larger body of code, such as a class library. 20 | 21 | - [**Adapter Pattern**](./06_adapter.py): Allows objects with incompatible interfaces to collaborate. 22 | 23 | - [**Template Method Pattern**](./16_template%20method.py): Defines the skeleton of an algorithm in a superclass but lets subclasses override specific steps of the algorithm without changing its structure. 24 | 25 | Each design pattern is implemented in a separate Python file, which includes a brief description of the pattern, its purpose, and an example of how it can be used in practice. 26 | 27 | Feel free to use and modify these code examples for your own projects or to further your understanding of software design patterns. Contributions and feedback are always welcome. -------------------------------------------------------------------------------- /01_OOP_in_python/07 - OOP Principles/item.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | 4 | class Item: 5 | pay_rate = 0.8 # The pay rate after 20% discount 6 | all = [] 7 | def __init__(self, name: str, price: float, quantity=0): 8 | # Run validations to the received arguments 9 | assert price >= 0, f"Price {price} is not greater than or equal to zero!" 10 | assert quantity >= 0, f"Quantity {quantity} is not greater or equal to zero!" 11 | 12 | # Assign to self object 13 | self.__name = name 14 | self.__price = price 15 | self.quantity = quantity 16 | 17 | # Actions to execute 18 | Item.all.append(self) 19 | 20 | @property 21 | def price(self): 22 | return self.__price 23 | 24 | def apply_discount(self): 25 | self.__price = self.__price * self.pay_rate 26 | 27 | def apply_increment(self, increment_value): 28 | self.__price = self.__price + self.__price * increment_value 29 | 30 | @property 31 | def name(self): 32 | # Property Decorator = Read-Only Attribute 33 | return self.__name 34 | 35 | @name.setter 36 | def name(self, value): 37 | if len(value) > 10: 38 | raise Exception("The name is too long!") 39 | else: 40 | self.__name = value 41 | 42 | def calculate_total_price(self): 43 | return self.__price * self.quantity 44 | 45 | @classmethod 46 | def instantiate_from_csv(cls): 47 | with open('items.csv', 'r') as f: 48 | reader = csv.DictReader(f) 49 | items = list(reader) 50 | 51 | for item in items: 52 | Item( 53 | name=item.get('name'), 54 | price=float(item.get('price')), 55 | quantity=int(item.get('quantity')), 56 | ) 57 | 58 | @staticmethod 59 | def is_integer(num): 60 | # We will count out the floats that are point zero 61 | # For i.e: 5.0, 10.0 62 | if isinstance(num, float): 63 | # Count out the floats that are point zero 64 | return num.is_integer() 65 | elif isinstance(num, int): 66 | return True 67 | else: 68 | return False 69 | 70 | def __repr__(self): 71 | return f"{self.__class__.__name__}('{self.name}', {self.__price}, {self.quantity})" 72 | 73 | -------------------------------------------------------------------------------- /01_OOP_in_python/05 - Class Inheritance/main.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | 4 | class Item: 5 | pay_rate = 0.8 # The pay rate after 20% discount 6 | all = [] 7 | def __init__(self, name: str, price: float, quantity=0): 8 | # Run validations to the received arguments 9 | assert price >= 0, f"Price {price} is not greater than or equal to zero!" 10 | assert quantity >= 0, f"Quantity {quantity} is not greater or equal to zero!" 11 | 12 | # Assign to self object 13 | self.name = name 14 | self.price = price 15 | self.quantity = quantity 16 | 17 | # Actions to execute 18 | Item.all.append(self) 19 | 20 | def calculate_total_price(self): 21 | return self.price * self.quantity 22 | 23 | def apply_discount(self): 24 | self.price = self.price * self.pay_rate 25 | 26 | @classmethod 27 | def instantiate_from_csv(cls): 28 | with open('items.csv', 'r') as f: 29 | reader = csv.DictReader(f) 30 | items = list(reader) 31 | 32 | for item in items: 33 | Item( 34 | name=item.get('name'), 35 | price=float(item.get('price')), 36 | quantity=int(item.get('quantity')), 37 | ) 38 | 39 | @staticmethod 40 | def is_integer(num): 41 | # We will count out the floats that are point zero 42 | # For i.e: 5.0, 10.0 43 | if isinstance(num, float): 44 | # Count out the floats that are point zero 45 | return num.is_integer() 46 | elif isinstance(num, int): 47 | return True 48 | else: 49 | return False 50 | 51 | def __repr__(self): 52 | return f"{self.__class__.__name__}('{self.name}', {self.price}, {self.quantity})" 53 | 54 | 55 | class Phone(Item): 56 | def __init__(self, name: str, price: float, quantity=0, broken_phones=0): 57 | # Call to super function to have access to all attributes / methods 58 | super().__init__( 59 | name, price, quantity 60 | ) 61 | 62 | # Run validations to the received arguments 63 | assert broken_phones >= 0, f"Broken Phones {broken_phones} is not greater or equal to zero!" 64 | 65 | # Assign to self object 66 | self.broken_phones = broken_phones 67 | 68 | phone1 = Phone("jscPhonev10", 500, 5, 1) 69 | 70 | print(Item.all) -------------------------------------------------------------------------------- /02_multi threading in python/10_event.py: -------------------------------------------------------------------------------- 1 | ''' 2 | In Python's threading module, an event object is a synchronization primitive that allows threads 3 | to communicate with each other. It provides a simple mechanism for threads to wait for a specific 4 | event to occur. 5 | 6 | The Event class in the threading module represents an event object. 7 | It has two main methods: wait() and set(). 8 | 9 | wait(): This method blocks the thread until the event is set. If the event is already set, 10 | the thread continues execution immediately. 11 | 12 | set(): This method sets the event, allowing any waiting threads to proceed. 13 | If multiple threads are waiting for the event, only one of them will proceed, 14 | and the rest will continue to wait until the event is set again. 15 | 16 | Here's an example that demonstrates the usage of an event object: 17 | ''' 18 | 19 | import threading 20 | import time 21 | 22 | def wait_for_event(event): 23 | print("Thread waiting for event.") 24 | event.wait() 25 | print("Thread received event.") 26 | 27 | # Create an event object 28 | event = threading.Event() 29 | 30 | # Create and start a thread that waits for the event 31 | thread = threading.Thread(target=wait_for_event, args=(event,)) 32 | thread.start() 33 | 34 | # Perform some tasks 35 | print("Main thread performing some tasks...") 36 | 37 | # Wait for a few seconds 38 | time.sleep(2) 39 | 40 | # Set the event to signal the waiting thread 41 | event.set() 42 | 43 | # Wait for the thread to complete 44 | thread.join() 45 | 46 | print("Main thread finished.") 47 | 48 | 49 | ''' 50 | In this example, the wait_for_event function represents a thread that waits for the 51 | event to be set. It calls event.wait() to block until the event is set. 52 | The main thread sets the event after a delay of 2 seconds using event.set(). 53 | 54 | When the event is set, the waiting thread proceeds and prints "Thread received event." 55 | Finally, the main thread waits for the waiting thread to finish using thread.join(), 56 | and "Main thread finished." is printed. 57 | 58 | Note that if the event is already set when event.wait() is called, 59 | the thread calling wait() will continue without blocking. 60 | If you want to reset the event after it is set, you can use the clear() method on 61 | the event object. 62 | ''' -------------------------------------------------------------------------------- /04_design patterns in python/05_builder.py: -------------------------------------------------------------------------------- 1 | """ 2 | builder 3 | - Builder is a creational design pattern that lets you construct complex objects step by step. 4 | The pattern allows you to produce different types and representations of an object using the same 5 | construction code. 6 | """ 7 | import abc 8 | 9 | 10 | class Car: # Product 11 | def __init__(self): 12 | self._wheel = None 13 | self._engine = None 14 | self._body = None 15 | 16 | def set_wheel(self, wheel): 17 | self._wheel = wheel 18 | 19 | def set_body(self, body): 20 | self._body = body 21 | 22 | def set_engine(self, engine): 23 | self._engine = engine 24 | 25 | def detail(self): 26 | print(f'Body: {self._body.shape}') 27 | print(f'Engine: {self._engine.hp}') 28 | print(f'Wheel: {self._wheel.size}') 29 | 30 | 31 | class AbstractBuilder(abc.ABC): # Abstract Builder 32 | 33 | @abc.abstractmethod 34 | def get_engine(self): pass 35 | 36 | @abc.abstractmethod 37 | def get_wheel(self): pass 38 | 39 | @abc.abstractmethod 40 | def get_body(self): pass 41 | 42 | 43 | class Benz(AbstractBuilder): # Concrete Builder 1 44 | def get_body(self): 45 | body = Body() 46 | body.shape = 'Suv' 47 | return body 48 | 49 | def get_engine(self): 50 | engine = Engine() 51 | engine.hp = 500 52 | return engine 53 | 54 | def get_wheel(self): 55 | wheel = Wheel() 56 | wheel.size = 22 57 | return wheel 58 | 59 | 60 | class Bmw(AbstractBuilder): # Concrete Builder 2 61 | def get_body(self): 62 | body = Body() 63 | body.shape = 'Sedan' 64 | return body 65 | 66 | def get_engine(self): 67 | engine = Engine() 68 | engine.hp = 340 69 | return engine 70 | 71 | def get_wheel(self): 72 | wheel = Wheel() 73 | wheel.size = 20 74 | return wheel 75 | 76 | 77 | class Director: 78 | _builder = None 79 | 80 | def set_builder(self, builder): 81 | self._builder = builder 82 | 83 | def construct(self): 84 | car = Car() 85 | 86 | body = self._builder.get_body() 87 | car.set_body(body) 88 | 89 | wheel = self._builder.get_wheel() 90 | car.set_wheel(wheel) 91 | 92 | engine = self._builder.get_engine() 93 | car.set_engine(engine) 94 | 95 | return car 96 | 97 | 98 | class Wheel: size = None 99 | class Body: shape = None 100 | class Engine: hp = None 101 | 102 | 103 | def client_builder(builder): 104 | builders = { 105 | 'bmw': Bmw, 106 | 'benz': Benz 107 | } 108 | 109 | car = builders[builder]() 110 | director = Director() 111 | director.set_builder(car) 112 | result = director.construct() 113 | return result.detail() 114 | 115 | 116 | client_builder('bmw') 117 | -------------------------------------------------------------------------------- /04_design patterns in python/19_memento.py: -------------------------------------------------------------------------------- 1 | """ 2 | Memento 3 | - a behavioral design pattern that lets you save and restore the previous state of an object. 4 | """ 5 | import abc 6 | from datetime import datetime 7 | from random import sample 8 | from string import ascii_letters 9 | 10 | 11 | class Originator: 12 | _state = None 13 | 14 | def __init__(self, state): 15 | self._state = state 16 | print(f'Originator: My initial state is: {self._state}') 17 | 18 | def do_something(self): 19 | print('Originator: I am doing something important.') 20 | self._state = self._generate_random_string(30) 21 | print(f'Originator: my state has changed to: {self._state}') 22 | 23 | def _generate_random_string(self, length): 24 | return ''.join(sample(ascii_letters, length)) 25 | 26 | def save(self): 27 | return ConcreteMemento(self._state) 28 | 29 | def restore(self, memento): 30 | self._state = memento.get_state() 31 | print(f'Originator: state has changed to: {self._state}') 32 | 33 | 34 | class Memento(abc.ABC): 35 | @abc.abstractmethod 36 | def get_name(self): 37 | pass 38 | 39 | @abc.abstractmethod 40 | def get_date(self): 41 | pass 42 | 43 | 44 | class ConcreteMemento(Memento): 45 | def __init__(self, state): 46 | self._state = state 47 | self._date = str(datetime.now()) 48 | 49 | def get_state(self): 50 | return self._state 51 | 52 | def get_name(self): 53 | return f'{self._date} / {self._state}' 54 | 55 | def get_date(self): 56 | return self._date 57 | 58 | 59 | class Caretaker: 60 | def __init__(self, originator): 61 | self._originator = originator 62 | self._mementos = [] 63 | 64 | def backup(self): 65 | print('\nCareTaker: saving originator state...') 66 | self._mementos.append(self._originator.save()) 67 | 68 | def undo(self): 69 | if not len(self._mementos): 70 | return None 71 | 72 | memento = self._mementos.pop() 73 | print(f'CareTaker: Restoring state to: {memento.get_name()}') 74 | try: 75 | self._originator.restore(memento) 76 | except Exception: 77 | self.undo() 78 | 79 | def show_history(self): 80 | print('CareTaker: here is the list of mementos: ') 81 | for memento in self._mementos: 82 | print(memento.get_name()) 83 | 84 | 85 | originator = Originator('first-state') 86 | caretaker = Caretaker(originator) 87 | 88 | caretaker.backup() 89 | originator.do_something() 90 | 91 | caretaker.backup() 92 | originator.do_something() 93 | 94 | caretaker.backup() 95 | originator.do_something() 96 | 97 | print() 98 | caretaker.show_history() 99 | 100 | print() 101 | 102 | caretaker.undo() 103 | 104 | caretaker.undo() 105 | caretaker.undo() 106 | 107 | caretaker.undo() 108 | -------------------------------------------------------------------------------- /02_multi threading in python/08_semaphore_and_boundedsemaphore.py: -------------------------------------------------------------------------------- 1 | ''' 2 | In Python, a semaphore is a synchronization primitive provided by the threading module. 3 | It is used to control access to a shared resource or a limited number of resources. 4 | Semaphores allow multiple threads to access a resource simultaneously up to a specified limit. 5 | ''' 6 | 7 | ''' 8 | In this example, a semaphore with a limit of 3 resources is created using threading.Semaphore(3). 9 | The worker_func represents the work that needs to be performed by the worker threads. 10 | Each worker thread first acquires a resource from the semaphore using semaphore.acquire(). 11 | If a resource is available (counter > 0), it proceeds with its work. Otherwise, 12 | if all resources are currently in use (counter = 0), the thread will be blocked until a 13 | resource becomes available. 14 | 15 | After completing its work, the thread releases the resource back to the semaphore using 16 | semaphore.release(). This allows another thread to acquire the resource and continue its work. 17 | 18 | In this example, even though there are more than 3 worker threads, 19 | only 3 threads can acquire resources from the semaphore simultaneously. 20 | The other threads will be blocked until resources become available. 21 | 22 | Semaphores are useful in scenarios where you want to limit the number of concurrent 23 | accesses to a particular resource or when you want to control the number of threads 24 | that can execute a specific section of code simultaneously. 25 | ''' 26 | 27 | 28 | 29 | import threading 30 | 31 | # Create a semaphore with a limit of 3 resources 32 | semaphore = threading.Semaphore(3) 33 | 34 | ''' 35 | In Python, BoundedSemaphore is a variant of the Semaphore class provided by the threading module. 36 | It behaves similarly to a regular semaphore but with an added upper bound on the number of available 37 | resources. This upper bound restricts the number of release() calls that can be made on the 38 | BoundedSemaphore. 39 | ''' 40 | semaphore = threading.BoundedSemaphore(3) 41 | 42 | def worker_func(worker_id): 43 | # Acquire a resource from the semaphore 44 | semaphore.acquire() 45 | print(f"Worker {worker_id} acquired a resource") 46 | 47 | # Perform some work 48 | print(f"Worker {worker_id} is working...") 49 | 50 | # Release the resource back to the semaphore 51 | semaphore.release() 52 | print(f"Worker {worker_id} released the resource") 53 | 54 | # Create multiple worker threads 55 | num_workers = 5 56 | worker_threads = [] 57 | for i in range(num_workers): 58 | worker_thread = threading.Thread(target=worker_func, args=(i,)) 59 | worker_threads.append(worker_thread) 60 | worker_thread.start() 61 | 62 | # Wait for the worker threads to complete 63 | for worker_thread in worker_threads: 64 | worker_thread.join() 65 | -------------------------------------------------------------------------------- /02_multi threading in python/07_dead_lock_and_Rlock.py: -------------------------------------------------------------------------------- 1 | ''' 2 | what is dead lock: 3 | A deadlock is a situation that can occur in multithreaded or multiprocessing programs 4 | where two or more threads or processes are unable to proceed because each is waiting 5 | for the other to release a resource or take some action. As a result, the program 6 | becomes stuck and cannot make progress. 7 | 8 | In Python, deadlocks can occur when multiple threads or processes acquire locks or resources 9 | in a way that creates a circular dependency. This situation can arise when the threads or 10 | processes acquire multiple locks or resources and the order of acquisition is not consistent 11 | across all threads or processes. 12 | 13 | How to solve a dead lock: 14 | In Python, RLock stands for "reentrant lock," which is a variation of the standard Lock object 15 | provided by the threading module. The RLock class allows multiple consecutive acquisitions 16 | of the lock by the same thread without causing a deadlock. It provides a reentrant mechanism, 17 | meaning a thread can acquire the lock multiple times and must release it the same number of 18 | times to fully unlock it. 19 | ''' 20 | 21 | import threading 22 | 23 | # Two resources (locks) 24 | resource1_lock = threading.Lock() 25 | resource2_lock = threading.Lock() 26 | 27 | # to fix the dead lock issue in this example, we need to use Rlock: 28 | 29 | # resource1_lock = threading.RLock() 30 | # resource2_lock = threading.RLock() 31 | 32 | 33 | 34 | def thread1_func(): 35 | with resource1_lock: 36 | print("Thread 1 acquired resource 1") 37 | with resource2_lock: 38 | print("Thread 1 acquired resource 2") 39 | 40 | def thread2_func(): 41 | with resource2_lock: 42 | print("Thread 2 acquired resource 2") 43 | with resource1_lock: 44 | print("Thread 2 acquired resource 1") 45 | 46 | # Create two threads 47 | thread1 = threading.Thread(target=thread1_func) 48 | thread2 = threading.Thread(target=thread2_func) 49 | 50 | # Start the threads 51 | thread1.start() 52 | thread2.start() 53 | 54 | # Wait for the threads to complete 55 | thread1.join() 56 | thread2.join() 57 | 58 | ''' 59 | In this example, there are two threads that acquire two resources (resource1_lock and resource2_lock). 60 | thread1 first acquires resource1_lock and then attempts to acquire resource2_lock, while thread2 first 61 | acquires resource2_lock and then attempts to acquire resource1_lock. If the timing is unfortunate, 62 | it can lead to a deadlock situation. 63 | If thread1 acquires resource1_lock and thread2 acquires resource2_lock simultaneously, 64 | both threads will be blocked when they try to acquire the second resource, resulting in a deadlock. 65 | Each thread is waiting for the other to release the resource it needs, preventing both threads 66 | from making progress. 67 | ''' -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Certainly! Here's an example of a CONTRIBUTING.md file for a GitHub repository: 2 | 3 | # Contributing to advanced_python 4 | 5 | Thank you for your interest in contributing to advanced_python! We appreciate any contributions, whether they are bug fixes, feature enhancements, or documentation improvements. This document outlines the process for contributing to the project to ensure a smooth collaboration. 6 | 7 | ## Getting Started 8 | 9 | To get started with contributing, follow these steps: 10 | 11 | 1. Fork the repository on GitHub. 12 | 2. Clone your forked repository locally. 13 | 3. Create a new branch for your changes: `git checkout -b my-branch`. 14 | 4. Make your desired changes, ensuring they adhere to the project's guidelines. 15 | 5. Test your changes thoroughly. 16 | 6. Commit your changes: `git commit -m "Brief description of your changes"`. 17 | 7. Push your branch to your forked repository: `git push origin my-branch`. 18 | 8. Open a pull request (PR) against the main branch of the original repository. 19 | 20 | ## Guidelines and Best Practices 21 | 22 | When contributing to the project, please adhere to the following guidelines and best practices: 23 | 24 | - Follow the existing coding style and conventions used in the project. 25 | - Write clear, concise, and well-documented code. 26 | - Include tests for your changes, ensuring they pass successfully. 27 | - Provide a descriptive and informative PR title and description. 28 | - If your changes address any issues or feature requests, reference them in your PR. 29 | - Be responsive to any feedback or comments on your PR and make the necessary updates. 30 | 31 | ## Code of Conduct 32 | 33 | Contributors are expected to follow the project's [Code of Conduct](CODE_OF_CONDUCT.md). Please ensure that all interactions and contributions are respectful and inclusive. 34 | 35 | ## Issue Tracker 36 | 37 | If you encounter any bugs, have feature requests, or want to suggest improvements, please check the [issue tracker](link_to_issue_tracker) to see if it has already been reported or discussed. If not, feel free to open a new issue. 38 | 39 | ## Communication 40 | 41 | If you have any questions, need clarifications, or want to discuss your ideas before contributing, you can reach out to the project maintainers through [communication channels like Slack, Discord, or Gitter]. 42 | 43 | ## License 44 | 45 | By contributing to this project, you agree that your contributions will be licensed under the same license as the project (specified in the repository's LICENSE file). If you are unsure or have any concerns, please contact the project maintainers for clarification. 46 | 47 | We appreciate your time and effort in contributing to [Repository Name]! Your contributions help improve the project and are greatly valued. 48 | 49 | --- 50 | This CONTRIBUTING.md file provides guidelines and instructions for contributing to a GitHub repository. It covers the steps to get started, guidelines and best practices, code of conduct, issue tracking, communication channels, and licensing information. Contributors are encouraged to follow these guidelines to ensure a smooth collaboration and maintain the project's quality and integrity. 51 | -------------------------------------------------------------------------------- /03_asyncronous in python (asyncio)/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Asynchronous Programming with asyncio 3 | 4 | This repository demonstrates the usage of `asyncio`, a Python library for writing asynchronous code using coroutines, event loops, and tasks. 5 | 6 | ## What is asyncio? 7 | 8 | `asyncio` is a built-in Python library that enables the development of concurrent and asynchronous applications. It provides a framework for writing single-threaded concurrent code using coroutines, multiplexing I/O access over sockets and other resources, running network clients and servers, and other related primitives. 9 | 10 | With `asyncio`, you can write code that efficiently handles concurrent operations without the need for multiple threads or callbacks. It leverages the `async` and `await` syntax introduced in Python 3.5 to define coroutines, which are special functions that can be paused and resumed. This allows you to write non-blocking, cooperative multitasking code that can handle I/O-bound operations efficiently. 11 | 12 | ## Features of asyncio 13 | 14 | - **Coroutines**: `asyncio` allows you to write asynchronous code using coroutines. Coroutines are functions defined with the `async` keyword and can be suspended using the `await` keyword. 15 | 16 | - **Event Loop**: `asyncio` provides an event loop, which is a central execution component that schedules and runs coroutines, handles I/O operations, and manages callbacks. 17 | 18 | - **Tasks**: Tasks are higher-level abstractions over coroutines. They represent the execution of a coroutine in an event loop. Tasks can be used to track the progress and retrieve results from coroutines. 19 | 20 | - **Concurrency**: `asyncio` enables efficient concurrent programming. It supports running multiple coroutines concurrently within a single thread, allowing you to achieve concurrency without the need for multiple threads or processes. 21 | 22 | - **Network Operations**: `asyncio` includes components for network programming, such as clients and servers. It provides support for TCP, UDP, SSL, and other protocols. 23 | 24 | - **Integration**: `asyncio` can be integrated with other libraries and frameworks. It provides compatibility with existing codebases and allows you to combine asynchronous code with synchronous code as needed. 25 | 26 | ## Getting Started 27 | 28 | To run the examples in this repository, make sure you have Python 3.7 or later installed. Clone the repository and install the required dependencies using the following steps: 29 | 30 | 1. Clone the repository: 31 | 32 | ```bash 33 | git clone https://github.com/barzansaeedpour/advanced_python.git 34 | ``` 35 | 36 | 2. Navigate to the project directory: 37 | 38 | ```bash 39 | cd "asyncronous in python (asyncio)" 40 | ``` 41 | 42 | 3. Create a virtual environment (optional but recommended): 43 | 44 | ```bash 45 | python3 -m venv venv 46 | ``` 47 | 48 | 4. Activate the virtual environment: 49 | 50 | - **Windows**: 51 | 52 | ```bash 53 | venv\Scripts\activate 54 | ``` 55 | 56 | - **Unix/macOS**: 57 | 58 | ```bash 59 | source venv/bin/activate 60 | ``` 61 | 62 | 5. Install the required dependencies: 63 | 64 | ```bash 65 | pip install -r requirements.txt 66 | ``` 67 | 68 | 6. Run the desired example script: 69 | 70 | ```bash 71 | python example.py 72 | ``` 73 | 74 | ## Examples 75 | 76 | This repository includes several examples that demonstrate various aspects of `asyncio`: 77 | 78 | 1. `example1.py`: Basic example showcasing the usage of `async` and `await` to define coroutines. 79 | 80 | 2. `example2.py`: Demonstrates the event loop and the execution of multiple coroutines concurrently. 81 | 82 | 3. `example3.py`: Illustrates the usage of tasks to track the execution of coroutines. 83 | 84 | 4. `example4.py`: Shows how to perform asynchronous network operations with `asyncio`. 85 | 86 | Feel free to explore and modify the examples to further understand the capabilities of `asyncio`. 87 | 88 | ## -------------------------------------------------------------------------------- /01_OOP_in_python/README.md: -------------------------------------------------------------------------------- 1 | # Object-Oriented Programming Principles in Python 2 | 3 | This repository provides examples of implementing key object-oriented programming principles using Python. Each principle is explained and demonstrated with code examples. 4 | 5 | ## Principles Covered 6 | 7 | 1. [Encapsulation](#encapsulation) 8 | 2. [Inheritance](#inheritance) 9 | 3. [Polymorphism](#polymorphism) 10 | 4. [Abstraction](#abstraction) 11 | 5. [Composition](#composition) 12 | 13 | ## Encapsulation 14 | 15 | Encapsulation refers to the bundling of data and methods (behavior) within an object. It ensures that the internal state of an object is protected and can be accessed or modified only through defined interfaces (methods). Encapsulation promotes data hiding, abstraction, and modular design. 16 | 17 | Example: 18 | ```python 19 | class Car: 20 | def __init__(self, make, model): 21 | self.make = make 22 | self.model = model 23 | self._mileage = 0 24 | 25 | def drive(self, distance): 26 | self._mileage += distance 27 | 28 | def get_mileage(self): 29 | return self._mileage 30 | 31 | car = Car("Toyota", "Camry") 32 | car.drive(100) 33 | print(car.get_mileage()) # Output: 100 34 | ``` 35 | 36 | ## Inheritance 37 | 38 | Inheritance enables the creation of new classes (derived classes) based on existing classes (base or parent classes). The derived classes inherit the attributes and behaviors of the base class, allowing code reuse and the establishment of hierarchical relationships. Inheritance supports the "is-a" relationship and promotes code extensibility and flexibility. 39 | 40 | Example: 41 | ``` python 42 | class Shape: 43 | def area(self): 44 | pass 45 | 46 | class Rectangle(Shape): 47 | def __init__(self, length, width): 48 | self.length = length 49 | self.width = width 50 | 51 | def area(self): 52 | return self.length * self.width 53 | 54 | rectangle = Rectangle(5, 3) 55 | print(rectangle.area()) # Output: 15 56 | ``` 57 | 58 | ## Polymorphism 59 | 60 | Polymorphism allows objects of different types to be treated as instances of a common superclass. It enables methods to be overridden in derived classes, allowing the same method name to exhibit different behaviors based on the specific object type at runtime. Polymorphism promotes code flexibility, modularity, and facilitates the implementation of complex systems. 61 | 62 | Example: 63 | ``` python 64 | class Animal: 65 | def sound(self): 66 | pass 67 | 68 | class Dog(Animal): 69 | def sound(self): 70 | return "Woof!" 71 | 72 | class Cat(Animal): 73 | def sound(self): 74 | return "Meow!" 75 | 76 | def make_sound(animal): 77 | print(animal.sound()) 78 | 79 | dog = Dog() 80 | cat = Cat() 81 | 82 | make_sound(dog) # Output: Woof! 83 | make_sound(cat) # Output: Meow! 84 | ``` 85 | 86 | ## Abstraction 87 | 88 | Abstraction focuses on representing essential features of objects while hiding unnecessary details. It allows developers to create abstract classes or interfaces that define a common structure and behavior without specifying the specific implementation. Abstraction helps in managing complexity, separating concerns, and providing a high-level view of the system. 89 | 90 | Example: 91 | ``` python 92 | from abc import ABC, abstractmethod 93 | 94 | class Vehicle(ABC): 95 | @abstractmethod 96 | def start(self): 97 | pass 98 | 99 | class Car(Vehicle): 100 | def start(self): 101 | return "Car started!" 102 | 103 | car = Car() 104 | print(car.start()) # Output: Car started! 105 | 106 | ``` 107 | 108 | ## Composition 109 | 110 | Composition refers to constructing complex objects or systems by combining simpler objects or components. It involves creating relationships between objects where one object contains or is composed of other objects. Composition promotes code reusability, flexibility, and enables the creation of modular and loosely coupled systems. 111 | 112 | Example: 113 | ``` python 114 | class Engine: 115 | def __init__(self, horsepower): 116 | self.horsepower = horsepower 117 | 118 | class Car: 119 | def __init__(self, make, model, engine): 120 | self.make = make 121 | self.model = model 122 | self.engine = engine 123 | 124 | def get_engine_horsepower(self): 125 | return self.engine.horsepower 126 | 127 | engine = Engine(200) 128 | car = Car("Toyota", "Camry", engine) 129 | print(car.get_engine_horsepower()) # Output: 200 130 | ``` 131 | 132 | ## Contributing 133 | 134 | Feel free to contribute by adding more examples, improving existing code, or suggesting enhancements. 135 | 136 | ## License 137 | 138 | This project is licensed under the [MIT License](LICENSE). 139 | -------------------------------------------------------------------------------- /02_multi threading in python/README.md: -------------------------------------------------------------------------------- 1 | # Multi-Threading in Python 2 | 3 | This repository provides examples and explanations of multi-threading in Python. Multi-threading is a powerful technique that allows concurrent execution of multiple threads within a single program, enabling efficient utilization of system resources and improving overall performance. 4 | 5 | ## Table of Contents 6 | 7 | - [Introduction](#introduction) 8 | - [Getting Started](#getting-started) 9 | - [Basic Concepts](#basic-concepts) 10 | - [Thread Synchronization](#thread-synchronization) 11 | - [Thread Safety](#thread-safety) 12 | 13 | 14 | ## Introduction 15 | 16 | Python provides a built-in `threading` module that allows developers to create and manage threads easily. With multi-threading, you can execute multiple threads simultaneously, each performing different tasks concurrently. This can be particularly beneficial in scenarios where tasks can run independently and do not have strict dependencies. 17 | 18 | ## Getting Started 19 | 20 | To use the `threading` module in Python, ensure that you have Python installed on your system. You can download Python from the official website: [Python.org](https://www.python.org/). 21 | 22 | ## Basic Concepts 23 | 24 | 1. **Process vs Thread**: A process and a thread are both units of execution in a computer program, but they have some fundamental differences: 25 | 26 | A process is an instance of a program that is being executed. It has its own memory space, resources, and a unique process identifier (PID). A thread, on the other hand, is a unit of execution within a process. Multiple threads can exist within a single process, sharing the same memory space and resources. 27 | 28 | | | Process | Thread | 29 | | ------------- | ----------------------------------------- | ----------------------------------------- | 30 | | Execution | Independent of other processes | Runs within the context of a process | 31 | | Memory | Own separate memory space | Shares memory space with other threads | 32 | | Communication | Inter-process communication mechanisms | Direct function calls, shared memory | 33 | | Overhead | Higher | Lower | 34 | | Scalability | Limited scalability due to resource usage | Highly scalable due to lightweight nature | 35 | 36 | 2. **Thread Creation**: Threads are created by instantiating the `Thread` class from the `threading` module. You can either directly instantiate the class or subclass it and override the `run()` method. 37 | 38 | ```python 39 | import threading 40 | 41 | # Instantiating the Thread class 42 | thread = threading.Thread(target=my_function, args=(arg1, arg2)) 43 | 44 | # Subclassing the Thread class 45 | class MyThread(threading.Thread): 46 | def run(self): 47 | # Code to be executed in the thread 48 | ... 49 | ``` 50 | 51 | 3. **Starting and Joining Threads**: To start the execution of a thread, call the `start()` method. This will invoke the `run()` method of the thread. Use the `join()` method to wait for the thread to complete its execution. 52 | 53 | ```python 54 | # Starting a thread 55 | thread.start() 56 | 57 | # Joining a thread 58 | thread.join() 59 | ``` 60 | 61 | 4. **Thread Synchronization**: When multiple threads access shared resources, thread synchronization techniques such as locks, semaphores, and conditions are used to prevent race conditions and ensure data consistency. 62 | 63 | 5. **Thread Safety**: Thread safety refers to designing code in a way that allows multiple threads to execute concurrently without causing any issues. Python provides several thread-safe data structures and synchronization primitives in the `threading` module. 64 | 65 | ## Thread Synchronization 66 | 67 | Thread synchronization is crucial when multiple threads share and modify the same resources to avoid conflicts and ensure data integrity. Python offers various synchronization primitives, including locks, semaphores, and conditions, to handle thread synchronization. 68 | 69 | Here are some commonly used synchronization primitives: 70 | 71 | - **Lock**: A lock is a basic synchronization primitive that allows only one thread to acquire the lock at a time. Other threads that attempt to acquire the lock will be blocked until it's released. 72 | 73 | - **Semaphore**: A semaphore is a synchronization object that restricts the number of threads accessing a resource simultaneously. It maintains a count and allows a specified number of threads to enter while blocking others. 74 | 75 | - **Condition**: A condition variable allows threads to wait for a specific condition to be satisfied. It provides methods like `wait()`, `notify()`, and `notifyAll()` to coordinate the execution of multiple threads. 76 | 77 | - **Event**: An event is a synchronization primitive that allows threads to wait for an event to occur. It provides methods like `set()` and `wait()` to signal and wait for the occurrence of an event. 78 | 79 | ## Thread Safety 80 | 81 | Thread safety is essential to ensure that concurrent execution of multiple threads does not lead to unexpected 82 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | --------------------------------------------------------------------------------