├── .gitignore ├── Examples ├── a_package │ ├── __init__.py │ └── a_module.py ├── example_01_tokenize.py ├── example_02_parse.py ├── example_03_view_pyc.py ├── example_04_compile.py ├── example_05_memory.py ├── example_06_refcount.py ├── example_07_cyclical_refs.py ├── example_08_scope.py ├── example_09_globals.py ├── example_10_nonlocal.py ├── example_11_variables.py ├── example_12_copy.py ├── example_13_inspecting.py ├── example_14_attrs.py ├── example_15_typehints.py ├── example_16_module_dunders.py ├── example_17_function_dunders.py ├── example_18_class_dunders.py ├── example_19_slots.py ├── example_20_basic_methods.py ├── example_21_numbers.py ├── example_22_containers.py ├── example_23_iterator.py ├── example_24_attr_access.py ├── example_25_context_manager.py ├── example_26_descriptors.py ├── example_27_async.py ├── example_28_metaprogramming.py ├── hello.py ├── timer.py └── xyzmodule.c ├── README.md ├── docs ├── PyCharm_interpreter.md ├── WINSETPATH.md └── img │ ├── pycharm_python_1.png │ ├── pycharm_python_2.png │ ├── pycharm_python_3a.png │ ├── pycharm_python_3b.png │ ├── pycharm_python_4.png │ ├── pycharm_python_5a.png │ └── pycharm_python_5b.png ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE configuration 2 | .idea 3 | .vscode 4 | 5 | # Environments 6 | venv*/ 7 | .venv*/ 8 | 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # Packaging / C extensions 15 | build/ 16 | dist/ 17 | *.so 18 | xyz.egg-info/ 19 | 20 | # Notebook 21 | .ipynb_checkpoints/ -------------------------------------------------------------------------------- /Examples/a_package/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ariannedee/python-under-the-hood/a2a2599491232259747faff91ba799d3b2108922/Examples/a_package/__init__.py -------------------------------------------------------------------------------- /Examples/a_package/a_module.py: -------------------------------------------------------------------------------- 1 | class AClass: 2 | def a_method(self, arg_1, arg_2='a', *, key_1='b', key_2='c'): 3 | print(f'regular args: {arg_1=}, {arg_2=}') 4 | print(f'keyword only args: {key_1=}, {key_2=}') 5 | 6 | def inner_function(): 7 | print(f'Enclosed vars: {arg_1=}, {arg_2=}, {key_1}, {key_2}') 8 | 9 | return inner_function 10 | 11 | 12 | if __name__ == '__main__': 13 | AClass().a_method(1) 14 | -------------------------------------------------------------------------------- /Examples/example_01_tokenize.py: -------------------------------------------------------------------------------- 1 | """ 2 | Takes some code as a string and prints out the tokens. 3 | 4 | To tokenize a file, run $ python3 –m tokenize 5 | """ 6 | import io 7 | import token 8 | import tokenize 9 | 10 | code = """ 11 | def greet(name): 12 | print("Hello " + name) 13 | """ # Code to execute 14 | 15 | as_bytes = io.BytesIO(code.encode('utf-8')) # Turn into encoded bytes 16 | tokens = list(tokenize.tokenize(as_bytes.readline)) # Get tokens 17 | 18 | # Print TokenInfo objects 19 | for tok in tokens: 20 | print(tok) 21 | 22 | print() 23 | 24 | # Print summary of tokens 25 | for tok in tokens: 26 | print(f"{token.tok_name[tok.exact_type]:<20}{tok.string}") 27 | -------------------------------------------------------------------------------- /Examples/example_02_parse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Takes some code as a string and displays the abstract syntax tree (AST) 3 | produced after parsing it. 4 | """ 5 | import ast 6 | 7 | code = """ 8 | def greet(name): 9 | print("Hello " + name) 10 | """ # Code to execute 11 | 12 | tree = ast.parse(code) 13 | 14 | print(ast.dump(tree, indent=4)) 15 | -------------------------------------------------------------------------------- /Examples/example_03_view_pyc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Code reproduced from Stack Overflow: 3 | Q: Given a python .pyc file, is there a tool that let me view the bytecode? 4 | 5 | Answer link: https://stackoverflow.com/a/67428655 6 | 7 | To generate a .pyc file, run $ python -m py_compile or $ python -m compileall 8 | 9 | To view the byte-code for a file, run $ python3 -m dis 10 | """ 11 | import binascii 12 | import dis 13 | import marshal 14 | import platform 15 | import struct 16 | import sys 17 | import time 18 | 19 | 20 | def view_pyc_file(path): 21 | """Read and display a content of the Python`s bytecode in a pyc-file.""" 22 | 23 | with open(path, 'rb') as file: 24 | 25 | magic = file.read(4) 26 | bit_field = None 27 | timestamp = None 28 | hashstr = None 29 | size = None 30 | 31 | if sys.version_info.major == 3 and sys.version_info.minor >= 7: 32 | bit_field = int.from_bytes(file.read(4), byteorder=sys.byteorder) 33 | if 1 & bit_field == 1: 34 | hashstr = file.read(8) 35 | else: 36 | timestamp = file.read(4) 37 | size = file.read(4) 38 | size = struct.unpack('I', size)[0] 39 | elif sys.version_info.major == 3 and sys.version_info.minor >= 3: 40 | timestamp = file.read(4) 41 | size = file.read(4) 42 | size = struct.unpack('I', size)[0] 43 | else: 44 | timestamp = file.read(4) 45 | 46 | code = marshal.load(file) 47 | 48 | magic = binascii.hexlify(magic).decode('utf-8') 49 | timestamp = time.asctime(time.localtime(struct.unpack('I', timestamp)[0])) 50 | 51 | dis.disassemble(code) 52 | 53 | print('-' * 80) 54 | print( 55 | 'Python version: {}\nMagic code: {}\nTimestamp: {}\nSize: {}\nHash: {}\nBitfield: {}' 56 | .format(platform.python_version(), magic, timestamp, size, hashstr, bit_field) 57 | ) 58 | 59 | 60 | if __name__ == '__main__': 61 | view_pyc_file('__pycache__/hello.cpython-312.pyc') -------------------------------------------------------------------------------- /Examples/example_04_compile.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generate a code object from a code string. 3 | """ 4 | from dis import disassemble 5 | 6 | code_str = f""" 7 | def greet(name): 8 | print('Hello ' + name) 9 | 10 | greet(world) 11 | greet(hi) 12 | """ 13 | 14 | code = compile(code_str, '', 'exec') 15 | 16 | print(code) 17 | print(code.co_code) # Byte instructions 18 | print(code.co_names) # Names used in the code 19 | print(code.co_consts) # Constants/literals used in the code 20 | 21 | disassemble(code) # Display human-readable instructions 22 | -------------------------------------------------------------------------------- /Examples/example_05_memory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Each function call creates a new layer on the call stack. Local variables live here. 3 | The value of the variables (objects) are stored in the heap. 4 | 5 | Once a function returns, its layer on the stack is removed and its memory is freed. 6 | Once the number of references to a heap object drops to 0, the memory is freed. 7 | """ 8 | 9 | 10 | def func_2(x): 11 | x = x - 1 12 | return x 13 | 14 | 15 | def func_1(x): 16 | x = x * 2 17 | y = func_2(x) 18 | return y 19 | 20 | 21 | x = 2 22 | y = func_1(x) 23 | -------------------------------------------------------------------------------- /Examples/example_06_refcount.py: -------------------------------------------------------------------------------- 1 | """Check the number of references to an object. Will always be at least 1.""" 2 | import sys 3 | 4 | a = [] 5 | print(sys.getrefcount(a)) # 2 6 | 7 | b = a 8 | print(sys.getrefcount(a)) # 3 9 | 10 | c = [a] 11 | print(sys.getrefcount(a)) # 4 12 | 13 | del b 14 | print(sys.getrefcount(a)) # 3 15 | 16 | del a 17 | 18 | print(c) 19 | print(sys.getrefcount(c[0])) # 2 20 | -------------------------------------------------------------------------------- /Examples/example_07_cyclical_refs.py: -------------------------------------------------------------------------------- 1 | """Check reference count on objects that have been deleted""" 2 | import ctypes 3 | 4 | 5 | # this class will allow us to access the object from memory (even after it is deleted) 6 | class PyObject(ctypes.Structure): 7 | _fields_ = [("refcnt", ctypes.c_long)] 8 | 9 | 10 | my_dict_1 = {} 11 | my_dict_2 = {} 12 | 13 | # Create cyclical reference 14 | my_dict_1['dict2'] = my_dict_2 15 | my_dict_2['dict1'] = my_dict_1 16 | 17 | obj_address = id(my_dict_1) # get memory address of my_dict_1 18 | 19 | print(PyObject.from_address(obj_address).refcnt) # my_dict_1 has 2 references 20 | 21 | del my_dict_1, my_dict_2 # deleting both objects 22 | print(PyObject.from_address(obj_address).refcnt) # my_dict_1 has 1 reference still 23 | -------------------------------------------------------------------------------- /Examples/example_08_scope.py: -------------------------------------------------------------------------------- 1 | """Show the variables at different scopes: 2 | - local (in function) 3 | - enclosed (in outer function) 4 | - global 5 | - builtins 6 | 7 | Makes use of locals(), globals() and vars() builtin functions 8 | """ 9 | from math import pi 10 | from pprint import pprint 11 | 12 | r = -1000 13 | 14 | 15 | def circumference(r): 16 | 17 | def diameter(): 18 | d = 2 * r 19 | 20 | print('inner:') 21 | pprint(locals()) 22 | print() 23 | 24 | return d 25 | 26 | area = diameter() * pi 27 | 28 | print('outer:') 29 | pprint(locals()) 30 | print() 31 | 32 | return area 33 | 34 | 35 | area_1 = circumference(1) 36 | area_2 = circumference(2) 37 | 38 | print('global:') 39 | pprint(globals()) 40 | print() 41 | 42 | print('builtins:') 43 | pprint(vars(__builtins__)) 44 | -------------------------------------------------------------------------------- /Examples/example_09_globals.py: -------------------------------------------------------------------------------- 1 | """The global keyword can be used to alter a global variable within a function""" 2 | num_calls = 0 3 | 4 | 5 | def a_function(): 6 | global num_calls 7 | num_calls += 1 8 | print(f"Function called {num_calls} times") 9 | 10 | 11 | a_function() 12 | a_function() 13 | a_function() 14 | -------------------------------------------------------------------------------- /Examples/example_10_nonlocal.py: -------------------------------------------------------------------------------- 1 | """The nonlocal keyword can be used to alter an outer function variable within an inner function 2 | If an outer function goes out of scope, the inner function can still reference it. 3 | """ 4 | 5 | 6 | def outer(var): 7 | def inner(): 8 | nonlocal var 9 | var += 1 10 | return var 11 | 12 | print(var) 13 | return inner 14 | 15 | 16 | closure = outer(5) 17 | 18 | print(closure()) 19 | print(closure()) 20 | print(closure()) 21 | print(closure()) 22 | -------------------------------------------------------------------------------- /Examples/example_11_variables.py: -------------------------------------------------------------------------------- 1 | """Altering immutable variables means pointing to a new object at a new location. 2 | In CPython, the id is an int representing the memory location of an object.""" 3 | 4 | 5 | def print_id(obj): 6 | print(f"""ID of {obj}: 7 | {id(obj)} 8 | {hex(id(obj))} 9 | {super(type(obj), obj).__repr__()} 10 | """) 11 | 12 | 13 | # These are the same because they refer to the same object 14 | x = 256 15 | y = x 16 | print_id(x) 17 | print_id(y) 18 | 19 | # These are different because x refers to a new object 20 | # Same if object is interred or immortal (-5 <= x <= 255) 21 | x += 1 22 | print_id(x) 23 | print_id(y) 24 | 25 | # These may or may not be the same depending on the initial value of x 26 | y += 1 27 | print_id(x) 28 | print_id(y) 29 | -------------------------------------------------------------------------------- /Examples/example_12_copy.py: -------------------------------------------------------------------------------- 1 | """Copying mutable variables. 2 | Shows variable reassignment, shallow copy and deep copy.""" 3 | import copy 4 | 5 | a = [[]] 6 | b = a # References same object as a 7 | c = copy.copy(a) # Shallow copy - id(c) != id(a) but contents are the same objects 8 | d = copy.deepcopy(a) # Deep copy - all contents are new objects 9 | 10 | a.append(1) 11 | a[0].append(2) 12 | print(a) # [[2], 1] 13 | print(b) # [[2], 1] 14 | print(c) # [[2]] 15 | print(d) # [[]] 16 | -------------------------------------------------------------------------------- /Examples/example_13_inspecting.py: -------------------------------------------------------------------------------- 1 | """ 2 | vars - https://docs.python.org/3/library/functions.html#vars 3 | vars(): returns locals() 4 | vars(obj): returns the __dict__ mapping of attributes of an object 5 | 6 | dir - https://docs.python.org/3/library/functions.html#dir 7 | dir(): returns a list of names in the current scope (keys of locals() dict) 8 | dir(obj): attempts to return a list of valid attributes for the object (i.e. obj.{attr}) 9 | """ 10 | import inspect 11 | from pprint import pprint 12 | 13 | import hello 14 | 15 | 16 | class ClassA: 17 | att1 = 1 18 | 19 | def __init__(self): 20 | self.att2 = 2 21 | 22 | 23 | class ClassB(ClassA): 24 | att3 = 3 25 | 26 | def __init__(self): 27 | super().__init__() 28 | self.att4 = 4 29 | 30 | 31 | inst_A = ClassA() 32 | inst_B = ClassB() 33 | x = 5 34 | 35 | 36 | def a_func(y): 37 | """A function""" 38 | z = 7 39 | print('\nlocal vars()') 40 | pprint(vars()) 41 | print('\nlocal dir()') 42 | pprint(dir()) 43 | 44 | 45 | # Locals 46 | a_func(6) 47 | a_func.num_calls = 1 48 | 49 | # Globals 50 | print('\n--- global vars() ---\n') 51 | pprint(vars()) 52 | print('\n--- global dir() ---\n') 53 | pprint(dir(), compact=True) 54 | 55 | # Function 56 | print('\n--- vars(a_func) ---\n') 57 | print(vars(a_func)) 58 | print('\n--- local dir(a_func) ---\n') 59 | pprint(dir(a_func), compact=True) 60 | 61 | # Module 62 | print('\n--- vars(hello) ---\n') 63 | module_dict = vars(hello) 64 | del module_dict['__builtins__'] # Don't display builtins 65 | pprint(module_dict) 66 | print('\n--- local dir(hello) ---\n') 67 | pprint(dir(hello), compact=True) 68 | 69 | # Class 70 | print('\n--- vars(ClassA) ---\n') 71 | pprint(vars(ClassA)) 72 | print('\n--- dir(ClassA) ---\n') 73 | pprint(dir(ClassA), compact=True) 74 | print('\n--- vars(inst_A) ---\n') 75 | pprint(vars(inst_A)) 76 | print('\n--- dir(inst_A) ---\n') 77 | pprint(dir(inst_A), compact=True) 78 | 79 | # Subclass 80 | print('\n--- vars(ClassB) ---\n') 81 | pprint(vars(ClassB)) 82 | print('\n--- dir(ClassB) ---\n') 83 | pprint(dir(ClassB), compact=True) 84 | print('\n--- getmembers(ClassB) ---\n') 85 | pprint(inspect.getmembers(ClassB)) 86 | print('\n--- vars(inst_B) ---\n') 87 | pprint(vars(inst_B)) 88 | print('\n--- dir(inst_B) ---\n') 89 | pprint(dir(inst_B), compact=True) 90 | print('\n--- getmembers(inst_B) ---\n') 91 | pprint(inspect.getmembers(inst_B)) 92 | print('\n--- getmembers(inst_B) that are routines ---\n') 93 | pprint(inspect.getmembers(inst_B, predicate=inspect.isroutine)) 94 | print('\n--- getmembers(inst_B) that are not routines (data attributes) ---\n') 95 | pprint(inspect.getmembers(inst_B, predicate=lambda x: not inspect.isroutine(x))) 96 | 97 | # Code 98 | print('\n--- getsource(ClassB) ---\n') 99 | print(inspect.getsource(ClassB)) 100 | print('\n--- getsourcelines(ClassB) ---\n') 101 | pprint(inspect.getsourcelines(ClassB)) 102 | -------------------------------------------------------------------------------- /Examples/example_14_attrs.py: -------------------------------------------------------------------------------- 1 | """Use builtin attr functions to create dynamic attribute names on a custom class.""" 2 | from datetime import date, timedelta 3 | from pprint import pprint 4 | 5 | 6 | class Goal: 7 | """Track a goal that you want to perform for X days""" 8 | def __init__(self, name, num_days, days_since_start=0): 9 | """ 10 | :param name: Description of the goal 11 | :param num_days: # of days to perform it (in a row) 12 | :param days_since_start: # days since you started the goal (e.g. already in-progress) 13 | """ 14 | self.name = name 15 | self.num_days = num_days 16 | self.start_date = date.today() - timedelta(days=days_since_start) 17 | for i in range(num_days): 18 | setattr(self, f'day_{i + 1}', False) 19 | 20 | def complete(self, day, note=None): 21 | setattr(self, f'day_{day}', True) 22 | if note: 23 | setattr(self, f'day_{day}_note', note) 24 | 25 | def get_summary(self): 26 | result = f"{self.name} for {self.num_days} days" 27 | days_since_start = (date.today() - self.start_date).days 28 | 29 | for day in range(1, min(days_since_start, self.num_days) + 1): 30 | result += '\n' + self.get_day_summary(day) 31 | 32 | return result 33 | 34 | def get_day_summary(self, day): 35 | if not hasattr(self, f'day_{day}'): 36 | return None 37 | 38 | if getattr(self, f'day_{day}'): 39 | complete = '✅' 40 | else: 41 | complete = '❌' 42 | if hasattr(self, f'day_{day}' + '_note'): 43 | note = '- ' + getattr(self, f'day_{day}' + '_note') 44 | else: 45 | note = '' 46 | return f"Day {day}: {complete} {note}" 47 | 48 | 49 | if __name__ == '__main__': 50 | goal = Goal('Exercise 20 mins', num_days=20, days_since_start=7) 51 | goal.complete(1, 'ran 20 mins') 52 | goal.complete(3, 'HIIT class') 53 | goal.complete(5) 54 | goal.complete(6, 'walked to dinner') 55 | 56 | pprint(vars(goal)) 57 | 58 | print(goal.get_summary()) 59 | -------------------------------------------------------------------------------- /Examples/example_15_typehints.py: -------------------------------------------------------------------------------- 1 | """Make use of type hints for warnings and code completion in your IDE""" 2 | import requests 3 | 4 | 5 | from typing import List, Tuple 6 | 7 | from requests import Response 8 | 9 | URL = "https://nominatim.openstreetmap.org/reverse" 10 | 11 | Coordinates = Tuple[float, float] 12 | 13 | 14 | # Function type annotations 15 | def get_location_name(coords: Coordinates) -> tuple: 16 | params = {'lat': coords[0], 'lon': coords[1], 'format': 'json', 'zoom': 12} 17 | response: Response = requests.get(URL, params) 18 | location_details: dict = response.json() 19 | address: dict = location_details['address'] 20 | return ((address.get('city') or address.get('town')), address['state'], address['country']) 21 | 22 | 23 | # Variable with builtin types 24 | vancouver: tuple = (45.63, -122.67) 25 | dublin: tuple[float, float] = (40.10, -83.11) 26 | 27 | print(get_location_name(vancouver)) # PyCharm shows type mismatch because tuple != tuple[float, float] 28 | 29 | # Using types from typing module 30 | london: Tuple[float, float] = (42.99, -81.25) 31 | cities: List[Tuple[float, float]] = [vancouver, dublin] 32 | 33 | # Using type aliases (Coordinates defined at top of file) 34 | brooklyn: Coordinates = (-37.816, 144.842) 35 | more_cities: List[Coordinates] 36 | more_cities = [dublin, london, brooklyn] 37 | 38 | for city in more_cities: 39 | print(get_location_name(city)) 40 | 41 | # Can define type before assignment 42 | a: str 43 | 44 | try: 45 | # Check if your IDE supports annotation code suggestions 46 | a.upper() 47 | except Exception as e: 48 | print(repr(e)) 49 | -------------------------------------------------------------------------------- /Examples/example_16_module_dunders.py: -------------------------------------------------------------------------------- 1 | """Show the dunder variables available on modules""" 2 | from pprint import pprint 3 | 4 | import hello 5 | from a_package import a_module 6 | 7 | my_str: str = 'abc' 8 | 9 | 10 | print() 11 | print("This module's variables:") 12 | pprint(vars()) 13 | 14 | print() 15 | print("hello's variables:") 16 | pprint(vars(hello)) 17 | 18 | print() 19 | print("a_package.a_module's variables:") 20 | pprint(vars(a_module)) 21 | -------------------------------------------------------------------------------- /Examples/example_17_function_dunders.py: -------------------------------------------------------------------------------- 1 | """Show the dunder variables available on functions""" 2 | from a_package.a_module import AClass 3 | 4 | 5 | def a_function(name: str, shout: bool = False) -> str: 6 | """Greet someone""" 7 | greeting: str = "Hello, " + name 8 | if shout: 9 | greeting = greeting.upper() 10 | return greeting 11 | 12 | 13 | a_function.an_attribute = 'Added to __dict__' 14 | 15 | 16 | # Top-level function with type annotations 17 | print() 18 | print('a_function dunders') 19 | for dunder in dir(a_function): 20 | value = getattr(a_function, dunder) 21 | if not callable(value) and dunder not in '__builtins__': 22 | print(f'{dunder}: {value}') 23 | 24 | 25 | # Method in a package module 26 | print() 27 | print('a_method dunders on a class') 28 | for dunder in dir(AClass.a_method): 29 | value = getattr(AClass.a_method, dunder) 30 | if not callable(value) and dunder not in ['__builtins__', '__globals__']: 31 | print(f'{dunder}: {value}') 32 | 33 | # Instance method in a package module 34 | print() 35 | print('a_method dunders on an instance') 36 | for dunder in dir(AClass().a_method): 37 | value = getattr(AClass().a_method, dunder) 38 | if not callable(value) or dunder in ['__class__', '__func__']: 39 | print(f'{dunder}: {value}') 40 | 41 | # Closures (inner function has access to outer function variables, even after outer function is closed) 42 | a_module_inner = AClass().a_method(1, 2, key_1=3, key_2=4) # Returns the inner function 43 | 44 | print() 45 | print('inner dunders') 46 | print(f'{a_module_inner.__closure__=}') 47 | print(f'{a_module_inner.__name__=}') 48 | print(f'{a_module_inner.__qualname__=}') 49 | 50 | a_module_inner() 51 | -------------------------------------------------------------------------------- /Examples/example_18_class_dunders.py: -------------------------------------------------------------------------------- 1 | """Show the dunder variables available on custom classes""" 2 | 3 | 4 | class Class1: 5 | """Class 1 docs""" 6 | class_attr_1: str = '1' 7 | 8 | def __init__(self, param: int): 9 | self.inst_attr_1: int = param 10 | 11 | 12 | class Class2(Class1): 13 | class_attr_2: list = [] 14 | 15 | def __init__(self, *args, **kwargs): 16 | super().__init__(*args, **kwargs) 17 | self.inst_attr_2: dict = {} 18 | 19 | @property 20 | def property_2(self): 21 | return 2 22 | 23 | 24 | print(f"""{Class1.__name__=} 25 | {Class1.__module__=} 26 | {Class1.__doc__=} 27 | {Class1.__annotations__=} 28 | {Class1.__type_params__=} 29 | {Class1.__dict__=} 30 | {Class1.__bases__=} 31 | {Class1.__mro__=} 32 | """) 33 | 34 | print(f"""{Class2.__name__=} 35 | {Class2.__module__=} 36 | {Class2.__doc__=} 37 | {Class2.__annotations__=} 38 | {Class2.__type_params__=} 39 | {Class2.__dict__=} 40 | {Class2.__bases__=} 41 | {Class2.__mro__=} 42 | """) 43 | 44 | instance_1 = Class1(1) 45 | instance_2 = Class2(2) 46 | 47 | print(f"""{instance_1.__module__=} 48 | {instance_1.__doc__=} 49 | {instance_1.__annotations__=} 50 | {instance_1.__dict__=} 51 | {instance_1.__class__=} 52 | """) 53 | 54 | print(f"""{instance_2.__module__=} 55 | {instance_2.__doc__=} 56 | {instance_2.__annotations__=} 57 | {instance_2.__dict__=} 58 | {instance_2.__class__=} 59 | """) 60 | -------------------------------------------------------------------------------- /Examples/example_19_slots.py: -------------------------------------------------------------------------------- 1 | """Use the __slots__ attribute to define what attributes a class can have, without relying on __dict__.""" 2 | 3 | 4 | class WithoutSlots: 5 | def __init__(self): 6 | self.attr_1 = 1 7 | self.attr_2 = 2 8 | 9 | 10 | class WithSlots: 11 | __slots__ = ('attr_1', 'attr_2') 12 | 13 | def __init__(self): 14 | self.attr_1 = 1 15 | self.attr_2 = 2 16 | 17 | 18 | without_slots = WithoutSlots() 19 | without_slots.attr_3 = 3 20 | print(without_slots.__dict__) 21 | 22 | with_slots = WithSlots() 23 | try: 24 | with_slots.attr_3 = 'Error' 25 | except AttributeError as e: 26 | print('\nTrying to set an attribute not in slots:') 27 | print(repr(e)) 28 | 29 | try: 30 | print(with_slots.__dict__) 31 | except AttributeError as e: 32 | print('\nTrying to access __dict__ when __slots__ is set:') 33 | print(repr(e)) 34 | tb = e.__traceback__ 35 | print(tb.tb_frame) 36 | -------------------------------------------------------------------------------- /Examples/example_20_basic_methods.py: -------------------------------------------------------------------------------- 1 | """Book class that implements basic class dunder methods""" 2 | import copy 3 | 4 | 5 | class Book: 6 | __lt__ = __le__ = __gt__ = __ge__ = None 7 | 8 | def __new__(cls, *args, **kwargs): 9 | print(f'__new__ called with {repr(cls)}, {repr(args)}, {repr(kwargs)}') 10 | obj = super().__new__(cls) 11 | return obj 12 | 13 | def __init__(self, title, author): 14 | self.title = title 15 | self.author = author 16 | print(f'__init__ called with {title}, {author}') 17 | 18 | def __del__(self): 19 | print(f"{self} destroyed") 20 | 21 | def __str__(self): 22 | return f"{self.title} by {self.author}" 23 | 24 | def __repr__(self): 25 | return f"Book({repr(self.title)}, {repr(self.author)})" 26 | 27 | def __eq__(self, other): 28 | if isinstance(other, Book): 29 | return self.title == other.title and self.author == other.author 30 | return False 31 | 32 | def __bool__(self): 33 | """Returns True if no values are empty strings or None""" 34 | return bool(self.title) and bool(self.author) 35 | 36 | def __hash__(self): 37 | return hash((self.title, self.author)) 38 | 39 | def __format__(self, format_spec): 40 | if not format_spec: 41 | return str(self) 42 | elif format_spec == 'u': 43 | return str(self).upper() 44 | elif format_spec == 't': 45 | return self.title 46 | elif format_spec == 'a': 47 | return self.author 48 | raise ValueError(f"Invalid format specifier {format_spec} for object of type 'Book'") 49 | 50 | 51 | if __name__ == '__main__': 52 | book1 = Book('The Odyssey', 'Homer') # __new__ and then __init__ called 53 | book2 = copy.copy(book1) 54 | print(str(book1)) # __str__ called 55 | print(book1) # __str__ also called 56 | 57 | books = [book1, book2] 58 | print(repr(book1)) # __repr__ called 59 | print(books) # __repr__ also called 60 | 61 | print(bool(book1)) # __bool__ called 62 | if book1: # __bool__ also called 63 | print("Book 1 is True-ish") 64 | 65 | if book1 == book2: # __eq__ called 66 | print("Same book") 67 | 68 | book2.title = 'The Iliad' 69 | if book1 != book2: # __eq__ called even though __neq__ not implemented 70 | print("Different book") 71 | 72 | try: 73 | book1 > book2 74 | except TypeError as e: 75 | print(repr(e)) 76 | 77 | num_reads = { 78 | book1: 1, 79 | book2: 2, 80 | } 81 | print(num_reads) # book1 == book2 and hash(book1) == hash(book2) so only 1 entry made 82 | 83 | print(f"I loved {book1:u}") # Calls __format__ with 'u' 84 | 85 | del book1 86 | print('Book 1 not destroyed yet because of reference in books') 87 | -------------------------------------------------------------------------------- /Examples/example_21_numbers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dollars class that supports number operations. 3 | - Limits values to 2 decimal places. 4 | - Supports addition between Dollars, int and float 5 | """ 6 | import pytest 7 | 8 | 9 | class Dollars: 10 | def __init__(self, dollars=0, cents=0, negative=False): 11 | try: 12 | cents = round(float(cents)) 13 | dollars = int(dollars) 14 | except (ValueError, TypeError): 15 | raise ValueError(f"Invalid integers {repr(dollars)} or {repr(cents)}") 16 | 17 | if dollars < 0 or cents < 0: 18 | raise ValueError("Cannot have negative dollar or cent values. Use negative=True.") 19 | elif cents > 99: 20 | raise ValueError("Cents must be 99 or less") 21 | 22 | self.negative = negative 23 | self.dollars = dollars 24 | self.cents = cents 25 | 26 | def __str__(self): 27 | as_str = f"${self.dollars}.{self.cents:0>2}" 28 | if self.negative: 29 | return '-' + as_str 30 | return as_str 31 | 32 | def __repr__(self): 33 | return f"Dollars(dollars={self.dollars}, cents={self.cents}, negative={self.negative})" 34 | 35 | def as_cents(self): 36 | cents = self.dollars * 100 + self.cents 37 | if self.negative: 38 | cents *= -1 39 | return cents 40 | 41 | @classmethod 42 | def from_cents(cls, cents): 43 | try: 44 | cents = float(cents) 45 | except ValueError: 46 | raise ValueError(f"Cannot create dollars from type {type(cents).__name__}") 47 | if cents < 0: 48 | negative = True 49 | cents = abs(cents) 50 | else: 51 | negative = False 52 | cents = round(cents) 53 | return Dollars(cents // 100, cents % 100, negative) 54 | 55 | def __add__(self, other): 56 | if isinstance(other, Dollars): 57 | other_as_cents = other.as_cents() 58 | elif not isinstance(other, (int, float)): 59 | raise ValueError(f"Operand '+' not implemented for Dollars and {type(other).__name__}") 60 | else: 61 | other_as_cents = other * 100 62 | total_cents = self.as_cents() + other_as_cents 63 | return Dollars.from_cents(total_cents) 64 | 65 | def __radd__(self, other): 66 | return self.__add__(other) 67 | 68 | def __iadd__(self, other): 69 | if isinstance(other, Dollars): 70 | other = other.as_cents() 71 | elif not isinstance(other, (int, float)): 72 | raise ValueError(f"Operand '+' not implemented for Dollars and {type(other).__name__}") 73 | else: 74 | other = other * 100 75 | cents = self.as_cents() + other 76 | self.dollars = int(cents // 100) 77 | self.cents = int(cents % 100) 78 | 79 | return self 80 | 81 | 82 | # ----- TEST SUITE ----- 83 | 84 | def test_creating_dollars_from_cents(): 85 | d1 = Dollars.from_cents(199.5) 86 | assert d1.dollars == 2 87 | assert d1.cents == 0 88 | assert d1.negative is False 89 | 90 | big_negative_float = -100_000_000_000_001 91 | d2 = Dollars.from_cents(big_negative_float) 92 | assert d2.dollars == 1_000_000_000_000 93 | assert d2.cents == 1 94 | assert d2.negative is True 95 | 96 | 97 | def test_dollars_as_cents(): 98 | d1 = Dollars() 99 | assert d1.as_cents() == 0 100 | 101 | d2 = Dollars(3, 33) 102 | assert d2.as_cents() == 333 103 | 104 | d3 = Dollars(1_000_000_000_000, 1, negative=True) 105 | assert d3.as_cents() == -100_000_000_000_001 106 | 107 | 108 | def test_dollars_add(): 109 | d1 = Dollars.from_cents(111) 110 | d2 = Dollars.from_cents(222) 111 | d3 = d1 + d2 112 | assert d3.dollars == 3 113 | assert d3.cents == 33 114 | 115 | d4 = d1 + 9.99 116 | assert d4.dollars == 11 117 | assert d4.cents == 10 118 | 119 | 120 | def test_dollars_radd(): 121 | d1 = Dollars.from_cents(111) 122 | d2 = 2.22 + d1 123 | assert d2.dollars == 3 124 | assert d2.cents == 33 125 | 126 | 127 | def test_dollars_iadd(): 128 | d1 = Dollars.from_cents(111) 129 | d2 = Dollars.from_cents(222) 130 | d1 += d2 131 | assert d1.dollars == 3 132 | assert d1.cents == 33 133 | 134 | d2 += 9.99 135 | assert d2.dollars == 12 136 | assert d2.cents == 21 137 | 138 | 139 | if __name__ == '__main__': 140 | pytest.main() 141 | -------------------------------------------------------------------------------- /Examples/example_22_containers.py: -------------------------------------------------------------------------------- 1 | """Emulate a sequence with a LinkedList class. 2 | Supports +, *, iteration, len(), """ 3 | from copy import copy 4 | 5 | 6 | class LinkedList: 7 | def __init__(self, values=None): 8 | self.head = None 9 | self.tail = None 10 | if values: 11 | for value in values: 12 | if isinstance(value, Node): 13 | value = value.value 14 | self.append(value) 15 | 16 | def append(self, value): 17 | if isinstance(value, Node): 18 | value = value.value 19 | node = Node(value) 20 | if self.head is None: 21 | self.head = node 22 | self.tail = node 23 | else: 24 | self.tail.next = node 25 | self.tail = node 26 | 27 | def count(self, value): 28 | num = 0 29 | for node in self: 30 | if node.value == value: 31 | num += 1 32 | return num 33 | 34 | def index(self, value): 35 | for i, node in enumerate(self): 36 | if node.value == value: 37 | return i 38 | raise ValueError(f'{repr(value)} is not in LinkedList') 39 | 40 | def extend(self, iter): 41 | self.__iadd__(iter) 42 | 43 | def insert(self, index, value): 44 | if not isinstance(index, int): 45 | raise TypeError(f"index should be type 'int' but got '{type(index).__name__}'") 46 | if index < 0: 47 | index = len(self) + index + 1 48 | elif index == 0: 49 | node = Node(value) 50 | node.next = self.head 51 | self.head = node 52 | return 53 | 54 | cur_i = 1 55 | cur_node = self.head 56 | while cur_node is not None: 57 | if cur_i == index: 58 | new_node = Node(value) 59 | next_node = cur_node.next 60 | cur_node.next = new_node 61 | new_node.next = next_node 62 | return 63 | cur_i += 1 64 | cur_node = cur_node.next 65 | new_node = Node(value) 66 | next_node = cur_node.next 67 | cur_node.next = new_node 68 | new_node.next = next_node 69 | 70 | def pop(self): 71 | ... 72 | 73 | def remove(self, value): 74 | ... 75 | 76 | def reverse(self): 77 | ... 78 | 79 | def sort(self): 80 | ... 81 | 82 | def copy(self): 83 | new_ll = LinkedList() 84 | for node in self: 85 | new_ll.append(node.value) 86 | return new_ll 87 | 88 | def __len__(self): 89 | count = 0 90 | for _ in self: 91 | count += 1 92 | return count 93 | 94 | def __getitem__(self, idx): 95 | """e.g. self[1:3]""" 96 | as_list = list(self) 97 | try: 98 | result_from_list = as_list.__getitem__(idx) 99 | except TypeError as e: 100 | raise TypeError(str(e).replace('list', 'LinkedList')) 101 | if isinstance(result_from_list, list): 102 | return LinkedList(result_from_list) 103 | return result_from_list 104 | 105 | def __setitem__(self, idx, value): 106 | # self[idx] = 'value' 107 | if isinstance(idx, slice): 108 | raise TypeError('LinkedList only supports single item assignment') 109 | as_list = [n for n in self] 110 | print(repr(as_list)) 111 | try: 112 | node_to_edit = as_list.__getitem__(idx) 113 | node_to_edit.value = value 114 | except TypeError as e: 115 | raise TypeError(str(e).replace('list', 'LinkedList')) 116 | 117 | def __delitem__(self, key): 118 | """del self[idx]""" 119 | if isinstance(key, slice): 120 | raise TypeError('LinkedList only supports single item deletion') 121 | len_list = len(self) 122 | if key < 0: 123 | key = len_list + key 124 | index = 0 125 | prev = None 126 | node = self.head 127 | while True: 128 | if key == index: 129 | if node == self.head: 130 | self.head = node.next 131 | else: 132 | prev.next = node.next 133 | return 134 | index += 1 135 | prev = node 136 | node = node.next 137 | 138 | def __iter__(self): 139 | node = self.head 140 | while node is not None: 141 | yield node 142 | node = node.next 143 | 144 | def __add__(self, other): 145 | if not is_iter(other): 146 | raise TypeError(f'can only concatenate an iterable (not {type(other).__name__}) to LinkedList') 147 | 148 | new_ll = self.copy() 149 | for val in other: 150 | new_ll.append(val) 151 | return new_ll 152 | 153 | def __radd__(self, other): 154 | other_copy = copy(other) 155 | for node in self: 156 | other_copy.append(node.value) 157 | return other_copy 158 | 159 | def __iadd__(self, other): 160 | if not is_iter(other): 161 | raise TypeError(f'can only concatenate an iterable (not {type(other).__name__}) to LinkedList') 162 | for val in other: 163 | if isinstance(val, Node): 164 | val = val.value 165 | self.append(val) 166 | return self 167 | 168 | def __mul__(self, other): 169 | new_ll = self.copy() 170 | if not isinstance(other, int): 171 | raise TypeError(f"can't multiply sequence by non-int of type '{type(other).__name__}'") 172 | 173 | if other <= 0: 174 | return LinkedList() 175 | 176 | for i in range(other): 177 | for node in self: 178 | new_ll.append(node) 179 | return new_ll 180 | 181 | def __imul__(self, other): 182 | new_ll = LinkedList() 183 | if not isinstance(other, int): 184 | raise TypeError(f"can't multiply sequence by non-int of type '{type(other).__name__}'") 185 | 186 | if other <= 0: 187 | while self.head: 188 | node = self.head 189 | self.head = self.head.next 190 | del node 191 | 192 | for i in range(other - 1): 193 | for node in self: 194 | new_ll.append(node) 195 | self.__iadd__(new_ll) 196 | return self 197 | 198 | def __contains__(self, item): 199 | for node in self: 200 | if node.value == item: 201 | return True 202 | return False 203 | 204 | def __str__(self): 205 | contents = " -> ".join(str(i) for i in self) 206 | if not contents: 207 | return f'Empty' 208 | return f'{contents}' 209 | 210 | def __repr__(self): 211 | as_list = ", ".join(repr(i) for i in self) 212 | return f"LinkedList([{as_list}])" 213 | 214 | 215 | class Node: 216 | def __init__(self, value): 217 | self.value = value 218 | self.next = None 219 | 220 | def __str__(self): 221 | return repr(self.value) 222 | 223 | def __repr__(self): 224 | return f"Node({repr(self.value)})" 225 | 226 | 227 | def is_iter(obj): 228 | try: 229 | iter(obj) 230 | return True 231 | except TypeError: 232 | return False 233 | 234 | 235 | if __name__ == '__main__': 236 | ll = LinkedList([1, 2, 3]) 237 | 238 | # __add__ 239 | print(ll + LinkedList(['a', 'b', 'c'])) 240 | 241 | # __radd__ 242 | print(['a', 'b', 'c'] + ll) 243 | 244 | # __mul__ 245 | print(ll * 4) 246 | 247 | # __iadd__ 248 | ll += ['A', 'B'] 249 | print(ll) 250 | 251 | # __getitem__, printing with = calls repr() 252 | print(f"{ll[2]=}") 253 | print(f"{ll[-2]=}") 254 | print(f"{ll[1:3]=}") 255 | print(f"{ll[::-1]=}") 256 | 257 | # __setitem__ 258 | ll[-4] = 100 259 | print(ll) 260 | 261 | # __delitem__ 262 | del ll[-2] 263 | print(ll) 264 | 265 | # __iter__ 266 | for i in ll: 267 | print(repr(i)) 268 | 269 | # __contains__ 270 | if 100 in ll: 271 | print("Found 100") 272 | 273 | # __reversed__ not implemented, but __len__ and __iter__ are 274 | for i in reversed(ll): 275 | print(i) 276 | 277 | # __imul__ 278 | ll *= 2 279 | print(ll) 280 | -------------------------------------------------------------------------------- /Examples/example_23_iterator.py: -------------------------------------------------------------------------------- 1 | """Simple LinkedList using a custom iterator for __iter__ instead of a generator. 2 | Iterators must implement __iter__ and __next__.""" 3 | class LinkedListIterator: 4 | def __init__(self, node): 5 | self.node = node 6 | 7 | def __iter__(self): 8 | return self 9 | 10 | def __next__(self): 11 | cur = self.node 12 | if cur: 13 | self.node = cur.next 14 | else: 15 | raise StopIteration 16 | return cur.value 17 | 18 | 19 | class LinkedList: 20 | def __init__(self, values=None): 21 | self.head = None 22 | self.tail = None 23 | if values: 24 | for value in values: 25 | self.append(value) 26 | 27 | def append(self, value): 28 | node = Node(value) 29 | if self.head is None: 30 | self.head = node 31 | self.tail = node 32 | else: 33 | self.tail.next = node 34 | self.tail = node 35 | 36 | def __iter__(self): 37 | return LinkedListIterator(self.head) 38 | 39 | def __str__(self): 40 | contents = " -> ".join(str(i) for i in self) 41 | return f'{contents}' 42 | 43 | def __repr__(self): 44 | as_list = ", ".join(str(i) for i in self) 45 | return f"LinkedList([{as_list}])" 46 | 47 | 48 | class Node: 49 | def __init__(self, value): 50 | self.value = value 51 | self.next = None 52 | 53 | def __str__(self): 54 | return str(self.value) 55 | 56 | def __repr__(self): 57 | return f"Node({repr(self.value)})" 58 | 59 | 60 | if __name__ == '__main__': 61 | ll = LinkedList([1, 2, 3]) 62 | print(ll) 63 | ll.append(4) 64 | for i in ll: 65 | print(i) 66 | 67 | try: 68 | for i in reversed(ll): 69 | print(i) 70 | except TypeError as e: 71 | print(repr(e)) 72 | 73 | print(repr(ll)) -------------------------------------------------------------------------------- /Examples/example_24_attr_access.py: -------------------------------------------------------------------------------- 1 | """Customizing attribute access""" 2 | 3 | 4 | class SharedAttributes: 5 | """Setting an attribute sets it on every instance of the class""" 6 | class_dict = {} 7 | 8 | def __getattribute__(self, name): 9 | print(f'__getattribute__({repr(name)})') 10 | 11 | # Avoid infinite recursion from getting self.dict_ 12 | d = object.__getattribute__(self, 'class_dict') 13 | try: 14 | return d[name] 15 | except KeyError: 16 | raise AttributeError() 17 | 18 | def __getattr__(self, name): 19 | print(f'__getattr__({repr(name)})') 20 | return -1 21 | 22 | def __setattr__(self, name, value): 23 | print(f'__setattr__({repr(name)}, {repr(value)})') 24 | d = object.__getattribute__(self, 'class_dict') 25 | d[name] = value 26 | 27 | def __delattr__(self, name): 28 | print(f'__delattr__({repr(name)})') 29 | d = object.__getattribute__(self, 'class_dict') 30 | del d[name] 31 | 32 | def __dir__(self): 33 | d = object.__getattribute__(self, 'class_dict') 34 | base_dir = object.__dir__(self) 35 | return base_dir + list(d.keys()) 36 | 37 | 38 | if __name__ == '__main__': 39 | s1 = SharedAttributes() 40 | s2 = SharedAttributes() 41 | 42 | print(s1.a) # Doesn't exist, returns -1 43 | 44 | s1.b = 10 45 | print(getattr(s1, 'b')) # getattr() calls __getattribute__ first 46 | print(s2.b) # Value exists on all instances 47 | print(dir(s1)) 48 | print(dir(s2)) 49 | 50 | del s2.b 51 | print(s1.b) 52 | print(dir(s1)) 53 | -------------------------------------------------------------------------------- /Examples/example_25_context_manager.py: -------------------------------------------------------------------------------- 1 | """ Context manager for printing to the console in a different color. 2 | Exceptions are suppressed and printed out in red. 3 | 4 | Codes are 'r', 'g', 'y', 'b' and 'p', for red, green, yellow, blue and purple. 5 | 6 | with ColorConsole(code): 7 | # prints to the console with a color defined by code 8 | """ 9 | 10 | 11 | class ColorConsole: 12 | RED = '\33[31m' 13 | GREEN = '\33[32m' 14 | YELLOW = '\33[33m' 15 | BLUE = '\033[94m' 16 | PURPLE = '\33[35m' 17 | 18 | ENDCOLOR = '\033[0m' 19 | 20 | def __init__(self, code='b'): 21 | self.code = code 22 | match code: 23 | case 'b': 24 | self.color = ColorConsole.BLUE 25 | case 'g': 26 | self.color = ColorConsole.GREEN 27 | case 'y': 28 | self.color = ColorConsole.YELLOW 29 | case 'p': 30 | self.color = ColorConsole.PURPLE 31 | case 'r': 32 | self.color = ColorConsole.RED 33 | case other: 34 | self.color = ColorConsole.ENDCOLOR 35 | print(f"Invalid code: {repr(code)}. Must be 'b', 'g', 'y', or 'r'.") 36 | 37 | def __enter__(self): 38 | print(self.color, end='') 39 | return self 40 | 41 | def __exit__(self, exc_type, exc_val, exc_tb): 42 | if exc_type: 43 | print(ColorConsole.RED, end='') 44 | print(f'{exc_type.__name__} caught in exit function:') 45 | print(f' {exc_val}') 46 | print(ColorConsole.ENDCOLOR, end='') 47 | # return True if you don't want exceptions to be propagated 48 | return True 49 | 50 | # Async functions used in example 27 51 | async def __aenter__(self): 52 | return self.__enter__() 53 | 54 | async def __aexit__(self, exc_type, exc_val, exc_tb): 55 | return self.__exit__(exc_type, exc_val, exc_tb) 56 | 57 | def __repr__(self): 58 | return f"ColorConsole({repr(self.code)})" 59 | 60 | 61 | if __name__ == '__main__': 62 | with ColorConsole('b') as value: 63 | print('I should be blue') 64 | print(value) 65 | # If an error is raised in the context, stop execution and call exit 66 | int(None) 67 | 68 | print('I should be black') 69 | -------------------------------------------------------------------------------- /Examples/example_26_descriptors.py: -------------------------------------------------------------------------------- 1 | """Descriptors 2 | MyClassDescriptor creates a descriptor set on class (default is 10) 3 | MyInstanceDescriptor creates a descriptor with the value set on an instance 4 | """ 5 | 6 | 7 | class MyClassDescriptor: 8 | def __init__(self): 9 | self.value = 10 10 | 11 | def __get__(self, instance, owner): 12 | return self.value 13 | 14 | def __set__(self, instance, value): 15 | self.value = value 16 | 17 | 18 | class MyInstanceDescriptor: 19 | def __init__(self): 20 | self.value = {} 21 | 22 | def __get__(self, instance, owner): 23 | obj = instance if instance else owner 24 | return self.value.get(obj, None) 25 | 26 | def __set__(self, instance, value): 27 | self.value[instance] = value 28 | 29 | 30 | class AClass: 31 | a = MyClassDescriptor() 32 | b = MyInstanceDescriptor() 33 | 34 | 35 | if __name__ == '__main__': 36 | obj = AClass() 37 | print("Class descriptor\n") 38 | print(f"{AClass.a=}") 39 | print(f"{obj.a=}") 40 | obj.a = 20 41 | print(f"{AClass.a=}") 42 | print(f"{obj.a=}") 43 | 44 | print("\nInstance descriptor\n") 45 | print(f"{AClass.b=}") 46 | print(f"{obj.b=}") 47 | AClass.b = 1 48 | obj.b = 2 49 | print(f"{AClass.b=}") 50 | print(f"{obj.b=}") 51 | -------------------------------------------------------------------------------- /Examples/example_27_async.py: -------------------------------------------------------------------------------- 1 | """Asynchronously retrieve cat facts using aiohttp library 2 | Uses an async context manager of ColorConsole (from example 25) 3 | """ 4 | import asyncio 5 | 6 | from aiohttp import ClientSession 7 | 8 | from Examples import timer 9 | from Examples.example_25_context_manager import ColorConsole 10 | 11 | URL = 'https://catfact.ninja/fact' 12 | 13 | 14 | async def print_fact(session): 15 | async with session.get(URL, ssl=False) as response: 16 | response = await response.json() 17 | print(response.get('fact', response.get('message', 'Error'))) 18 | 19 | 20 | async def print_some_facts(num): 21 | async with ClientSession() as session: 22 | async with ColorConsole('g'): 23 | tasks = [asyncio.create_task(print_fact(session), name=f'Task {i}') for i in range(num)] 24 | await asyncio.gather(*tasks) 25 | 26 | 27 | @timer.timeit 28 | def async_main(): 29 | loop = asyncio.new_event_loop() 30 | asyncio.set_event_loop(loop) 31 | loop.run_until_complete(print_some_facts(20)) 32 | 33 | 34 | if __name__ == '__main__': 35 | async_main() 36 | -------------------------------------------------------------------------------- /Examples/example_28_metaprogramming.py: -------------------------------------------------------------------------------- 1 | """Custom SimpleEnum implementation to show metaclasses 2 | 3 | Enum members have a name and value. 4 | Enums can be iterated over. 5 | """ 6 | import re 7 | 8 | 9 | class EnumMemberMeta(type): 10 | """Defines how the Enum members get printed""" 11 | def __new__(cls, name, bases, attrs): 12 | obj = super().__new__(cls, name, bases, attrs) 13 | obj.enum_name = name 14 | obj.name = attrs.get('name', '') 15 | obj.value = attrs.get('value', '') 16 | return obj 17 | 18 | def __str__(self): 19 | return f"{self.enum_name}.{self.name}" 20 | 21 | def __repr__(self): 22 | return f"<{self.enum_name}.{self.name}: {self.value}>" 23 | 24 | 25 | def create_enum_member(enum_name, member_name, value): 26 | """Creates an enum member with a name and value""" 27 | new_class_attrs = { 28 | 'name': member_name, 29 | 'value': value, 30 | '__annotations__': {'name': str}, 31 | } 32 | return EnumMemberMeta(enum_name, (), new_class_attrs) 33 | 34 | 35 | class EnumMeta(type): 36 | def __new__(cls, name, bases, attrs): 37 | def is_dunder(name_): return re.fullmatch(r'__\w+__', name_) 38 | 39 | # Gather all user-defined class attributes 40 | class_attrs = {} 41 | non_class_attrs = {} 42 | for attr_name, attr_value in attrs.items(): 43 | if is_dunder(attr_name): 44 | non_class_attrs[attr_name] = attr_value 45 | else: 46 | class_attrs[attr_name] = attr_value 47 | 48 | # Turn class attrs into enum members 49 | enum_attrs = {} 50 | for attr_name, attr_value in class_attrs.items(): 51 | member = create_enum_member(name, attr_name, attr_value) 52 | enum_attrs[attr_name] = member 53 | 54 | # Create new namespace __dict__ 55 | all_attrs = non_class_attrs 56 | all_attrs.update(enum_attrs) 57 | all_attrs['enum_attrs'] = enum_attrs 58 | 59 | return super().__new__(cls, name, bases, all_attrs) 60 | 61 | def __iter__(self): 62 | for member in self.enum_attrs.values(): 63 | yield member 64 | 65 | 66 | class SimpleEnum(metaclass=EnumMeta): 67 | """All implementation is in EnumMeta. 68 | Can create new class with: 69 | 1. class EnumName(SimpleEnum): 70 | ... 71 | 2. class EnumName(metaclass=EnumMeta): 72 | ... 73 | """ 74 | pass 75 | 76 | 77 | class Rating(SimpleEnum): 78 | good = 1 79 | okay = 0 80 | bad = -1 81 | 82 | 83 | print(Rating.good) # Rating.good 84 | print(repr(Rating.good)) # 85 | print(Rating.good.name.upper()) # good 86 | print(Rating.good.value) # 1 87 | 88 | for rating in Rating: 89 | print(repr(rating)) 90 | -------------------------------------------------------------------------------- /Examples/hello.py: -------------------------------------------------------------------------------- 1 | def greet(name): 2 | print('Hello ' + name) 3 | 4 | greet('world') 5 | -------------------------------------------------------------------------------- /Examples/timer.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | def timeit(func): 5 | def inner(*args, **kwargs): 6 | start = time.perf_counter() 7 | try: 8 | return func(*args, **kwargs) 9 | finally: 10 | end = time.perf_counter() 11 | print(f"Function {func.__name__} took {end - start}s") 12 | return inner 13 | -------------------------------------------------------------------------------- /Examples/xyzmodule.c: -------------------------------------------------------------------------------- 1 | /* Extending Python with C: https://docs.python.org/3/extending/extending.html 2 | Creates a module named 'xyz' that has a function 'echo' that prints the input to the console twice 3 | 4 | To install locally (without editing CPython or affecting your Python executable): 5 | $ python setup.py build_ext --inplace 6 | See setup.py for more info 7 | 8 | To add the extension to a Python executable (make sure you can compile CPython first): 9 | 1. Move this file to cpython/Modules 10 | 2. Add "xyz xyzmodule.o" to cpython/Modules/Setup.local 11 | 3. Compile Python based on your OS (https://realpython.com/cpython-source-code-guide/) 12 | 4. Run Python 13 | >> import xyz 14 | >> xyz.echo('hello world') 15 | */ 16 | 17 | #define PY_SSIZE_T_CLEAN 18 | #include 19 | 20 | static PyObject * 21 | xyz_echo(PyObject *self, PyObject *args) 22 | { 23 | const char *string; 24 | 25 | if (!PyArg_ParseTuple(args, "s", &string)) { // If the positional arguments were invalid 26 | return NULL; // Raise an exception 27 | } 28 | 29 | printf("%s\n%s\n", string, string); // Print string out twice 30 | 31 | // Returns None 32 | Py_INCREF(Py_None); 33 | return Py_None; 34 | } 35 | 36 | static PyMethodDef XyzMethods[] = { 37 | {"echo", xyz_echo, METH_VARARGS, 38 | "Print out the text twice."}, 39 | {NULL, NULL, 0, NULL} /* Sentinel */ 40 | }; 41 | 42 | static struct PyModuleDef xyzmodule = { 43 | PyModuleDef_HEAD_INIT, 44 | "xyz", /* name of module */ 45 | NULL, /* module documentation, may be NULL */ 46 | -1, /* size of per-interpreter state of the module, 47 | or -1 if the module keeps state in global variables. */ 48 | XyzMethods 49 | }; 50 | 51 | PyMODINIT_FUNC 52 | PyInit_xyz(void) 53 | { 54 | return PyModule_Create(&xyzmodule); 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Under the Hood Live Training 2 | 3 | This is the code for the *O'Reilly Live Training* - **Python Under the Hood** presented by Arianne Dee 4 | 5 | Before the class, please follow these instructions: 6 | 1. [Install Python](#1-ensure-python-39-or-higher-is-installed) 7 | 1. [Use an appropriate code editor](#2-use-an-appropriate-code-editor) 8 | 1. [Download the code](#3-download-the-course-files) 9 | 5. [Download the resources](#4-at-the-beginning-of-class-download-the-resources) 10 | 11 | ## Set up instructions 12 | ### 1. Ensure Python 3.9 or higher is installed 13 | Install the latest version here: https://www.python.org/downloads/ 14 | 15 | In *PowerShell* on Windows or *Terminal* on Mac or Linux, 16 | make sure you can access Python from the command line. 17 | 18 | 1. `$ python --version` 19 | 1. `$ python3 --version` 20 | 1. `$ python3.13 --version` (replace 3.13 with your target version number) 21 | 1. `$ py --version` on Windows 22 | 1. `$ py -3.13 --version` (replace 3.13 with your target version number) 23 | 24 | One or more of those commands should print 25 | a Python version of 3.9 or higher. 26 | 27 | If none of them do, you have to follow instructions to 28 | [add Python to your PATH variable](docs/WINSETPATH.md). 29 | 30 | ### 2. Use an appropriate code editor 31 | The instructor will demo using PyCharm Community: https://www.jetbrains.com/pycharm/download/ 32 | 33 | The example code requires being able to run Python scripts (.py files) 34 | and commands in the terminal (e.g. `$ python -m `). 35 | 36 | ### 3. Download the course files 37 | GitHub repository: https://github.com/ariannedee/python-under-the-hood 38 | 39 | #### If you know git: 40 | Clone the repository. 41 | 42 | #### If you don't know git: 43 | 1. Click the "< > Code" (green) button at the top-right of the page 44 | 2. Click "Download ZIP" 45 | 3. Unzip it and move the **python-under-the-hood-main** folder to a convenient location 46 | 47 | ### 4. Install package dependencies 48 | Optional: set up a new virtual environment 49 | 50 | Then install dependencies in `requirements.txt`. 51 | 52 | `$ pip install -r requirements.txt` 53 | 54 | ### 5. At the beginning of class, download the resources 55 | When you have signed in to the class, 56 | the **Resources** widget will have PDFs for the slides. 57 | 58 | ## FAQs 59 | 60 | ### PyCharm can't find Python 3 61 | 62 | On a Mac: 63 | - Go to **PyCharm** > **Preferences** 64 | 65 | On a PC: 66 | - Go to **File** > **Settings** 67 | 68 | Once in Settings: 69 | 1. Go to **Project: oop-python** > **Project Interpreter** 70 | 1. Look for your Python version in the Project Interpreter dropdown 71 | 1. If it's not there, click **gear icon** > **Add...** 72 | 1. In the new window, select **System Interpreter** on the left, and then look for the Python version in the dropdown 73 | 1. If it's not there, click the **...** button and navigate to your Python location 74 | - To find where Python is located, [look in these directories](docs/PATH_LOCATIONS.md) 75 | - You may have to search the internet for where Python gets installed by default on your operating system 76 | -------------------------------------------------------------------------------- /docs/PyCharm_interpreter.md: -------------------------------------------------------------------------------- 1 | # Configuring your Python virtual environment PyCharm (Community and Pro) 2 | 3 | ## 1. Open your settings/preferences 4 | 5 | 6 | 7 | ## 2. Navigate to the Project Interpreter 8 | 9 | 10 | 11 | ## 3a. Select an existing interpreter 12 | 13 | 14 | 15 | If your desired version was found, click OK. If not, go on to 3b. 16 | 17 | ## 3b. Add a new interpreter 18 | 19 | 20 | 21 | ## 4. Add a system interpreter 22 | 23 | 24 | ## 5a. Select an existing system interpreter 25 | 26 | 27 | 28 | If your desired version was found, click OK. If not, go on to 5b. 29 | 30 | ## 5b. Find your system interpreter 31 | 32 | 33 | 34 | If you’re not sure where to find it, try one of the following locations 35 | (replace 3.9 or 39 with the desired version number): 36 | 37 | ### Windows (look for python.exe) 38 | - C:\Python39 39 | - C:\Program Files\Python39 40 | - C:\Users\username\AppData\Local\Programs\Python\Python39-XX 41 | 42 | ### Mac (look for python3.9 or python3) 43 | - /usr/local/bin/ 44 | - /Library/Frameworks/Python.framework/Versions/3.9/bin/ 45 | - /usr/local/Cellar/python/3.9.X_X/bin/ 46 | - /Users/username/anaconda/bin/ 47 | - /anaconda3/bin/ 48 | 49 | ### Linux (look for python3.9 or python3) 50 | - /usr/bin/ 51 | - /usr/local/bin -------------------------------------------------------------------------------- /docs/WINSETPATH.md: -------------------------------------------------------------------------------- 1 | ## Windows set up instruction 2 | If you installed Python with the default options, 3 | you will probably need to add Python to the PATH variable. 4 | This let's your operating system know where to look for the Python executable 5 | when you try running it. 6 | 7 | To add Python to your PATH variable: 8 | 1. Find the path of **python.exe** on your system. 9 | [Look in these directories](PATH_LOCATIONS.md) or search for it. 10 | 11 | 1. Open *System Properties* and click on the *Advanced* tab 12 | 13 | 1. Click on *Environment Variables* 14 | 15 | 1. Under *System variables* find and click on the *Path* variable 16 | 17 | 1. In edit mode, go to the end of the line and add **;C:\Python38** or whatever folder *python.exe* is in. 18 | Note the semicolon before the path; this will separate it from the previous path. 19 | 20 | To check that the PATH variable was set properly: 21 | 1. Open the *Command Prompt* application in Windows 22 | or *Terminal* on Mac or Linux 23 | 24 | 1. Type `python --version` and press enter 25 | 26 | 1. Type `python3 --version` and press enter 27 | 28 | 1. Type `py --version` and press enter (Windows) 29 | 30 | 1. At least one of these commands should print 31 | a Python version of 3.6 or higher 32 | (whichever version you just downloaded) 33 | 34 | If you are having problems: 35 | 36 | Search the internet for "**Add python to path on Windows *10 / Vista / XP / etc***" 37 | to find instructions for your version of Windows. 38 | -------------------------------------------------------------------------------- /docs/img/pycharm_python_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ariannedee/python-under-the-hood/a2a2599491232259747faff91ba799d3b2108922/docs/img/pycharm_python_1.png -------------------------------------------------------------------------------- /docs/img/pycharm_python_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ariannedee/python-under-the-hood/a2a2599491232259747faff91ba799d3b2108922/docs/img/pycharm_python_2.png -------------------------------------------------------------------------------- /docs/img/pycharm_python_3a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ariannedee/python-under-the-hood/a2a2599491232259747faff91ba799d3b2108922/docs/img/pycharm_python_3a.png -------------------------------------------------------------------------------- /docs/img/pycharm_python_3b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ariannedee/python-under-the-hood/a2a2599491232259747faff91ba799d3b2108922/docs/img/pycharm_python_3b.png -------------------------------------------------------------------------------- /docs/img/pycharm_python_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ariannedee/python-under-the-hood/a2a2599491232259747faff91ba799d3b2108922/docs/img/pycharm_python_4.png -------------------------------------------------------------------------------- /docs/img/pycharm_python_5a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ariannedee/python-under-the-hood/a2a2599491232259747faff91ba799d3b2108922/docs/img/pycharm_python_5a.png -------------------------------------------------------------------------------- /docs/img/pycharm_python_5b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ariannedee/python-under-the-hood/a2a2599491232259747faff91ba799d3b2108922/docs/img/pycharm_python_5b.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | pytest 3 | requests 4 | rich 5 | setuptools 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | For local installation: 3 | $ pip install setuptools <-- if using Python 3.12+ 4 | $ python setup.py build_ext --inplace 5 | 6 | This will: 7 | - Add a build folder with C-extension files (.so and .o) 8 | - Put a copy of the .so file in the project root directory 9 | - This is what will load when you import xyz 10 | 11 | >>> import xyz 12 | >>> xyz.echo('Hello world') # Will print to console twice 13 | 14 | *Note* this is just a minimal setup to get something working 15 | """ 16 | from setuptools import setup, Extension 17 | 18 | xyz_module = Extension('xyz', 19 | sources=['Examples/xyzmodule.c']) 20 | setup(name='xyz', 21 | version='0.0.1', 22 | description='This is an example package with a C extension', 23 | ext_modules=[xyz_module]) 24 | --------------------------------------------------------------------------------