Get started !
online LTE test
online C test

Updated or New
GPRS RAN refresh notes New
GSM RAN refresh notes New



About
Feedback
Information Theory
Modulation
Multiple Access
DSP (wip)
OSI Model
Data Link layer
SS7
Word about ATM
GSM
GPRS
UMTS
WiMAX
LTE
CV2X
5G
Standard Reference
Reference books
Resources on Web
Miscellaneous
Mind Map
Magic MSC tool
Bar graph tool
C programming
C++ programming
Perl resources
Python programming
Javascript/HTML
MATLAB
ASCII table
Project Management

another knowledge site

3GPP Modem
Simulator


Sparkle At Office comic strip

Python Programming

Index
1)Versions, documentation
2)Books on Python
3)Hello World !
4)Three syntax rules
5)Multiline statement
6)String str
7)List list
8)Slicing the list
9)Dictionary dict
10)tuple
11)File
12)print
13)type
14)Ternary selection "x if y else z"
15)Set set
16)Shallow copy and deep copy
17)None
18)Few regularly used programming statements
19)Difference between operators == and is
20)Iterators (manual)
21)Factory function
22)nonlocal
23)global
24)Mutability of function parameters
25)Function parameters (arguments)
26)Function annotations
27)lambda expression
28)filter, functools/reduce, and map iterators
29)Comprehension
30)Generator function using yield
31)yield with iterator
32)Comprehension iterator
33)Modules
34)Classes
35)abstractmethod
36)Creating your own container object
37)staticmethod
38)classmethod
39)User defined function decorator
40)User defined exception handling
41)Context management
42)User defined context management
43)Unicode strings
44)property decorator
45)descriptor class
46)__setattr__ and __getattr__
47)User defined Class decorator
48)Implementing Singeton decorator
49)Implementing public and private attributes with decorator
50)Continue discussion of nested decorators
51)Function decorator for function (or method)
52)Metaclass
53)Another example of metaclass
54)Example of using metaclass effectively (with decorator)
55)Execution time measurements with metaclass
56)Glossary

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.

Official site python.org
Documentation docs.python.org
Tutorials wiki.python.org
Download/install download page at wiki.python.org

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")

Hello World ! on linux with Python 3.10.12 ------------- press any key to exit

4) Three syntax rules Below syntax rules differentiate Python from C-like languages.

  • We may drop Parentheses ()
  • .. in many places, like if conditions, for statement, and so on.

  • Statement ends at end of line (implicitly)
  • No need for semicolon (;) unless you want to put number of statements in one line.

  • Block is indicated by indentation
  • No need for curly brackets ({}); indent the code properly and we are good.

    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('----------')

    This is a long storry ... ... it starts with ..... We can even use '"""' for multiline commenting ... .. or for disabling part of the code .... ['person1', 'person2', 'person3'] Right group Right group ----------

    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('----------')

    ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 're'] ---------- The string is "Python is an object oriented programming language" The string is "PYTHON IS AN OBJECT ORIENTED PROGRAMMING LANGUAGE" The string is "python is an object oriented programming language" Is it alpha ? - 0 Is string "Pythonisanobjectorientedprogramminglanguage" alpha ? - 1 Replaced "C++ is an object oriented programming language" With format - Python is an object oriented programming language ['Python', 'is', 'an', 'object', 'oriented', 'programming', 'language'] ---------- ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] ---------- Python ----------

    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('----------')

    family = ['Husband', 'Wife', 'Son', 'Daughter'] number of members = 4 children = ['Son', 'Daughter'] twice_family = ['Husband', 'Wife', 'Son', 'Daughter', 'Husband', 'Wife', 'Son', 'Daughter'] extended_family = ['Husband', 'Wife', 'Son', 'Daughter', 'Father-in-law', 'Mother-in-law'] second from last = Father-in-law extended_family after popping 5th member (i.e. 'Father-in-law') = ['Husband', 'Wife', 'Son', 'Daughter', 'Mother-in-law'] few more in extended_family = ['Husband', 'Wife', 'Son', 'Daughter', 'Mother-in-law', 'Brother-in-law', 'Sister-in-law'] extended_family after removing 6th member = ['Husband', 'Wife', 'Son', 'Daughter', 'Mother-in-law', 'Sister-in-law'] extended_family after sort = ['Daughter', 'Husband', 'Mother-in-law', 'Sister-in-law', 'Son', 'Wife'] Reduced ['Daughter', 'Husband', 'Mother-in-law', 'Sister-in-law', 'Son', 'Wife'] Reduced ['Husband', 'Mother-in-law', 'Sister-in-law', 'Son', 'Wife'] Reduced ['Mother-in-law', 'Sister-in-law', 'Son', 'Wife'] Reduced ['Sister-in-law', 'Son', 'Wife'] Reduced ['Son', 'Wife'] Reduced ['Wife'] ---------- ['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] ----------

    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('----------')

    big_family = ['Husband', 'Wife', 'Son', 'Daughter', 'Father', 'Mother', 'Father-in-law', 'Mother-in-law'] [1:3] ['Wife', 'Son'] [1:] ['Wife', 'Son', 'Daughter', 'Father', 'Mother', 'Father-in-law', 'Mother-in-law'] [:3] ['Husband', 'Wife', 'Son'] [:-1] ['Husband', 'Wife', 'Son', 'Daughter', 'Father', 'Mother', 'Father-in-law'] [-1:] ['Mother-in-law'] [::] all items [::] ['Husband', 'Wife', 'Son', 'Daughter', 'Father', 'Mother', 'Father-in-law', 'Mother-in-law'] [::-1] reverses the list, right ? [::-1] ['Mother-in-law', 'Father-in-law', 'Mother', 'Father', 'Daughter', 'Son', 'Wife', 'Husband'] [1:len(big_family)-1:2] ['Wife', 'Daughter', 'Mother'] [0:len(big_family)-1:2] ['Husband', 'Son', 'Father', 'Father-in-law'] ----------

    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('----------')

    {'husband': 'Husband', 'wife': 'Wife'} Husband {'husband': 'Husband', 'wife': 'Wife'} Wife {'father-in-law': 'Father-in-law', 'mother-in-law': 'Mother-in-law'} Father-in-law husband is Husband in-laws is {'father-in-law': 'Father-in-law', 'mother-in-law': 'Mother-in-law'} wife is Wife no brother ---------- ['__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__ior__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__ror__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'] ---------- 0 ----------

    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('----------')

    ('husband', 'wife', 'children') wife 3 ('husband', 'wife', 'children', 'father', 'mother', 'father-in-law', 'mother-in-law') ---------- ['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] ----------

    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('----------')

    ---------- What a life ?!! An awesome programmer's life !! Programming never ends. More later... ---------- What a life ?!! An awesome programmer's life !! Programming never ends. More later... ---------- What a life ?!! An awesome programmer's life !! Programming never ends. More later... ---------- What a life ?!! An awesome programmer's life !! Programming never ends. More later... ---------- ['What a life ?!!', "An awesome programmer's life !!", 'Programming never ends.', 'More later...'] ---------- ["An awesome programmer's life !!"] ---------- ['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'reconfigure', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'write_through', 'writelines'] ----------

    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('----------')

    Husband, Wife, Son, Daughter Husband, Wife, Son, Daughter Husband, Wife, Son, Daughter Husband, Wife, Son, Daughter Husband, Wife, Son, Daughter ----------

    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('----------')

    <class 'str'> <class 'list'> <class 'dict'> <class 'tuple'> <class '_io.TextIOWrapper'> <class 'type'> <class 'bool'> <class 'bool'> family is of type 'list' string is of type 'str' nuclear_family is of type 'tuple' ---------- ['__abstractmethods__', '__annotations__', '__base__', '__bases__', '__basicsize__', '__call__', '__class__', '__delattr__', '__dict__', '__dictoffset__', '__dir__', '__doc__', '__eq__', '__flags__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__instancecheck__', '__itemsize__', '__le__', '__lt__', '__module__', '__mro__', '__name__', '__ne__', '__new__', '__or__', '__prepare__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__setattr__', '__sizeof__', '__str__', '__subclasscheck__', '__subclasses__', '__subclasshook__', '__text_signature__', '__weakrefoffset__', 'mro'] ----------

    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('----------')

    Let us use Python Let us use Python ----------

    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('----------')

    Workers - {'man1', 'woman2', 'woman1', 'man2'} Managers - {'woman3'} Employees - {'woman3', 'woman1', 'man2', 'man1', 'woman2'} Employees who are Managers - {'woman3'} Employees who are not Managers - {'woman1', 'woman2', 'man1', 'man2'} employess set is superset of workers set ---------- ['__and__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update'] ----------

    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('----------')

    Soccer team - ['Man1', 'Man2', 'Man3'] Cricket team - ['Man1', 'Man2', 'Man3'] replacing 3rd man in Soccer team Soccer team - ['Man1', 'Man2', 'Man4'] Cricket team - ['Man1', 'Man2', 'Man4'] This is not what we want Let us do it again Soccer team - ['Man1', 'Man2', 'Man3'] Cricket team - ['Man1', 'Man2', 'Man3'] replacing 3rd man in Soccer team Soccer team - ['Man1', 'Man2', 'Man4'] Cricket team - ['Man1', 'Man2', 'Man3'] Ok now ----------

    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('----------')

    <class 're.Match'> <class 'NoneType'> Awesome ! Let us use Python. We cannot use C for efficient OOP. ----------

    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('----------')

    hero = good villain = bad All consistent. Villain is bad Cheers, Hero ! Cheers, Hero ! Cheers, Hero ! Cheers done. Hero saved us from Villain ----------

    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('----------')

    hero and common_man are one and the same imposter and villain are one and the same common_man changed to bad hero and common_man are *not* same common_man and villain are one and the same heros and fake_heros looks same heros and fake_heros are *not* same ----------

    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('----------')

    automatic iteration (built-in) Man Woman Boy Girl ---------- manual iteration Man Woman Boy Girl <class 'list_iterator'> ---------- automatic iteration (built-in) of dictionary 1) Using items ... husband is Man wife is Woman son is Boy daughter is Girl ---------- 2) Using sorted (key sorting) ... daughter is Girl husband is Man son is Boy wife is Woman ---------- manual iteration of dictionary husband is Man wife is Woman son is Boy daughter is Girl <class 'dict_keyiterator'> ---------- ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__'] ---------- ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] ----------

    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('----------')

    2 * 1 = 2 2 * 2 = 4 2 * 3 = 6 2 * 4 = 8 2 * 5 = 10 2 * 6 = 12 2 * 7 = 14 2 * 8 = 16 2 * 9 = 18 2 * 10 = 20 ---------- 3 * 1 = 3 3 * 2 = 6 3 * 3 = 9 3 * 4 = 12 3 * 5 = 15 3 * 6 = 18 3 * 7 = 21 3 * 8 = 24 3 * 9 = 27 3 * 10 = 30 <class 'function'> ---------- ['__annotations__', '__builtins__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] ----------

    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('----------'

    1: 4^2 = 16 2: 25^2 = 625 ----------

    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('----------')

    1: 4^2 = 16 2: 25^2 = 625 So far 2 operations ----------

    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('----------')

    Man1 is Husband Woman1 is Wife Girl1 is first child ---------- Man2 is Husband Woman2 is Wife ---------- Man3 is Husband Woman3 is Wife Boy3 is first child Girl3 is second child ---------- Man4 Woman4 Girl4 Boy4 ---------- King is King1 Queen is Queen1 Prince is Prince1 Princess is Princess1 ----------

    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('----------')

    Annotation for get_member_by_relation {'family': <class 'dict'>, 'relation': <class 'str'>, 'return': <class 'str'>} Husband is husband no brother ----------

    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('----------')

    2 * 1 = 2 2 * 2 = 4 2 * 3 = 6 2 * 4 = 8 2 * 5 = 10 2 * 6 = 12 2 * 7 = 14 2 * 8 = 16 2 * 9 = 18 2 * 10 = 20 ---------- 3 * 1 = 3 3 * 2 = 6 3 * 3 = 9 3 * 4 = 12 3 * 5 = 15 3 * 6 = 18 3 * 7 = 21 3 * 8 = 24 3 * 9 = 27 3 * 10 = 30 ----------

    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('----------')

    All mothers ... Mother Mother-in-law All members ... Husband, Wife, Son, Daughter, Father, Mother, Father-in-law, Mother-in-law Dear Husband, Dear Wife, Dear Son, Dear Daughter, Dear Father, Dear Mother, Dear Father-in-law, Dear Mother-in-law, I love my_family. ----------

    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('----------')

    All possible pairs (Husband1, Husband2) (Husband1, Wife2) (Husband1, Son2) (Husband1, Daughter2) (Wife1, Husband2) (Wife1, Wife2) (Wife1, Son2) (Wife1, Daughter2) (Son1, Husband2) (Son1, Wife2) (Son1, Son2) (Son1, Daughter2) (Daughter1, Husband2) (Daughter1, Wife2) (Daughter1, Son2) (Daughter1, Daughter2) All possible female pairs (Wife1, Wife2) (Wife1, Daughter2) (Daughter1, Wife2) (Daughter1, Daughter2) ----------

    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('----------')

    Enter X to stop 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97X ----------

    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('----------')

    Enter non-integer or zero to stop:300 1987 Enter non-integer or zero to stop:35 149 Enter non-integer or zero to stop:10000 104729 Enter non-integer or zero to stop:500 3571 Enter non-integer or zero to stop:10000 104729 Enter non-integer or zero to stop:50000 611953 Enter non-integer or zero to stop:50000 611953 Enter non-integer or zero to stop:49000 598687 Enter non-integer or zero to stop: ----------

    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('----------')

    Generating list of first 10000 primes ... Press any key to see next twin primes (enter X to stop) (3, 5) (5, 7) (11, 13) (17, 19) (29, 31) (41, 43) (59, 61) (71, 73) (101, 103)X ----------

    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

    I own Mercedes. My friend owns Mercedes. Now I own BMW. Now my friend owns Mercedes. ---------- ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'car', 'which_car_do_you_own'] ---------- Now my friend owns Volvo. Now I own BMW. Reloading friend Now my friend owns Mercedes. Now I own BMW. Now my friend owns Mercedes. Now I own BMW. ----------

    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('----------')

    man(Normal_guy) human() INFO: object man(name='Normal_guy') I am a Normal_guy. Normal_guy spider() Mysterious_Spider spiderman() Spiderman Duplicate is same as Normal_guy Another_guy is *not* same as Normal_guy Another_guy Normal_guy Normal_guy thief ---------- ['__add__', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slotnames__', '__str__', '__subclasshook__', '__weakref__', 'name_change', 'speak', 'subclass_to_implement'] ---------- ~human() ~man(thief) ~spider(Mysterious_Spider) ~spiderman(Spiderman) ~human() ~man(Another_guy)

    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('----------')

    man() ----------

    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('----------')

    man(Man1) man(Man2) woman(Woman1) woman(Woman2) group() ---------- We have weekend party group of size 4. Who are there in the group ? ... Man1, Man2, Woman1, Woman2 ---------- Let us introduce ourselves ... My name is Man1. My name is Man2. My name is Woman1. My name is Woman2. ---------- First two members are Man1, Man2 Last two members are Woman2, Woman1 ---------- ~man(Man1) ~man(Man2) ~woman(Woman1) ~woman(Woman2) ~group()

    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('----------')

    man(Man1) human(DNA# 1) woman(Woman1) human(DNA# 2) man(Man2) human(DNA# 3) woman(Woman2) human(DNA# 4) ---------- human.get_population() Population is 4. man.get_population() Population is 4. man.get_population() Population is 4. human.get_population() Population is 4. human.get_population() Population is 4. ---------- ~man(Man1) ~human(DNA# 1) ~woman(Woman1) ~human(DNA# 2) ~man(Man2) ~human(DNA# 3) ~woman(Woman2) ~human(DNA# 4)

    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('----------')

    man(Man1) human(DNA# 1) woman(Woman1) human(DNA# 2) man(Man2) human(DNA# 3) woman(Woman2) human(DNA# 4) man(Boy1) human(DNA# 5) ---------- human.get_population() Population is 5. man.get_population() Population is 5. man.get_population() Population is 5. human.get_population() Population is 5. human.get_population() Population is 5. Number of men are 2. Number of men are 2. Number of boys are 1. Number of boys are 1. Number of women are 2. Number of women are 2. ---------- ~man(Man1) ~human(DNA# 1) ~woman(Woman1) ~human(DNA# 2) ~man(Man2) ~human(DNA# 3) ~woman(Woman2) ~human(DNA# 4) ~man(Boy1) ~human(DNA# 5)

    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('----------')

    function1 called function11 called function21 called 21 function22 called 22 11 function12 called 12 1 ---------- Function sequence logged is (chronological order): function1() function11() function21() function22() function12() ----------

    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('----------')

    ---------- Weekend_partE_group: Man1, Man2, Woman1, Woman2. ---------- Added Friend. Updated Weekend_partE_group: Man1, Man2, Woman1, Woman2, Friend. ---------- Could not add Stranger; 'Group is full (size_limit = 5)' Updated Weekend_partE_group: Man1, Man2, Woman1, Woman2, Friend. ---------- Error: 'Group is full (size_limit = 5)' ---------- finally from chained exception Error (chained): 'member addition failed' 'Group is full (size_limit = 5)' finally from main caller ----------

    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('----------')

    File contents: <---------- What a life ?!! An awesome programmer's life !! Programming never ends. More later... ----------> ----------

    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('----------')

    ---------- group.__enter__() Adding Man1, Man2, Woman1, Woman2.. Adding Friend.. Adding Stranger.. group.__exit__() Error ocurred 'Group is full (size_limit = 5)'. Weekend_partE_group: Man1, Man2, Woman1, Woman2, Friend. ----------

    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('----------')

    First 4 letters of Devanagiri script are: अ, आ, इ, and ई. Error: "'ascii' codec can't encode character '\u0905' in position 0: ordinal not in range(128)" Error: "'latin-1' codec can't encode character '\u0905' in position 0: ordinal not in range(256)" b'\xe0\xa4\x85, \xe0\xa4\x86, \xe0\xa4\x87, and \xe0\xa4\x88.' b'\xff\xfe\x05\t,\x00 \x00\x06\t,\x00 \x00\x07\t,\x00 \x00a\x00n\x00d\x00 \x00\x08\t.\x00' b'\xff\xfe\x00\x00\x05\t\x00\x00,\x00\x00\x00 \x00\x00\x00\x06\t\x00\x00,\x00\x00\x00 \x00\x00\x00\x07\t\x00\x00,\x00\x00\x00 \x00\x00\x00a\x00\x00\x00n\x00\x00\x00d\x00\x00\x00 \x00\x00\x00\x08\t\x00\x00.\x00\x00\x00' Ordinal value of 'अ' is 2309. Unicode for 'अ' is \u0905 ----------

    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('----------')

    man(Man) get_age() Man's age is 0. change_age() get_age() Now Man's age is 32. man(Boy) get_age() Boy's age is 10. ---------- ~man(Man) ~man(Boy)

    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('----------')

    man(Man) age() Man's age is 0. age setter() age() Now Man's age is 32. man(Boy) age() Boy's age is 10. ---------- ~man(Man) ~man(Boy)

    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('----------')

    man(Man) age_descriptor get() Man's age is 0. age_descriptor set() age_descriptor get() Now Man's age is 32. man(Boy) age_descriptor get() Boy's age is 10. ---------- ~man(Man) ~man(Boy)

    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('----------')

    man(Man) age_descriptor get() Man's age is 0. age_descriptor set() age_descriptor get() Now Man's age is 32. man(Boy) age_descriptor get() Boy's age is 10. ---------- ~man(Man) ~man(Boy)

    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('----------')

    man(Man) __setattr__() __setattr__() __getattr__() Man's age is 0. __setattr__() __getattr__() Now Man's age is 32. man(Boy) __setattr__() __setattr__() __getattr__() Boy's age is 10. ---------- ~man(Man) ~man(Boy)

    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('----------')

    undercover() cop() drug_dealer() ---------- __getattr__() Pass on information to police department do_drug_peddling() ---------- cop() drug_dealer() ---------- ~cop() ~cop() ~drug_dealer() ~drug_dealer()


    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('----------')

    undercover() drug_dealer() cop() ---------- __getattr__() Pass on information to police department do_drug_peddling() ---------- drug_dealer() cop() ---------- ~cop() ~cop() ~drug_dealer() ~drug_dealer()

    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('----------')

    one_and_only_one() on_instantiation() God() God created on_instantiation() on_instantiation() ---------- ~God()


    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('----------')

    one_and_only_one() on_instantiation() God() God created on_instantiation() on_instantiation() ---------- ~God()

    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('----------')

    Private_Attributes() Public_Attributes() Public_Attributes.instantiate_interceptor() Private_Attributes.instantiate_interceptor() ====> Testing 'man' <==== Private_Attributes.attribute_interceptor() Public_Attributes.attribute_interceptor() man() Public_Attributes.__setattr__() Private_Attributes.__setattr__() ---------- Private_Attributes.__getattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Setting public attribute worked. Private_Attributes.__getattr__() Public_Attributes.__getattr__() Sport ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Error: "Attribute 'age' is not present." ---------- Private_Attributes.__str__() Public_Attributes.__str__() man.__str__() Man1 ---------- Private_Attributes.__getattr__() Public_Attributes.__getattr__() I am Man1. ====> Testing 'boy' <==== Private_Attributes.attribute_interceptor() Public_Attributes.attribute_interceptor() man() Public_Attributes.__setattr__() Private_Attributes.__setattr__() ---------- Private_Attributes.__getattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Setting public attribute worked. Private_Attributes.__getattr__() Public_Attributes.__getattr__() Music ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Error: "Attribute 'age' is not present." ---------- Private_Attributes.__str__() Public_Attributes.__str__() man.__str__() Boy1 ---------- Private_Attributes.__getattr__() Public_Attributes.__getattr__() I am Boy1. ---------- Private_Attributes.~attribute_interceptor() Private_Attributes.~attribute_interceptor() Public_Attributes.~attribute_interceptor() Public_Attributes.~attribute_interceptor() ~man() ~man()

    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('----------')

    Private_Attributes() Public_Attributes() Public_Attributes.instantiate_interceptor() Private_Attributes.instantiate_interceptor() ====> Testing 'man' <==== Private_Attributes.attribute_interceptor() Public_Attributes.attribute_interceptor() man() Public_Attributes.__setattr__() Private_Attributes.__setattr__() ---------- Private_Attributes.__getattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Setting public attribute worked. Private_Attributes.__getattr__() Public_Attributes.__getattr__() Sport ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Error: "Attribute 'age' is not present." ---------- base_attribute_interceptor() Private_Attributes.__getattr__() base_attribute_interceptor() Public_Attributes.__getattr__() man.__str__() Man1 ---------- Private_Attributes.__getattr__() Public_Attributes.__getattr__() I am Man1. ====> Testing 'boy' <==== Private_Attributes.attribute_interceptor() Public_Attributes.attribute_interceptor() man() Public_Attributes.__setattr__() Private_Attributes.__setattr__() ---------- Private_Attributes.__getattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Setting public attribute worked. Private_Attributes.__getattr__() Public_Attributes.__getattr__() Music ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Error: "Attribute 'age' is not present." ---------- base_attribute_interceptor() Private_Attributes.__getattr__() base_attribute_interceptor() Public_Attributes.__getattr__() man.__str__() Boy1 ---------- Private_Attributes.__getattr__() Public_Attributes.__getattr__() I am Boy1. ---------- Private_Attributes.~attribute_interceptor() Private_Attributes.~attribute_interceptor() Public_Attributes.~attribute_interceptor() Public_Attributes.~attribute_interceptor() ~man() ~man()


    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('----------')

    Private_Attributes() Public_Attributes() Public_Attributes.instantiate_interceptor() Private_Attributes.instantiate_interceptor() ====> Testing 'man' <==== Private_Attributes.attribute_interceptor() Public_Attributes.attribute_interceptor() man() Public_Attributes.__setattr__() Private_Attributes.__setattr__() ---------- Private_Attributes.__getattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Setting public attribute worked. Private_Attributes.__getattr__() Public_Attributes.__getattr__() Sport ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Error: "Attribute 'age' is not present." ---------- base_attribute_interceptor() base_attribute_interceptor() man.__str__() Man1 ---------- Private_Attributes.__getattr__() Public_Attributes.__getattr__() I am Man1. ====> Testing 'boy' <==== Private_Attributes.attribute_interceptor() Public_Attributes.attribute_interceptor() man() Public_Attributes.__setattr__() Private_Attributes.__setattr__() ---------- Private_Attributes.__getattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Error: "Attribute 'name' is private." ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Setting public attribute worked. Private_Attributes.__getattr__() Public_Attributes.__getattr__() Music ---------- Private_Attributes.__setattr__() Public_Attributes.__setattr__() Error: "Attribute 'age' is not present." ---------- base_attribute_interceptor() base_attribute_interceptor() man.__str__() Boy1 ---------- Private_Attributes.__getattr__() Public_Attributes.__getattr__() I am Boy1. ---------- Private_Attributes.~attribute_interceptor() Private_Attributes.~attribute_interceptor() Public_Attributes.~attribute_interceptor() Public_Attributes.~attribute_interceptor() ~man() ~man()

    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('----------')

    validate_age() function_interceptor() man() ---------- argument_interceptor() age OK ---------- argument_interceptor() age invalid ---------- Man1(32) ---------- ~man()


    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('----------')

    validate_age() function_interceptor() argument_interceptor() age OK age_function(32) ---------- argument_interceptor() age invalid ----------

    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('----------')

    God.__new__(man) God.__init__(man) ---------- man() human() ---------- God.__del__(<class '__main__.man'>) ~man() ~human()


    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('----------')

    Universe(man) ---------- man() human() ---------- ~man() ~human()


    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('----------')

    Devil(man) Devil.__New__(man) Devil.__Init__(man) ---------- man() human() ---------- ~man() ~human()

    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('----------')

    God.__new__(human) God.__init__(human) God.__new__(bird) God.__init__(bird) God.__new__(fish) God.__init__(fish) ---------- human() ---------- I am human. ---------- bird() ---------- I am bird. ---------- fish() ---------- I am fish. ---------- God.__del__(<class '__main__.human'>) God.__del__(<class '__main__.bird'>) God.__del__(<class '__main__.fish'>) ~human() ~bird() ~fish()

    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('----------')

    God.__new__(human) function_watch(<function human.__init__ at 0x7f1c0e05b5b0>) function_watch(<function human.__del__ at 0x7f1c0e05b640>) God.__init__(human) God.__new__(man) function_watch(<function man.__init__ at 0x7f1c0e05b7f0>) function_watch(<function man.__str__ at 0x7f1c0e05b880>) function_watch(<function man.__del__ at 0x7f1c0e05b910>) God.__init__(man) ---------- man() human() ---------- man ---------- <function man.__init__ at 0x7f1c0e05b7f0>() <function human.__init__ at 0x7f1c0e05b5b0>() <function man.__str__ at 0x7f1c0e05b880>() ---------- God.__del__(<class '__main__.human'>) God.__del__(<class '__main__.man'>) ~man() ~human()


    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('----------')

    God_who_watch(<function function_watch at 0x7f46462ce440>) God.__new__(human) function_watch(<function human.__init__ at 0x7f46462cf400>) function_watch(<function human.__del__ at 0x7f46462cf640>) God.__init__(human) God.__new__(man) function_watch(<function man.__init__ at 0x7f46462cf7f0>) function_watch(<function man.__str__ at 0x7f46462cf880>) function_watch(<function man.__del__ at 0x7f46462cf910>) God.__init__(man) ---------- man() human() ---------- man ---------- <function man.__init__ at 0x7f46462cf7f0>() <function human.__init__ at 0x7f46462cf400>() <function man.__str__ at 0x7f46462cf880>() ---------- God.__del__(<class '__main__.human'>) God.__del__(<class '__main__.man'>) ~man() ~human()

    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('----------')

    God_who_watch(<function function_timing at 0x7ff27b652440>) God.__new__(human) function_timing(<function human.__init__ at 0x7ff27b653400>) function_timing(<function human.__del__ at 0x7ff27b653640>) God.__init__(human) God.__new__(man) function_timing(<function man.__init__ at 0x7ff27b6537f0>) function_timing(<function man.__str__ at 0x7ff27b653880>) function_timing(<function man.__del__ at 0x7ff27b653910>) God.__init__(man) ---------- man() human() ---------- man ---------- <function human.__init__ at 0x7ff27b653400>()(17 µs) <function man.__init__ at 0x7ff27b6537f0>()(40 µs) <function man.__str__ at 0x7ff27b653880>()(0 µs) ---------- God.__del__(<class '__main__.human'>) God.__del__(<class '__main__.man'>) ~man() ~human()

    56) Glossary
    KeyError - 9
    None - 17
    StopIteration - 32
    ValueError - 31
    __Init__ - 52
    __New__ - 52
    __add__ - 36
    __class__ - 50
    __del__ - 34
    __delattr__ - 46
    __delete__ - 45, 45
    __dict__ - 46, 49, 50, 50
    __enter__ - 42
    __exit__ - 42
    __get__ - 45, 45
    __getattr__ - 46
    __getitem__ - 36
    __init__ - 34, 52
    __iter__ - 36
    __len__ - 36
    __new__ - 52
    __next__ - 31, 36
    __set__ - 45, 45
    __setattr__ - 46
    abstractmethod - 35
    append - 7, 30
    as - 33, 40, 41, 42
    attribute - 34
    break - 18
    class decorator - 47, 47
    classdict - 53
    classmethod - 38
    comprehension - 29
    comprehension iterator - 32
    constructor - 34
    continue - 18
    decorator - 54, 54
    deep copy - 16
    def - 21
    default parameter - 40
    del - 33
    deleter - 44
    descriptor - 45, 45
    destructor - 34
    dict - 9, 26
    elif - 18
    else - 14, 18, 40
    end - 11, 12
    except - 40
    exception - 40
    exception chaining - 40
    exception hierarchy - 40
    exception propagation - 40
    file - 12
    filter - 28
    finally - 40
    flush - 12
    for - 18
    from - 28, 33
    function decorator - 39
    functools - 28
    get - 26
    global - 23
    if - 14, 18
    import - 33
    in - 14
    is - 19
    iter - 20
    keys - 20
    lambda - 27, 28, 29
    list - 7
    map - 28
    metaclass - 52, 52, 52, 53, 54, 54, 55
    method - 34
    multiline - 5
    nested decorators - 49
    next - 20, 31
    nonlocal - 22, 48, 48
    open - 11, 41
    overloading - 34
    pass - 18
    pop - 7
    private attribute - 49
    property - 44, 44
    public attribute - 49
    random - 18
    range - 27
    reduce - 28
    reload - 33
    remove - 12
    rstrip - 11
    sep - 12
    setter - 44
    shallow copy - 16
    singleton - 48, 48
    slice - 36
    slicing - 8, 36
    sort - 7
    sorted - 20
    static - 37, 39
    staticmethod - 37, 38
    subclass - 34
    superclass - 34
    sys.stdout - 12
    ternary selection - 14
    time - 55
    try - 40
    type - 52, 52, 52
    unicode - 43
    while - 18
    with - 41, 42
    write - 11, 12
    yield - 30



    © Copyright Samir Amberkar 2023-24