Index |
For executing example programs, python 3.10.12 was used | ||||||||
1) Versions, documentation |
Python is maintaining two lines, 2.x and 3.x;
2.7 and 3.3 are taken as usual reference.
Many existing systems are still runing 2.x versions. Programs written in 2.x may *not* work on 3.x and vice versa. Beginners are recommended to start with 3.x.
| ||||||||
2) Books on Python |
For learning Python in detail, please consider below book.
Learning Python by Mark Lutz | ||||||||
3) Hello World ! |
Here we go...
#!python3_10_12 import sys print("Hello World !") print("on " + sys.platform) print(f"with Python {sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}") print("-------------") input("press any key to exit")
| ||||||||
4) Three syntax rules |
Below syntax rules differentiate Python from C-like languages.
Examples to illustrate the points. #!python3_10_12 C_Cpp_programmer = bool(True); if(C_Cpp_programmer): print("I am just beginning to write Python code"); print("I keep putting semicolon to end the statement and "); print("keep forgetting colon in if statement."); print('----------'); Let us rewrite. #!python3_10_12 Python_programmer = True if Python_programmer : print("""I am a Python programmer I do not have to do so much of typing effort. I just keep writing the code in a neat manner and I love it""") print('----------') Lutz - "Python is WYSIWUYG* language". *What You See Is What You Get. | ||||||||
5) Multiline statement |
Below example shows ways of extending beyond a line.
#!python3_10_12 print("""This is a long storry ... ... it starts with ..... """) print(""" We can even use '\"\"\"' for multiline commenting ... .. or for disabling part of the code .... """) """ We can even use '\"\"\"' for multiline commenting ... .. or for disabling part of the code .... """ # (), [], {} could be multiline group = [ "person1", "person2", "person3" ] print(group) if group[0] == "person1" and \ group[1] == "person2" and \ group[2] == "person3": print("Right group") if( group[0] == "person1" and group[1] == "person2" and group[2] == "person3" ): print("Right group") print('----------')
| ||||||||
6) String str |
String is built in type (object).
dir shows possible attributes (methods).
#!python3_10_12 import re print(dir()) print('----------') string = "Python is an object oriented programming language" print('The string is "%s"' % (string)) print('The string is "%s"' % (string.upper())) print('The string is "%s"' % (string.lower())) print('Is it alpha ? - %d' % (string.isalpha())) print('Is string "%s" alpha ? - %d' % (string.replace(' ',''), string.replace(' ','').isalpha())) print('Replaced "%s"' % (string.replace('Python','C++'))) print('With format - {}'.format(string)) print(string.split(' ')) print('----------') print(dir(string)) print('----------') match = re.match("Py.*on", string) #start with Py.. print(match.group(0)) print('----------')
| ||||||||
7) List list |
List is built in type (object).
#!python3_10_12 family = [ "Husband", "Wife", "Son", "Daughter" ] print("family = %s" % (family)) print("number of members = %d" % (len(family))) children = family[2:] print("children = %s" % (children)) twice_family = family * 2 print("twice_family = %s" % (twice_family)) extended_family = family + ["Father-in-law", "Mother-in-law"] print("extended_family = %s" % (extended_family)) print("second from last = %s" % (extended_family[-2])) member = extended_family.pop(4) print("extended_family after popping 5th member (i.e. '%s') = %s" % (member, extended_family)) extended_family.append('Brother-in-law') extended_family.append('Sister-in-law') print("few more in extended_family = %s" % (extended_family)) del extended_family[5] print("extended_family after removing 6th member = %s" % (extended_family)) extended_family.sort() print("extended_family after sort = %s" % (extended_family)) while extended_family: print("Reduced %s" % (extended_family)) first_member, *extended_family = extended_family print('----------') print(dir(family)) print('----------')
| ||||||||
8) Slicing the list |
L[i:j:k] -
from index i element
till index (j-1) element
in steps of k.
i(default=0), j(default=size of list), k(default=1) could be negative. #!python3_10_12 big_family = [ "Husband", "Wife", "Son", "Daughter", "Father", "Mother", "Father-in-law", "Mother-in-law" ] print("big_family = %s" % (big_family)) #slicing print("[1:3] % s" % (big_family[1:3])) print("[1:] % s" % (big_family[1:])) print("[:3] % s" % (big_family[:3])) print("[:-1] % s" % (big_family[:-1])) print("[-1:] % s" % (big_family[-1:])) print("") print("[::] all items") print("[::] % s" % (big_family[::])) print("") print("[::-1] reverses the list, right ?") print("[::-1] % s" % (big_family[::-1])) print("") print("[1:len(big_family)-1:2] % s" % (big_family[1:len(big_family)-1:2])) print("[0:len(big_family)-1:2] % s" % (big_family[0:len(big_family)-1:2])) print('----------')
| ||||||||
9) Dictionary dict |
Dictionary - another built-in type - is basically a hash table.
#!python3_10_12 # basic creation family = { "husband" : "Husband", "wife" : "Wife" } print(family) print(family["husband"]) # another way family = dict(husband="Husband", wife="Wife") print(family) print(family["wife"]) if not "in-laws" in family: # nesting family["in-laws"] = { "father-in-law" : "Father-in-law", "mother-in-law" : "Mother-in-law" } print(family["in-laws"]) else: print(family["in-laws"]) if "father-in-law" in family["in-laws"]: print(family["in-laws"]["father-in-law"]) else: print("no father-in-law") for family_relation in sorted(family): print( "%s is %s" % (family_relation, family[family_relation]) ) # if key is not present, Python will throw KeyError and stop the execution # to avoid it, better to check the key first if 'brother' in family: print("%s is %s" % (family['brother'], 'brother')) else: print("no %s" % ('brother')) print('----------') print(dir(family)) print('----------') # (explicit) memory clean-up (garbage collection) family = 0 print(family) print('----------')
| ||||||||
10) tuple |
Tuple - another built-in type - is basically like a list, but it is immutable i.e. it cannot be changed.
#!python3_10_12 nuclear_family = ( "husband", "wife", "children" ) print(nuclear_family) print(nuclear_family[1]) print(len(nuclear_family)) # below statement will throw error "TypeError: 'tuple' object does not support item assignment" # nuclear_family[len(nuclear_family)] = "girlfriend" parents = ("father", "mother", "father-in-law", "mother-in-law") extended_family = nuclear_family + parents print(extended_family) print('----------') print(dir(nuclear_family)) print('----------')
| ||||||||
11) File |
Below is a simple example of reading text files.
#!python3_10_12 autobiography_filename = 'autobiography.txt' autobiography_copy_filename = 'autobiography_copy.txt' file = open(autobiography_filename, 'r') # reading is default opening mode content = file.read() file.close() print('----------') print(content) file_copy = open(autobiography_copy_filename, 'w') for line in open(autobiography_filename): file_copy.write(line.lower()) file_copy.close() print('----------') for line in open(autobiography_filename): print(line) print('----------') for line in open(autobiography_filename): print(line, end='') #do not add new line at the end of each line during printing print('----------') for line in open(autobiography_filename): print(line.rstrip()) #rstrip seems to be removing \r (carrier return) print('----------') # list of all lines print([t_line.rstrip() for t_line in open(autobiography_filename)]) print('----------') # list of lines containing "program" print([t_line.rstrip() for t_line in open(autobiography_filename) if 'program' in t_line]) print('----------') print(dir(file)) import os os.remove(autobiography_copy_filename) print('----------')
| ||||||||
12) print |
print([object, ...][, sep=' '][, end='\n'][, file=sys.stdout][, flush=False].
Default values are as shown above. #!python3_10_12 husband, wife, son, daughter = "Husband", "Wife", "Son", "Daughter" family_filename = 'family.txt' family_copy_filename = 'family_copy.txt' print(husband, wife, son, daughter, sep=", ", end="\n") import sys print(husband, wife, son, daughter, sep=", ", end="\n", file=sys.stdout) family_txt = open(family_filename, 'w') print(husband, wife, son, daughter, sep=", ", end="\n", file=family_txt) family_txt.close() for line in open(family_filename): print(line, end='') for line in open(family_filename): sys.stdout.write(line) console = sys.stdout sys.stdout = open(family_copy_filename, 'w') for line in open(family_filename): print(line, end='') sys.stdout.close() sys.stdout = console for line in open(family_copy_filename): print(line, end='') import os os.remove(family_filename) os.remove(family_copy_filename) print('----------')
| ||||||||
13) type |
type is a built in object type which type (aka class) of an object.
Lutz - "I am required by law to tell you that type testing is almost always the wrong thing to do in a Python program". 🙂 #!python3_10_12 string = "Python is an object oriented programming language" family = [ "Husband", "Wife", "Son", "Daughter" ] dict_family = { "husband" : "Husband", "wife" : "Wife" } nuclear_family = ( "husband", "wife", "children" ) file = open('autobiography.txt', 'r') print(type(string)) print(type(family)) print(type(dict_family)) print(type(nuclear_family)) print(type(file)) print(type(type(nuclear_family))) print(type(True)) print(type(type(family) == type([]))) file.close() # three ways to test type if type(family) == type([]) : print("family is of type 'list'") if type(string) == str : print("string is of type 'str'") if isinstance(nuclear_family, tuple): print("nuclear_family is of type 'tuple'") print('----------') print(dir(type)) print('----------')
| ||||||||
14) Ternary selection "x if y else z" |
Do x if y is true else do z.
#!python3_10_12 about_python = "Python is OOP language" # ternary selection import re print("Let us use Python") if re.match(".* OOP .*", about_python) else print("Let us use C++") print("Let us use Python") if "OOP" in about_python else print("Let us use C++") print('----------')
| ||||||||
15) Set set |
Set is like dictionary without values.
#!python3_10_12 workers = set([ "man1", "man2", "woman1", "woman2" ]) print("Workers - %s" % (workers)) managers = set(["woman3"]) print("Managers - %s" % (managers)) # sets cannot added # below statement throws an error, "TypeError: unsupported operand type(s) for +: 'set' and 'set'" # extended_family = nuclear_family + parents # may be we use union to add employess = workers | managers print("Employees - %s" % (employess)) #intersection print("Employees who are Managers - %s" % (employess & managers)) #difference print("Employees who are not Managers - %s" % (employess - managers)) #check superset if employess > workers: print("employess set is superset of workers set") print('----------') print(dir(workers)) print('----------')
| ||||||||
16) Shallow copy and deep copy |
When we copy list from one to another,
we usually expect objects to be different and copy of each other.
This need to be enforced in Python. This happens with all objects, but it is not apparent with simple types like numbers (for details, please check Learning Python by Mark Lutz, chapter 6 "Shared reference). #!python3_10_12 soccer_team = [ "Man1", "Man2", "Man3" ] cricket_team = soccer_team # move or shallow copy print("Soccer team - %s" % (soccer_team)) print("Cricket team - %s" % (cricket_team)) print("replacing 3rd man in Soccer team") soccer_team[2] = "Man4" print("Soccer team - %s" % (soccer_team)) print("Cricket team - %s" % (cricket_team)) if cricket_team == soccer_team : print("This is not what we want") print("Let us do it again") soccer_team = [ "Man1", "Man2", "Man3" ] cricket_team = soccer_team[:] # deep copy print("Soccer team - %s" % (soccer_team)) print("Cricket team - %s" % (cricket_team)) print("replacing 3rd man in Soccer team") soccer_team[2] = "Man4" print("Soccer team - %s" % (soccer_team)) print("Cricket team - %s" % (cricket_team)) if cricket_team == soccer_team : print("Something still went wrong !") else: print("Ok now") else: print("All ok") print('----------')
| ||||||||
17) None |
None is a special object in Python and
feel like a null pointer in C.
In below example, if re.match fails to find a match, it returns None (object. #!python3_10_12 import re c_string = "C is a procedural programming language" py_string = "Python is a OOP language" print(type(re.match(".* OOP .*", py_string))) print(type(re.match(".* OOP .*", c_string))) if re.match(".* OOP .*", py_string): print("Awesome ! Let us use Python.") if re.match(".* OOP .*", c_string) == None: print("We cannot use C for efficient OOP.") print('----------')
| ||||||||
18) Few regularly used programming statements |
if-elif-else, while-else, for-else, pass, break, and continue. We also used standard module, random below. #!python3_10_12 hero, villain = 'good', 'bad' print("hero = %s" % (hero)) print("villain = %s" % (villain)) if villain == 'good': print("How can villain be good ?") elif hero == 'bad': print("How can hero be bad ?") else: print("All consistent.") # it is a single statement, it can still come on same line if villain == 'bad': print("Villain is bad") else: print("How can villain be good ?") three_cheers_to_hero = 3 while three_cheers_to_hero: print("Cheers, Hero !") three_cheers_to_hero -= 1 else: print("Cheers done.") normal_guy = villain three_cheers_to_normal_guy = 3 while three_cheers_to_normal_guy: three_cheers_to_normal_guy -= 1 if normal_guy is villain: pass #we need a statement here, but we do not want to anything here, let us use "pass" import random while villain: hero_came = random.choice([True, False]) if hero_came: print("Hero saved us from Villain") break else: print("When will Hero come ??") continue else: print("Villain is no more, we are free.") common_man = 'good' for person in [hero, common_man, normal_guy]: if person is 'bad': print("Found bad person") break else: print("All are good.") for person in [hero, common_man]: if person is 'bad': break else: print("All are good here.") print('----------')
| ||||||||
19) Difference between operators == and is |
== checks value equality whereas
is checks whether they occupy same place in memory.
If is condition is true, == condition will be true too and . if == condition is false, is condition will be false too. #!python3_10_12 hero = 'good' villain = 'bad' common_man = 'good' imposter = villain if hero is common_man: print("hero and common_man are one and the same") else: print("hero and common_man are *not* same") if imposter is villain: print("imposter and villain are one and the same") else: print("imposter and villain are *not* same") common_man = 'bad' print("common_man changed to %s" % (common_man)) if hero is common_man: print("hero and common_man are one and the same") else: print("hero and common_man are *not* same") if common_man is villain: print("common_man and villain are one and the same") else: print("common_man and villain are *not* same") heros = [ 'spiderman', 'superman' ] fake_heros = [ 'spiderman', 'superman' ] if heros == fake_heros: print("heros and fake_heros looks same") else: print("heros and fake_heros does *not* look same") if heros is fake_heros: print("heros and fake_heros are one and the same") else: print("heros and fake_heros are *not* same") print('----------')
| ||||||||
20) Iterators (manual) |
for allows us to iterate through built-in types with ease.
These objects also have iterators in them, which can be used manually to do the same. #!python3_10_12 family = [ "Man", "Woman", "Boy", "Girl" ] print("automatic iteration (built-in)") for member in family: print(member) print('----------') print("manual iteration") iterator = iter(family) while True: try: member = next(iterator) print(member) except StopIteration: break print(type(iterator)) family_dict = dict( husband="Man", wife="Woman", son="Boy", daughter="Girl" ) print('----------') print("automatic iteration (built-in) of dictionary") print("1) Using items ...") for (family_relation, member) in family_dict.items(): print( "%s is %s" % (family_relation, member) ) print('----------') print("2) Using sorted (key sorting) ...") for family_relation in sorted(family_dict.keys()): # sorted(family_dict will work too. print( "%s is %s" % (family_relation, family_dict[family_relation]) ) print('----------') print("manual iteration of dictionary") dict_iterator = iter(family_dict.keys()) while True: try: family_relation = next(dict_iterator) print("%s is %s" % (family_relation, family_dict[family_relation])) except StopIteration: break print(type(dict_iterator)) print('----------') print(dir(iterator)) print('----------') print(dir(dict_iterator)) print('----------')
| ||||||||
21) Factory function |
In below example, function multiply_number defined in inside table_of.
Magically, it retains value of number when its reference is passed around ! Function table_of works like a function factory. #!python3_10_12 def table_of(number): def multiply_number(x): return x * number return multiply_number table_of_2 = table_of(2) table_of_3 = table_of(3) for n in range(10): print("2 * %d = %d" % (n+1, table_of_2(n+1))) print('----------') for n in range(10): print("3 * %d = %d" % (n+1, table_of_3(n+1))) print(type(table_of_2)) print('----------') print(dir(table_of_2)) print('----------')
| ||||||||
22) nonlocal |
nonlocal is like function's static variables in C;
they retain their values, but accessible only within the function.
#!python3_10_12 def calculating_machine(): counter = 0 def calculate_square(n): nonlocal counter counter += 1 print("%d: %d^2 = %d" % (counter, n, n*n)) return calculate_square square_calculating_machine = calculating_machine() square_calculating_machine(4) square_calculating_machine(25) print('----------'
| ||||||||
23) global |
Unlike nonlocal, global variables are accessible outside function and within module.
#!python3_10_12 counter = 0 def calculating_machine(): def calculate_square(n): global counter counter += 1 print("%d: %d^2 = %d" % (counter, n, n*n)) return calculate_square square_calculating_machine = calculating_machine() square_calculating_machine(4) square_calculating_machine(25) print("So far %d operations" % (counter)) print('----------')
| ||||||||
24) Mutability of function parameters |
As Python uses reference of the variable during copy, mutability matters during function call.
If variable is mutable, function may modify its value. For example, if variable of type integer is passed and if function modified its value, caller's variable is unaffected. On the contrary, if list of integers is passed and if function modifies any of the list values, it will modify the list held by the caller. Integers, strings are immutable whereas lists, dictionaries are mutable. | ||||||||
25) Function parameters (arguments) |
def function(args, *listargs, **dictargs) is a general python function as illustrated in example below.
#!python3_10_12 def print_small_nuclear_family(husband, wife, child1=None, child2=None): print("%s is Husband" % (husband)) print("%s is Wife" % (wife)) if child1 is not None: print("%s is first child" % (child1)) if child2 is not None: print("%s is second child" % (child2)) print_small_nuclear_family("Man1", "Woman1", "Girl1") #traditional way print('----------') print_small_nuclear_family(wife="Woman2", husband="Man2") #newer and better way of calling function print('----------') print_small_nuclear_family("Man3", "Woman3", "Boy3", "Girl3") print('----------') def print_family(* members): for member in (members): print(member) print_family("Man4", "Woman4", "Girl4", "Boy4") print('----------') def print_royal_family(** royal_family): for (family_relation, member) in royal_family.items(): print( "%s is %s" % (family_relation, member) ) print_royal_family(King="King1", Queen="Queen1", Prince="Prince1", Princess="Princess1") print('----------')
| ||||||||
26) Function annotations |
Function can be annotated and used in a very formal way.
Example also illustrates dictionary's get method usage. #!python3_10_12 my_family = dict( husband="Husband", wife="Wife", son="Son", daughter="Daughter", father="Father", mother="Mother", father_in_law="Father-in-law", mother_in_law="Mother-in-law" ) def get_member_by_relation(family:dict, relation:str) -> str: return family.get(relation) print("Annotation for get_member_by_relation") print(get_member_by_relation.__annotations__) search_relation = 'husband' found_member = get_member_by_relation(family=my_family, relation=search_relation) if found_member is None: print("no %s" % (search_relation)) else: print("%s is %s" % (found_member, search_relation)) search_relation = 'brother' found_member = get_member_by_relation(family=my_family, relation=search_relation) if found_member is None: print("no %s" % (search_relation)) else: print("%s is %s" % (found_member, search_relation)) print('----------')
| ||||||||
27) lambda expression |
Syntax lambda arguments : expression.
lambda is almost like def, but it is an expression, not a block of statements and it does not return. #!python3_10_12 def table_of(number): multiply_number = (lambda x: x * number) return multiply_number table_of_2 = table_of(2) table_of_3 = table_of(3) for n in range(1,10): print("2 * %d = %d" % (n, table_of_2(n))) print('----------') for n in range(1,10): print("3 * %d = %d" % (1, table_of_3(n))) print('----------')
| ||||||||
28) filter, functools/reduce, and map iterators |
Another example of doing things in Python in more than one way.
reduce is part of functools and has to be explicitly imported. Example also illustrates another use of lambda. #!python3_10_12 my_family = dict( husband="Husband", wife="Wife", son="Son", daughter="Daughter", father="Father", mother="Mother", father_in_law="Father-in-law", mother_in_law="Mother-in-law" ) print("All mothers ...") for key_mother in (list(filter((lambda key: 'mother' in key), my_family.keys()))): print(my_family[key_mother]) from functools import reduce print("All members ...") print(reduce((lambda val1, val2: val1 + ', ' + val2), my_family.values())) print(reduce((lambda val1, val2: val1 + ",\n" + val2), map((lambda val: "Dear " + val), my_family.values())) + ",") print("I love my_family.") print('----------')
| ||||||||
29) Comprehension |
Syntax is [ expression for var1 in iterable1 if condition1 for var2 in iterable2 if condition2 ... ]
#!python3_10_12 #chess tournament family1 = dict( husband="Husband1", wife="Wife1", son="Son1", daughter="Daughter1", ) family2 = dict( husband="Husband2", wife="Wife2", son="Son2", daughter="Daughter2", ) # comprehension all_pairs = [ '(' + val1 + ', ' + val2 + ')' for val1 in family1.values() for val2 in family2.values() ] print("All possible pairs") for pair in all_pairs: print(pair) females1 = [] females2 = [] for female_key in (list(filter((lambda key: 'wife' in key or 'daughter' in key), family1.keys()))): females1.append(family1[female_key]) females2.append(family2[female_key]) # comprehension all_female_pairs = [ '(' + val1 + ', ' + val2 + ')' for val1 in females1 for val2 in females2 ] print("All possible female pairs") for pair in all_female_pairs: print(pair) print('----------')
| ||||||||
30) Generator function using yield |
In below example, function starts back at yield statement.
#!python3_10_12 # first 25 prime numbers for verification # 2, 3, 5, 7, 11, # 13, 17, 19, 23, 29, # 31, 37, 41, 43, 47, # 53, 59, 61, 67, 71, # 73, 79, 83, 89, 97 def next_prime_number(): previous_primes = [2, 3] number_of_primes_so_far = len(previous_primes) candidate = previous_primes[number_of_primes_so_far - 1] while True: candidate += 2 found_prime = True for index in range(0,number_of_primes_so_far-1): if (candidate % previous_primes[index]) == 0: found_prime = False break if found_prime == True: previous_primes.append(candidate) number_of_primes_so_far += 1 yield candidate print("Enter X to stop") print("2\n3",end='') for p in next_prime_number(): in_key = input() if in_key == 'X': break print(p, end='') print('----------')
| ||||||||
31) yield with iterator |
Earlier example rewritten to convert it into prime nummber engine which will give nth prime number.
#!python3_10_12 previous_primes = [2, 3] def next_prime_number(): number_of_primes_so_far = len(previous_primes) candidate = previous_primes[number_of_primes_so_far - 1] while True: candidate += 2 found_prime = True for index in range(0,number_of_primes_so_far-1): if (candidate % previous_primes[index]) == 0: found_prime = False break if found_prime == True: previous_primes.append(candidate) number_of_primes_so_far += 1 yield candidate prime_number_engine = next_prime_number() while True: in_key = input("Enter non-integer or zero to stop:") try: n = int(in_key) except ValueError: break if n == 0: break while n > len(previous_primes): p = prime_number_engine.__next__() # alternatively, next(prime_number_engine) print(previous_primes[n-1]) print('----------')
| ||||||||
32) Comprehension iterator |
In case if we are looking for calculations on large list(s), comprehension iterator is suitable.
Instead of square brackets, if we use normal brackets, we get comprehension iterator.
#!python3_10_12 previous_primes = [2, 3] def next_prime_number(): number_of_primes_so_far = len(previous_primes) candidate = previous_primes[number_of_primes_so_far - 1] while True: candidate += 2 found_prime = True for index in range(0,number_of_primes_so_far-1): if (candidate % previous_primes[index]) == 0: found_prime = False break if found_prime == True: previous_primes.append(candidate) number_of_primes_so_far += 1 yield candidate prime_number_engine = next_prime_number() def generate_first_n_primes(number_of_primes): while number_of_primes > len(previous_primes): next(prime_number_engine) number_of_primes = 10000 print("Generating list of first %d primes ..." % (number_of_primes)) generate_first_n_primes(number_of_primes) # let us print twin primes i.e. prime numbers that have a difference of 2 # comprehension iterator combinations = ( "(%d, %d)" % (val1, val2) for val1 in previous_primes for val2 in previous_primes if (val2 - val1) == 2 ) in_key = input("Press any key to see next twin primes (enter X to stop)") while in_key != 'X': try: combination = next(combinations) except StopIteration: break print(combination, end='') in_key = input() print('----------')
| ||||||||
33) Modules |
We can have module by creating another Python file with its own code.
We may use other module's code by import. If we import only specific variables/functions with from .. import .. as, interpreter will create copies of these variables. However functions will still use the variables from the original module. #!python3_10_12 from friend import car as my_car # my_car is local variable # "as []" is optional; had we not used it here, car would have been imported as car from friend import which_car_do_you_own # which_car_do_you_own is imported, # but (unlike immutable 'car') it will be able to access friend's variables print("I own %s." % (my_car)) print("My friend owns %s." % (which_car_do_you_own())) my_car = "BMW" print("Now I own %s." % (my_car)) print("Now my friend owns %s." % (which_car_do_you_own())) import friend print('----------') print(dir(friend)) print('----------') friend.car = "Volvo" print("Now my friend owns %s." % (friend.which_car_do_you_own())) print("Now I own %s." % (my_car)) from importlib import reload print("Reloading friend") reload(friend) print("Now my friend owns %s." % (friend.which_car_do_you_own())) print("Now I own %s." % (my_car)) del friend # friend.car and friend.which_car_do_you_own will *not* be accessible anymore # however, earlier imported ones will still be available print("Now my friend owns %s." % (which_car_do_you_own())) print("Now I own %s." % (my_car)) print('----------') -----> file: friend.py car = "Mercedes" def which_car_do_you_own(): return car
| ||||||||
34) Classes |
Parent class is called superclass whereas child class is called subclass.
Class variables are called attributes and member functions are called methods. Python do *not* provide true/strict encapsulation. "__" indicate to Python that the variable is internal to class. Python instantiates such internal variables by prefixing _ This way user of the call will not access it by mistake. If the user really want to, he/she is open to access it (by prefixing _ #!python3_10_12 ####### classes ####### class being: pass #empty class #------------ class spiderman: def __init__(self): print("spiderman()") self.__name = "Spiderman" def __str__(self): return self.__name def __del__(self): print("~spiderman(%s)" % (self.__name)) #------------ class spider: def __init__(self, name): print("spider()") self.__name = name def __str__(self): return self.__name def __del__(self): print("~spider(%s)" % (self.__name)) #------------ class human(): def __init__(self): print("human()") def __str__(self): self.subclass_to_implement() # softer way to delegate to subclass def subclass_to_implement(self): assert False, "subclass did not define it" # assert if subclass did not implement it def __del__(self): print("~human()") #------------ class man (human): #subclass of class 'human', that makes class 'man' superclass # constructor def __init__(self, name): print("man(%s)" % (name)) super().__init__() # call superclass's constructor if needed self.__name = name # its variable i.e. attribute # for debug and when __str__ is not overloaded def __repr__(self): return "INFO: object man(name='" + self.__name + "')" # member function i.e. method def speak(self): print("I am a %s." % (self.__name)) # string overloading def __str__(self): return self.__name # operator overloading def __add__(self, other): return spiderman() # another method def name_change(self, new_name): self.__name = new_name # destructor def __del__(self): super().__del__() # call superclass's destructor if needed print("~man(%s)" % (self.__name)) ####### main ####### Normal_guy = man("Normal_guy") print(repr(Normal_guy)) Normal_guy.speak() print(Normal_guy) Mysterious_Spider = spider("Mysterious_Spider") print(Mysterious_Spider) Spiderman = Normal_guy + Mysterious_Spider print(Spiderman) Duplicate = Normal_guy if Duplicate is Normal_guy: print("Duplicate is same as Normal_guy") else: print("Duplicate is *not* same as Normal_guy") import copy Another_guy = copy.copy(Normal_guy) if Another_guy is Normal_guy: print("Another_guy is same as Normal_guy") else: print("Another_guy is *not* same as Normal_guy") Another_guy.name_change("Another_guy") print(Another_guy) print(Normal_guy) # Python do *not* provide true/strict encapsulation # "__" indicate to Python that the variable is internal to class # Python instantiates such internal variables by prefixing _<class> # This way user of the call will not access it by mistake # If the user really want to, he/she is open to access it (by prefixing _<class>) Normal_guy.__name = "thief" print(Normal_guy) Normal_guy._man__name = "thief" print(Normal_guy) print('----------') print(dir(man)) print('----------')
| ||||||||
35) abstractmethod |
Method implementation by subclasses can be enforced with abstractmethod.
#!python3_10_12 from abc import ABCMeta, abstractmethod class human(metaclass=ABCMeta): pass @abstractmethod # forcing subclass to implement __str__ def __str__(self): pass class man (human): def __init__(self, name): print("man()") self.__name = name def __str__(self): # this being abstractmethod, we will get TypeError if we do not implement it return self.__name Man = man("Man") print('----------')
| ||||||||
36) Creating your own container object |
Let us create "group" of "men" and/or "women".
#!python3_10_12 ####### classes ####### class human(): def speak(self): assert False, "Subclass must implement speak()" #------------ class man (human): def __init__(self, name): print("man(%s)" % (name)) self.__name = name def __str__(self): return self.__name def speak(self): print(f"My name is { self.__name }.") # formatted def __del__(self): print("~man(%s)" % (self.__name)) #------------ class woman (human): def __init__(self, name): print("woman(%s)" % (name)) self.__name = name def __str__(self): return self.__name def speak(self): print(f"My name is { self.__name }.") # formatted def __del__(self): print("~woman(%s)" % (self.__name)) #------------ class group: def __init__(self): print("group()") self.__members = [] def __add__(self, other): self.__members.append(other) return self def __len__(self): return len(self.__members) def __str__(self): return ", ".join([ str(member) for member in self.__members ]) def __iter__(self): self.__iterator = iter(self.__members) return self def __next__(self): try: member = next(self.__iterator) except StopIteration: raise StopIteration return member def __getitem__(self, key): # support subscripting "[ ]" and slicing if key is slice: return self.__members[key.start:key.stop:key.step] else: return self.__members[key] def __del__(self): print("~group()") ####### main ####### Man1 = man("Man1") Man2 = man("Man2") Woman1 = woman("Woman1") Woman2 = woman("Woman2") Weekend_partE_group = group() print('----------') Weekend_partE_group = Weekend_partE_group + Man1 + Man2 + Woman1 + Woman2 print(f"We have weekend party group of size { len(Weekend_partE_group) }.") print("Who are there in the group ? ...") print(Weekend_partE_group) print('----------') print("Let us introduce ourselves ...") for member in Weekend_partE_group: member.speak() print('----------') print("First two members are %s" % ", ".join([ str(member) for member in Weekend_partE_group[0:2]])) print("Last two members are %s" % ", ".join([ str(member) for member in Weekend_partE_group[:-3:-1]])) print('----------')
| ||||||||
37) staticmethod |
staticmethod allows us to create static methods and variables.
#!python3_10_12 ####### classes ####### class human: population = 0 __dna_stamp_ctr = 0 @staticmethod #static method, argument - neither self nor class def get_population(): print("human.get_population()") return human.population def __init__(self): human.population += 1 human.__dna_stamp_ctr += 1 self.__dna_stamp = human.__dna_stamp_ctr print("human(DNA# %d)" % (self.__dna_stamp)) def __del__(self): print("~human(DNA# %d)" % (self.__dna_stamp)) human.population -= 1 #------------ class man (human): @staticmethod def get_population(): #can override print("man.get_population()") return human.population def __init__(self, name): print("man(%s)" % (name)) human.__init__(self) self.__name = name def __del__(self): print("~man(%s)" % (self.__name)) human.__del__(self) #------------ class woman (human): def __init__(self, name): print("woman(%s)" % (name)) human.__init__(self) self.__name = name def __del__(self): print("~woman(%s)" % (self.__name)) human.__del__(self) ####### main ####### Man1 = man("Man1") Woman1 = woman("Woman1") Man2 = man("Man2") Woman2 = woman("Woman2") print('----------') # different ways to access static method print(f"Population is { human.get_population() }.") print(f"Population is { man.get_population() }.") print(f"Population is { Man1.get_population() }.") print(f"Population is { woman.get_population() }.") print(f"Population is { Woman1.get_population() }.") print('----------')
| ||||||||
38) classmethod |
Extended earlier example to include classmethod decorator.
staticmethod and classmethod looks similar, but access is somewhat different. #!python3_10_12 ####### classes ####### class human: population = 0 __dna_stamp_ctr = 0 @staticmethod #static method, argument - neither self nor class def get_population(): print("human.get_population()") return human.population def __init__(self): human.population += 1 human.__dna_stamp_ctr += 1 self.__dna_stamp = human.__dna_stamp_ctr print("human(DNA# %d)" % (self.__dna_stamp)) def __del__(self): print("~human(DNA# %d)" % (self.__dna_stamp)) human.population -= 1 #------------ class man (human): @staticmethod def get_population(): #can override print("man.get_population()") return human.population male_population = 0 @classmethod def increment_male_population(cls): cls.male_population += 1 @classmethod def get_male_population(cls): return cls.male_population def __init__(self, name): print("man(%s)" % (name)) human.__init__(self) self.__name = name self.increment_male_population() def __del__(self): print("~man(%s)" % (self.__name)) human.__del__(self) #------------ class boy (man): male_population = 0 # all inherited except male_population #------------ class woman (human): female_population = 0 @classmethod def increment_female_population(cls): cls.female_population += 1 @classmethod def get_female_population(cls): return cls.female_population def __init__(self, name): print("woman(%s)" % (name)) human.__init__(self) self.__name = name self.increment_female_population() def __del__(self): print("~woman(%s)" % (self.__name)) human.__del__(self) ####### main ####### Man1 = man("Man1") Woman1 = woman("Woman1") Man2 = man("Man2") Woman2 = woman("Woman2") Boy1 = boy("Boy1") print('----------') # different ways to access static method print(f"Population is { human.get_population() }.") print(f"Population is { man.get_population() }.") print(f"Population is { Man1.get_population() }.") print(f"Population is { woman.get_population() }.") print(f"Population is { Woman1.get_population() }.") # different ways to access class method print(f"Number of men are { man.get_male_population() }.") print(f"Number of men are { Man1.get_male_population() }.") print(f"Number of boys are { boy.get_male_population() }.") print(f"Number of boys are { Boy1.get_male_population() }.") print(f"Number of women are { woman.get_female_population() }.") print(f"Number of women are { Woman1.get_female_population() }.") print('----------')
| ||||||||
39) User defined function decorator |
It is possible to intercept calls to functions (and even to classes/objects) by having a decorator.
Built-in function decorators - that we have covered so far - are staticmethod and classmethod.
Below example creates user defined function decorator to trace the function sequence.
#!python3_10_12 ####### classes/functions ####### class call_stack: # class to act as function decorator level_n_function_stack = [] curr_level = 0 def display_call_stack(): # no self or cls argument, that makes it static method for function_item in call_stack.level_n_function_stack: level = function_item[0] function_string = function_item[1] for space in range(1,level): print("\t", end='') print(function_string) def __init__(self, ip_function): self.function = ip_function self.function_name = ip_function.__name__ def __call__(self, *arguments): call_stack.curr_level += 1 call_stack.level_n_function_stack.append([call_stack.curr_level, self.function_name + '()']) return_val = self.function(*arguments) call_stack.curr_level -= 1 return return_val @call_stack # function decorator def function12(): print("function12 called") return 12 # alternatively, function12 = call_stack(function12) @call_stack def function22(): print("function22 called") return 22 @call_stack def function21(): print("function21 called") return 21 @call_stack def function11(): print("function11 called") print(function21()) print(function22()) return 11 @call_stack def function1(): print("function1 called") print(function11()) print(function12()) return 1 ####### main ####### print(function1()) print('----------') print("Function sequence logged is (chronological order):") call_stack.display_call_stack() print('----------')
| ||||||||
40) User defined exception handling |
Python provides exception class through which we may define our own exceptions of hierarchy of exceptions.
Python also to propagate or chain exceptions. #!python3_10_12 ####### classes ####### class human(): def speak(self): assert False, "Subclass must implement speak()" #------------ class man (human): def __init__(self, name): self.__name = name def __str__(self): return self.__name #------------ class woman (human): def __init__(self, name): self.__name = name def __str__(self): return self.__name #------------ class GroupFull(Exception): def __init__(self, message): self.__message = message def __str__(self): return self.__message #------------ class group: def __init__(self, size_limit=0): #default parameter self.__members = [] self.__size_limit = size_limit def __add__(self, other): if self.__size_limit: if len(self.__members) >= self.__size_limit: raise GroupFull(f"Group is full (size_limit = { self.__size_limit })") self.__members.append(other) return self def __len__(self): return len(self.__members) def __str__(self): return ", ".join([ str(member) for member in self.__members ]) #------------ def member_add_wrapper_propagate_exception(group, person): try: group = group + person except GroupFull as error: raise # exception propagation class MemberAddFail(Exception): def __init__(self, message): self.__message = message def __str__(self): return self.__message class MemberAddFail_ItIsNotMe(MemberAddFail): # user defined exception hierarchy pass def member_add_wrapper_chain_exception(group, person): try: group = group + person except GroupFull as error: raise MemberAddFail_ItIsNotMe("member addition failed") from error # exception chaining finally: print("finally from chained exception") ####### main ####### Man1 = man("Man1") Man2 = man("Man2") Woman1 = woman("Woman1") Woman2 = woman("Woman2") size_limit = 5 Weekend_partE_group = group(size_limit) print('----------') Weekend_partE_group = Weekend_partE_group + Man1 + Man2 + Woman1 + Woman2 print(f"Weekend_partE_group: { Weekend_partE_group }.") print('----------') Friend = woman("Friend") try: Weekend_partE_group = Weekend_partE_group + Friend except GroupFull as error: print(f"Could not add { Friend }; '{ error }'") except: print(f"Unknown error; '{ error }'") else: print(f"Added { Friend }.") finally: print(f"Updated Weekend_partE_group: { Weekend_partE_group }.") print('----------') Stranger = man("Stranger") try: Weekend_partE_group = Weekend_partE_group + Stranger except GroupFull as error: print(f"Could not add { Stranger }; '{ error }'") except: print(f"Unknown error; '{ error }'") else: print(f"Added { Stranger }.") finally: print(f"Updated Weekend_partE_group: { Weekend_partE_group }.") print('----------') Imposter = man("Imposter") try: member_add_wrapper_propagate_exception(Weekend_partE_group, Imposter) except Exception as error: print(f"Error: '{ error }'") print('----------') try: member_add_wrapper_chain_exception(Weekend_partE_group, Imposter) except Exception as error: print(f"Error (chained):") print(f"'{ error }'") cause = error.__cause__ while cause: print(f"'{ cause }'") cause = cause.__cause__ finally: print("finally from main caller") print('----------')
| ||||||||
41) Context management |
Syntax is with expression [as variable]: with-block.
expression must support context management.
#!python3_10_12 autobiography_filename = 'autobiography.txt' # file class supports context management with open(autobiography_filename, 'r') as file: content = file.read() # Above is equivalent to # file = open(autobiography_filename, 'r') # try: # content = file.read() # finally: # file.close() print("File contents:") print('<----------') print(content) print('---------->') print('----------')
| ||||||||
42) User defined context management |
By adding __enter__ and __exit__, we can support context management.
#!python3_10_12 ####### classes ####### class human(): def speak(self): assert False, "Subclass must implement speak()" #------------ class man (human): def __init__(self, name): self.__name = name def __str__(self): return self.__name #------------ class woman (human): def __init__(self, name): self.__name = name def __str__(self): return self.__name #------------ class GroupFull(Exception): def __init__(self, message): self.__message = message def __str__(self): return self.__message #------------ class group: def __init__(self, size_limit=0): self.__members = [] self.__size_limit = size_limit def __add__(self, other): if self.__size_limit: if len(self.__members) >= self.__size_limit: raise GroupFull(f"Group is full (size_limit = { self.__size_limit })") self.__members.append(other) return self def __len__(self): return len(self.__members) def __str__(self): return ", ".join([ str(member) for member in self.__members ]) def __enter__(self): print("group.__enter__()") return self def __exit__(self, exc_type, exc_value, exc_tb): print("group.__exit__()") if exc_type is not None: print(f"Error ocurred '{ exc_value }'.") return True # False will propagate the error, True will gracefully complete ####### main ####### Man1 = man("Man1") Man2 = man("Man2") Woman1 = woman("Woman1") Woman2 = woman("Woman2") Friend = woman("Friend") Stranger = man("Stranger") size_limit = 5 print('----------') with group(size_limit) as Weekend_partE_group: print("Adding Man1, Man2, Woman1, Woman2..") Weekend_partE_group = Weekend_partE_group + Man1 + Man2 + Woman1 + Woman2 print("Adding Friend..") Weekend_partE_group = Weekend_partE_group + Friend print("Adding Stranger..") Weekend_partE_group = Weekend_partE_group + Stranger print(f"Weekend_partE_group: { Weekend_partE_group }.") print('----------')
| ||||||||
43) Unicode strings |
Unicode escape sequence ('\u') can directly be used in string.
#!python3_10_12 Devanagiri_script_characters = "\u0905, \u0906, \u0907, and \u0908." print("First 4 letters of Devanagiri script are: ", end='') print(Devanagiri_script_characters) try: print(Devanagiri_script_characters.encode('ascii')) except UnicodeEncodeError as error: print(f'Error: "{ error }"') try: print(Devanagiri_script_characters.encode('latin1')) except UnicodeEncodeError as error: print(f'Error: "{ error }"') print(Devanagiri_script_characters.encode('utf8')) print(Devanagiri_script_characters.encode('utf16')) print(Devanagiri_script_characters.encode('utf32')) # finding unicode oridinal_value = ord("अ") print(f"Ordinal value of 'अ' is { oridinal_value }.") print(f"Unicode for 'अ' is \\u{oridinal_value:04X}") print('----------')
| ||||||||
44) property decorator |
Syntax property(getfunction, setfunction, delfunction, doc)
Method 1 #!python3_10_12 ####### classes ####### class human(): pass #------------ class man (human): def __init__(self, name, age=0): print("man(%s)" % (name)) self.__name = name self.__age = age def __str__(self): return self.__name def get_age(self): print("get_age()") return self.__age def change_age(self, age): print("change_age()") self.__age = age age = property(get_age, change_age, None, "Property 'age'") # del function not set def __del__(self): print("~man(%s)" % (self.__name)) #------------ class boy (man): pass # all inherited ####### main ####### Man = man("Man") print(f"{ Man }'s age is { Man.age }.") Man.age = 32 print(f"Now { Man }'s age is { Man.age }.") Boy = boy('Boy', 10) print(f"{ Boy }'s age is { Boy.age }.") print('----------')
Method 2 with Setter and Deleter Name of the function should be name as the attribute #!python3_10_12 ####### classes ####### class human(): pass #------------ class man (human): def __init__(self, name, age=0): print("man(%s)" % (name)) self.__name = name self.__age = age def __str__(self): return self.__name @property def age(self): "Property age" print("age()") return self.__age @age.setter def age(self, age): print("age setter()") self.__age = age @age.deleter def age(self): print("age deleter()") def __del__(self): print("~man(%s)" % (self.__name)) #------------ class boy (man): pass # all inherited ####### main ####### Man = man("Man") print(f"{ Man }'s age is { Man.age }.") Man.age = 32 print(f"Now { Man }'s age is { Man.age }.") Boy = boy('Boy', 10) print(f"{ Boy }'s age is { Boy.age }.") print('----------')
| ||||||||
45) descriptor class |
Separate descriptor
#!python3_10_12 ####### classes ####### class human(): pass #------------ class age_descriptor: # descriptor "Property age" def __get__(self, instance, owner): print("age_descriptor get()") return instance._age def __set__(self, instance, age): print("age_descriptor set()") instance._age = age def __delete__(self, instance): print("age_descriptor delete()") class man (human): def __init__(self, name, age=0): print("man(%s)" % (name)) self.__name = name self._age = age # cannot use __age, looks like descriptor cannot access it def __str__(self): return self.__name age = age_descriptor() # descriptor binding def __del__(self): print("~man(%s)" % (self.__name)) #------------ class boy (man): pass # all inherited ####### main ####### Man = man("Man") print(f"{ Man }'s age is { Man.age }.") Man.age = 32 print(f"Now { Man }'s age is { Man.age }.") Boy = boy('Boy', 10) print(f"{ Boy }'s age is { Boy.age }.") print('----------')
Nested descriptor class #!python3_10_12 ####### classes ####### class human(): pass #------------ class man (human): def __init__(self, name, age=0): print("man(%s)" % (name)) self.__name = name self._age = age # cannot use __age, looks like descriptor cannot access it def __str__(self): return self.__name class age_descriptor: # descriptor "Property age" def __get__(self, instance, owner): print("age_descriptor get()") return instance._age def __set__(self, instance, age): print("age_descriptor set()") instance._age = age def __delete__(self, instance): print("age_descriptor delete()") age = age_descriptor() # descriptor binding def __del__(self): print("~man(%s)" % (self.__name)) #------------ class boy (man): pass # all inherited ####### main ####### Man = man("Man") print(f"{ Man }'s age is { Man.age }.") Man.age = 32 print(f"Now { Man }'s age is { Man.age }.") Boy = boy('Boy', 10) print(f"{ Boy }'s age is { Boy.age }.") print('----------')
| ||||||||
46) __setattr__ and __getattr__ |
Generic __setattr__ and __getattr__ methods can be used to intercept access to attributes too !.
#!python3_10_12 ####### classes ####### class human(): pass #------------ class man (human): def __init__(self, name, age=0): print("man(%s)" % (name)) self._name = name # will trigger __setattr__ self._age = age # will trigger __setattr__ def __getattr__(self, attr): print("__getattr__()") if attr == 'name': return self._name if attr == 'age': return self._age raise AttributeError(attr) def __setattr__(self, attr, value): print("__setattr__()") if attr == 'name': attr = '_name' elif attr == 'age': attr = '_age' self.__dict__[attr] = value # use dict otherwise we end up in loop def __delattr__(self, attr, value): print("__delattr__()") def __str__(self): return self._name def __del__(self): print("~man(%s)" % (self._name)) #------------ class boy (man): pass # all inherited ####### main ####### Man = man("Man") print(f"{ Man }'s age is { Man.age }.") Man.age = 32 print(f"Now { Man }'s age is { Man.age }.") Boy = boy('Boy', 10) print(f"{ Boy }'s age is { Boy.age }.") print('----------')
| ||||||||
47) User defined Class decorator |
Just like function, we can wrap or decorate a class we well !
Method 1 (class inside decorator function) #!python3_10_12 ####### classes ####### def undercover(cls): print("undercover()") class cop: def __init__(self, *args): print("cop()") self.__criminal = cls(*args) def __getattr__(self, *args): print("__getattr__()") print("Pass on information to police department") return getattr(self.__criminal, *args) def __del__(self, *args): print("~cop()") return cop @undercover # function as class decorator class drug_dealer: def __init__(self): print("drug_dealer()") def do_drug_peddling(self): print("do_drug_peddling()") def __del__(self, *args): print("~drug_dealer()") ####### main ####### New_gang_member = drug_dealer() print('----------') New_gang_member.do_drug_peddling() print('----------') New_gang_member2 = drug_dealer() print('----------')
Method 2 (class outside decorator function) #!python3_10_12 ####### classes ####### class cop: def __init__(self, instance): print("cop()") self.__criminal = instance def __getattr__(self, *args): print("__getattr__()") print("Pass on information to police department") return getattr(self.__criminal, *args) def __del__(self, *args): print("~cop()") def undercover(cls): print("undercover()") def onCall(*args): return cop(cls(*args)) return onCall @undercover # function as class decorator class drug_dealer: def __init__(self): print("drug_dealer()") def do_drug_peddling(self): print("do_drug_peddling()") def __del__(self, *args): print("~drug_dealer()") ####### main ####### New_gang_member = drug_dealer() print('----------') New_gang_member.do_drug_peddling() print('----------') New_gang_member2 = drug_dealer() print('----------')
| ||||||||
48) Implementing Singeton decorator |
Singleton class can have only one instance.
You will also see a use of nonlocal. Method 1 (decorator function) #!python3_10_12 ####### classes ####### def one_and_only_one(cls): # singleton decorator function print("one_and_only_one()") __the_instance = None def on_instantiation(*args): print("on_instantiation()") nonlocal __the_instance if __the_instance is None: __the_instance = cls(*args) print("God created") return __the_instance return on_instantiation @one_and_only_one class God: def __init__(self): print("God()") def __del__(self): print("~God()") ####### main ####### The_God = God() One_more_God = God() And_one_more_God = God() print('----------')
Method 2 (decorator class) #!python3_10_12 ####### classes ####### class one_and_only_one: # singleton decorator class def __init__(self, cls): print("one_and_only_one()") self.__the_instance = None self.__cls = cls def __call__(self, *args): print("on_instantiation()") if self.__the_instance is None: self.__the_instance = self.__cls(*args) print("God created") return self.__the_instance @one_and_only_one class God: def __init__(self): print("God()") def __del__(self): print("~God()") ####### main ####### The_God = God() One_more_God = God() And_one_more_God = God() print('----------')
| ||||||||
49) Implementing public and private attributes with decorator |
The example also shows "nested decorators" to implement public and private attributes.
Note that operator overloading will have to be also done explicitly in decorator's class too. In below example, __str__ operator, if not overloaded in attribute_interceptor, will *not* call man.__str__ method. #!python3_10_12 ####### classes ####### class human(): pass #------------ def Public_Attributes(*attributes): print("Public_Attributes()") def instantiate_interceptor(cls): print("Public_Attributes.instantiate_interceptor()") class attribute_interceptor: def __init__(self, *args): print("Public_Attributes.attribute_interceptor()") self.__instance = cls(*args) def __getattr__(self, attr): print("Public_Attributes.__getattr__()") if attr in attributes: return getattr(self.__instance, attr) else: raise TypeError("Attribute '" + attr + "' is not present.") def __setattr__(self, attr, value): print("Public_Attributes.__setattr__()") if attr == '_attribute_interceptor__instance': self.__dict__[attr] = value elif attr in attributes: setattr(self.__instance, attr, value) else: raise TypeError("Attribute '" + attr + "' is not present.") def __str__(self): print("Public_Attributes.__str__()") return str(self.__instance) def __del__(self): print("Public_Attributes.~attribute_interceptor()") return attribute_interceptor return instantiate_interceptor #------------ def Private_Attributes(*attributes): print("Private_Attributes()") def instantiate_interceptor(cls): print("Private_Attributes.instantiate_interceptor()") class attribute_interceptor: def __init__(self, *args): print("Private_Attributes.attribute_interceptor()") self.__instance = cls(*args) def __getattr__(self, attr): print("Private_Attributes.__getattr__()") if attr not in attributes: return getattr(self.__instance, attr) else: raise TypeError("Attribute '" + attr + "' is private.") def __setattr__(self, attr, value): print("Private_Attributes.__setattr__()") if attr == '_attribute_interceptor__instance': self.__dict__[attr] = value elif attr not in attributes: setattr(self.__instance, attr, value) else: raise TypeError("Attribute '" + attr + "' is private.") def __str__(self): print("Private_Attributes.__str__()") return str(self.__instance) def __del__(self): print("Private_Attributes.~attribute_interceptor()") return attribute_interceptor return instantiate_interceptor #------------ @Private_Attributes('name') # nested decorators @Public_Attributes('hobby', 'speak') class man (human): def __init__(self, name): print("man()") self.name = name def __str__(self): # needs implementation in decorator's class too ! print("man.__str__()") return self.name def speak(self): print(f"I am { self.name }.") def __del__(self): print("~man()") #------------ class boy (man): pass # all inherited ####### main ####### print("\n\n====> Testing 'man' <====\n\n") Man1 = man("Man1") print('----------') # test private attribute try: print(Man1.name) except Exception as error: print(f"Error: \"{ error }\"") print('----------') try: Man1.name = "reMan1" except Exception as error: print(f"Error: \"{ error }\"") else: print(Man1.name) print('----------') # test public attribute try: Man1.hobby = "Sport" except Exception as error: print(f"Error: \"{ error }\"") else: print("Setting public attribute worked.") print(Man1.hobby) print('----------') try: Man1.age = 32 except Exception as error: print(f"Error: \"{ error }\"") else: print(Man1.age) print('----------') print(Man1) print('----------') Man1.speak() print("\n\n====> Testing 'boy' <====\n\n") Boy1 = boy("Boy1") print('----------') # test private attribute try: print(Boy1.name) except Exception as error: print(f"Error: \"{ error }\"") print('----------') try: Boy1.name = "reBoy1" except Exception as error: print(f"Error: \"{ error }\"") else: print(Boy1.name) print('----------') # test public attribute try: Boy1.hobby = "Music" except Exception as error: print(f"Error: \"{ error }\"") else: print("Setting public attribute worked.") print(Boy1.hobby) print('----------') try: Boy1.age = 12 except Exception as error: print(f"Error: \"{ error }\"") else: print(Boy1.age) print('----------') print(Boy1) print('----------') Boy1.speak() print('----------')
| ||||||||
50) Continue discussion of nested decorators |
Earlier implementation of nested decorators needed operator overloading both decorator classes
(attribute_interceptor).
This is inefficient; we can remove this inefficiency with additional base class, which will implement all operator overloading in generic way. Method 1 (adding operator in public attributes) #!python3_10_12 ####### classes ####### class human(): pass #------------ class base_attribute_interceptor: def __str__(self): print("base_attribute_interceptor()") return self.__class__.__getattr__(self, '__str__')() # other operators #------------ def Public_Attributes(*attributes): print("Public_Attributes()") def instantiate_interceptor(cls): print("Public_Attributes.instantiate_interceptor()") class attribute_interceptor (base_attribute_interceptor): def __init__(self, *args): print("Public_Attributes.attribute_interceptor()") self.__instance = cls(*args) def __getattr__(self, attr): print("Public_Attributes.__getattr__()") if attr in attributes: return getattr(self.__instance, attr) else: raise TypeError("Attribute '" + attr + "' is not present.") def __setattr__(self, attr, value): print("Public_Attributes.__setattr__()") if attr == '_attribute_interceptor__instance': self.__dict__[attr] = value elif attr in attributes: setattr(self.__instance, attr, value) else: raise TypeError("Attribute '" + attr + "' is not present.") def __del__(self): print("Public_Attributes.~attribute_interceptor()") return attribute_interceptor return instantiate_interceptor #------------ def Private_Attributes(*attributes): print("Private_Attributes()") def instantiate_interceptor(cls): print("Private_Attributes.instantiate_interceptor()") class attribute_interceptor (base_attribute_interceptor): def __init__(self, *args): print("Private_Attributes.attribute_interceptor()") self.__instance = cls(*args) def __getattr__(self, attr): print("Private_Attributes.__getattr__()") if attr not in attributes: return getattr(self.__instance, attr) else: raise TypeError("Attribute '" + attr + "' is private.") def __setattr__(self, attr, value): print("Private_Attributes.__setattr__()") if attr == '_attribute_interceptor__instance': self.__dict__[attr] = value elif attr not in attributes: setattr(self.__instance, attr, value) else: raise TypeError("Attribute '" + attr + "' is private.") def __del__(self): print("Private_Attributes.~attribute_interceptor()") return attribute_interceptor return instantiate_interceptor #------------ @Private_Attributes('name') # nested decorators @Public_Attributes('hobby', 'speak', '__str__') # __str__ in public attributes class man (human): def __init__(self, name): print("man()") self.name = name def __str__(self): print("man.__str__()") return self.name def speak(self): print(f"I am { self.name }.") def __del__(self): print("~man()") #------------ class boy (man): pass # all inherited ####### main ####### print("\n\n====> Testing 'man' <====\n\n") Man1 = man("Man1") print('----------') # test private attribute try: print(Man1.name) except Exception as error: print(f"Error: \"{ error }\"") print('----------') try: Man1.name = "reMan1" except Exception as error: print(f"Error: \"{ error }\"") else: print(Man1.name) print('----------') # test public attribute try: Man1.hobby = "Sport" except Exception as error: print(f"Error: \"{ error }\"") else: print("Setting public attribute worked.") print(Man1.hobby) print('----------') try: Man1.age = 32 except Exception as error: print(f"Error: \"{ error }\"") else: print(Man1.age) print('----------') print(Man1) print('----------') Man1.speak() print("\n\n====> Testing 'boy' <====\n\n") Boy1 = boy("Boy1") print('----------') # test private attribute try: print(Boy1.name) except Exception as error: print(f"Error: \"{ error }\"") print('----------') try: Boy1.name = "reBoy1" except Exception as error: print(f"Error: \"{ error }\"") else: print(Boy1.name) print('----------') # test public attribute try: Boy1.hobby = "Music" except Exception as error: print(f"Error: \"{ error }\"") else: print("Setting public attribute worked.") print(Boy1.hobby) print('----------') try: Boy1.age = 12 except Exception as error: print(f"Error: \"{ error }\"") else: print(Boy1.age) print('----------') print(Boy1) print('----------') Boy1.speak() print('----------')
Method 2 (using instance variable from decorator class) #!python3_10_12 ####### classes ####### class human(): pass #------------ class base_attribute_interceptor: def __str__(self): print("base_attribute_interceptor()") return str(self._attribute_interceptor__instance) # other operators #------------ def Public_Attributes(*attributes): print("Public_Attributes()") def instantiate_interceptor(cls): print("Public_Attributes.instantiate_interceptor()") class attribute_interceptor (base_attribute_interceptor): def __init__(self, *args): print("Public_Attributes.attribute_interceptor()") self.__instance = cls(*args) def __getattr__(self, attr): print("Public_Attributes.__getattr__()") if attr in attributes: return getattr(self.__instance, attr) else: raise TypeError("Attribute '" + attr + "' is not present.") def __setattr__(self, attr, value): print("Public_Attributes.__setattr__()") if attr == '_attribute_interceptor__instance': self.__dict__[attr] = value elif attr in attributes: setattr(self.__instance, attr, value) else: raise TypeError("Attribute '" + attr + "' is not present.") def __del__(self): print("Public_Attributes.~attribute_interceptor()") return attribute_interceptor return instantiate_interceptor #------------ def Private_Attributes(*attributes): print("Private_Attributes()") def instantiate_interceptor(cls): print("Private_Attributes.instantiate_interceptor()") class attribute_interceptor (base_attribute_interceptor): def __init__(self, *args): print("Private_Attributes.attribute_interceptor()") self.__instance = cls(*args) def __getattr__(self, attr): print("Private_Attributes.__getattr__()") if attr not in attributes: return getattr(self.__instance, attr) else: raise TypeError("Attribute '" + attr + "' is private.") def __setattr__(self, attr, value): print("Private_Attributes.__setattr__()") if attr == '_attribute_interceptor__instance': self.__dict__[attr] = value elif attr not in attributes: setattr(self.__instance, attr, value) else: raise TypeError("Attribute '" + attr + "' is private.") def __del__(self): print("Private_Attributes.~attribute_interceptor()") return attribute_interceptor return instantiate_interceptor #------------ @Private_Attributes('name') # nested decorators @Public_Attributes('hobby', 'speak') class man (human): def __init__(self, name): print("man()") self.name = name def __str__(self): print("man.__str__()") return self.name def speak(self): print(f"I am { self.name }.") def __del__(self): print("~man()") #------------ class boy (man): pass # all inherited ####### main ####### print("\n\n====> Testing 'man' <====\n\n") Man1 = man("Man1") print('----------') # test private attribute try: print(Man1.name) except Exception as error: print(f"Error: \"{ error }\"") print('----------') try: Man1.name = "reMan1" except Exception as error: print(f"Error: \"{ error }\"") else: print(Man1.name) print('----------') # test public attribute try: Man1.hobby = "Sport" except Exception as error: print(f"Error: \"{ error }\"") else: print("Setting public attribute worked.") print(Man1.hobby) print('----------') try: Man1.age = 32 except Exception as error: print(f"Error: \"{ error }\"") else: print(Man1.age) print('----------') print(Man1) print('----------') Man1.speak() print("\n\n====> Testing 'boy' <====\n\n") Boy1 = boy("Boy1") print('----------') # test private attribute try: print(Boy1.name) except Exception as error: print(f"Error: \"{ error }\"") print('----------') try: Boy1.name = "reBoy1" except Exception as error: print(f"Error: \"{ error }\"") else: print(Boy1.name) print('----------') # test public attribute try: Boy1.hobby = "Music" except Exception as error: print(f"Error: \"{ error }\"") else: print("Setting public attribute worked.") print(Boy1.hobby) print('----------') try: Boy1.age = 12 except Exception as error: print(f"Error: \"{ error }\"") else: print(Boy1.age) print('----------') print(Boy1) print('----------') Boy1.speak() print('----------')
| ||||||||
51) Function decorator for function (or method) |
Function decorator for method
#!python3_10_12 ####### classes ####### class human(): pass #------------ def validate_age(minimum_age, maximum_age): print("validate_age()") def function_interceptor(func): print("function_interceptor()") def argument_interceptor(self, age): print("argument_interceptor()") if age < minimum_age or age > maximum_age: raise TypeError("age invalid") print("age OK") return func(self, age) return argument_interceptor return function_interceptor #------------ class man (human): def __init__(self, name): print("man()") self.__name = name @validate_age(1,125) def age_method(self, ip_age): self.__age = ip_age def __str__(self): return f"{ self.__name }({ self.__age })" def __del__(self): print("~man()") ####### main ####### Man1 = man("Man1") print('----------') try: Man1.age_method(32) except Exception as error: print(error) print('----------') try: Man1.age_method(126) except Exception as error: print(error) print('----------') print(Man1) print('----------')
Function decorator for function #!python3_10_12 ####### classes ####### class human(): pass #------------ def validate_age(minimum_age, maximum_age): print("validate_age()") def function_interceptor(func): print("function_interceptor()") def argument_interceptor(age): print("argument_interceptor()") if age < minimum_age or age > maximum_age: raise TypeError("age invalid") print("age OK") return func(age) return argument_interceptor return function_interceptor #------------ @validate_age(1,125) def age_function(ip_age): print(f"age_function({ ip_age })") ####### main ####### try: age_function(32) except Exception as error: print(error) print('----------') try: age_function(126) except Exception as error: print(error) print('----------')
| ||||||||
52) Metaclass |
Lutz - "(Python) A Language of Hooks".
Metaclass gives more control even before the object is created, so they are meant for "class management". There are three ways. First method (metaclass inhertied from type) is the preferred one. 1) Class (inhertied from type) as Metaclass #!python3_10_12 ####### classes ####### class God(type): def __new__(meta, classname, supers, classdict): print(f"God.__new__({classname})") return type.__new__(meta, classname, supers, classdict) def __init__(cls, classname, supers, classdict): print(f"God.__init__({classname})") def __del__(cls): print(f"God.__del__({cls})") #------------ class human(): def __init__(self): print("human()") def __del__(self): print("~human()") #------------ class man (human, metaclass=God): def __init__(self): print("man()") super().__init__() def __del__(self): print("~man()") super().__del__() ####### main ####### print('----------') Man1 = man() print('----------')
2) Function as Metaclass #!python3_10_12 ####### classes ####### def Universe(classname, supers, classdict): print(f"Universe({classname})") return type(classname, supers, classdict) #------------ class human(): def __init__(self): print("human()") def __del__(self): print("~human()") #------------ class man (human, metaclass=Universe): def __init__(self): print("man()") super().__init__() def __del__(self): print("~man()") super().__del__() ####### main ####### print('----------') Man1 = man() print('----------')
3) Class (independent) as Metaclass #!python3_10_12 ####### classes ####### class Devil: def __call__(self, classname, supers, classdict): print(f"Devil({classname})") cls = self.__New__(classname, supers, classdict) # New, *not* new self.__Init__(cls, classname, supers, classdict) # Init, *not* init return cls def __New__(self, classname, supers, classdict): print(f"Devil.__New__({classname})") return type(classname, supers, classdict) def __Init__(self, cls, classname, supers, classdict): print(f"Devil.__Init__({classname})") #------------ class human(): def __init__(self): print("human()") def __del__(self): print("~human()") #------------ class man (human, metaclass=Devil()): # Call to Devil def __init__(self): print("man()") super().__init__() def __del__(self): print("~man()") super().__del__() ####### main ####### print('----------') Man1 = man() print('----------')
| ||||||||
53) Another example of metaclass |
Below example shows that metaclass itself has added attribute and method to the classes.
#!python3_10_12 ####### classes ####### def God__str__(name): def method__str__(self): return f"I am {name}." return method__str__ class God(type): def __new__(meta, classname, supers, classdict): print(f"God.__new__({classname})") classdict['name'] = classname classdict['__str__'] = God__str__(classname) return type.__new__(meta, classname, supers, classdict) def __init__(cls, classname, supers, classdict): print(f"God.__init__({classname})") def __del__(cls): print(f"God.__del__({cls})") #------------ class human(metaclass=God): def __init__(self): print("human()") def __del__(self): print("~human()") #------------ class bird (metaclass=God): def __init__(self): print("bird()") def __del__(self): print("~bird()") #------------ class fish (metaclass=God): def __init__(self): print("fish()") def __del__(self): print("~fish()") ####### main ####### print('----------') Human = human() print('----------') print(Human) print('----------') Bird = bird() print('----------') print(Bird) print('----------') Fish = fish() print('----------') print(Fish) print('----------')
| ||||||||
54) Example of using metaclass effectively (with decorator) |
In below example, metaclass God is applying call_stack to human's methods for tracing purposes.
Two ways are shown. One method #!python3_10_12 ####### classes ####### level_n_function_stack = [] curr_level = 0 def function_watch(ip_function): # decorator to log the method calls print(f"function_watch({ip_function})") def log_function_call(self, *args, **kwargs): global curr_level global level_n_function_stack curr_level += 1 level_n_function_stack.append([curr_level, f"{ip_function}()"]) return_val = ip_function(self, *args, **kwargs) curr_level -= 1 return return_val return log_function_call def display_function_watch(): for function_item in level_n_function_stack: level = function_item[0] function_string = function_item[1] for space in range(1,level): print("\t", end='') print(function_string) #------------ from types import FunctionType class God(type): def __new__(meta, classname, supers, classdict): print(f"God.__new__({classname})") for attr, attr_val in classdict.items(): if type(attr_val) is FunctionType: classdict[attr] = function_watch(attr_val) # prefix the decorator return type.__new__(meta, classname, supers, classdict) def __init__(cls, classname, supers, classdict): print(f"God.__init__({classname})") def __del__(cls): print(f"God.__del__({cls})") #------------ class human(metaclass=God): def __init__(self): print("human()") def __del__(self): print("~human()") #------------ class man (human): def __init__(self): print("man()") super().__init__() def __str__(self): return "man" def __del__(self): print("~man()") super().__del__() ####### main ####### print('----------') Man1 = man() print('----------') print(Man1) print('----------') display_function_watch() print('----------')
Alternate method #!python3_10_12 ####### classes ####### level_n_function_stack = [] curr_level = 0 def function_watch(ip_function): # decorator to log the method calls print(f"function_watch({ip_function})") def log_function_call(self, *args, **kwargs): global curr_level global level_n_function_stack curr_level += 1 level_n_function_stack.append([curr_level, f"{ip_function}()"]) return_val = ip_function(self, *args, **kwargs) curr_level -= 1 return return_val return log_function_call def display_function_watch(): for function_item in level_n_function_stack: level = function_item[0] function_string = function_item[1] for space in range(1,level): print("\t", end='') print(function_string) #------------ from types import FunctionType def God_who_care(watch_decorator): print(f"God_who_watch({watch_decorator})") class God(type): def __new__(meta, classname, supers, classdict): print(f"God.__new__({classname})") for attr, attr_val in classdict.items(): if type(attr_val) is FunctionType: classdict[attr] = watch_decorator(attr_val) # prefix the decorator return type.__new__(meta, classname, supers, classdict) def __init__(cls, classname, supers, classdict): print(f"God.__init__({classname})") def __del__(cls): print(f"God.__del__({cls})") return God #------------ class human(metaclass=God_who_care(function_watch)): def __init__(self): print("human()") def __del__(self): print("~human()") #------------ class man (human): def __init__(self): print("man()") super().__init__() def __str__(self): return "man" def __del__(self): print("~man()") super().__del__() ####### main ####### print('----------') Man1 = man() print('----------') print(Man1) print('----------') display_function_watch() print('----------')
| ||||||||
55) Execution time measurements with metaclass |
We use time module to measure execution time for each method call.
#!python3_10_12 ####### classes ####### import time function_execution_time = [] def function_timing(ip_function): # decorator to measure execution time of the method print(f"function_timing({ip_function})") def time_the_function(self, *args, **kwargs): global function_execution_time start_time = time.time() return_val = ip_function(self, *args, **kwargs) microseconds = int((time.time() - start_time) * 1e6) function_execution_time.append([f"{microseconds} µs", f"{ip_function}()"]) return return_val return time_the_function def display_function_timing(): for function_item in function_execution_time: timing = function_item[0] function_string = function_item[1] print(f"{function_string}({timing})") #------------ from types import FunctionType def God_who_care(timing_decorator): print(f"God_who_watch({timing_decorator})") class God(type): def __new__(meta, classname, supers, classdict): print(f"God.__new__({classname})") for attr, attr_val in classdict.items(): if type(attr_val) is FunctionType: classdict[attr] = timing_decorator(attr_val) # prefix the decorator return type.__new__(meta, classname, supers, classdict) def __init__(cls, classname, supers, classdict): print(f"God.__init__({classname})") def __del__(cls): print(f"God.__del__({cls})") return God #------------ class human(metaclass=God_who_care(function_timing)): def __init__(self): print("human()") def __del__(self): print("~human()") #------------ class man (human): def __init__(self): print("man()") super().__init__() def __str__(self): return "man" def __del__(self): print("~man()") super().__del__() ####### main ####### print('----------') Man1 = man() print('----------') print(Man1) print('----------') display_function_timing() print('----------')
| ||||||||
56) Glossary |
|
© Copyright Samir Amberkar 2023-24