about
Bits Generic Python
05/29/2023
Bits: Python Generics
code, output, and build for Python on Windows, macOS, and Linux
Synopsis:
This page demonstrates uses of Python Generic types and their objects using typing import. The purpose is to quickly acquire some familiarity with syntax and behavior of generic typing.- Python defines a typing module providing support for type hints.
- The translator ignores typing. You need to use a static analysis tool like mypy.py to detect type errors.
Demo Notes
1.0 CodeSnaps
Source Code - Py_Generic.py
#---------------------------------------------------------- # Py_Generic.py # # Python type hints and generics # - Demonstrates generic and type hint syntax # - Shows how to build user-defined generic type # - Type hints are ignored by the Python interpreter # - To check for type errors use tool mypy, e.g.: # # mypy Py_Generic.py --check-untyped-defs # # mypy requires installation using pip #---------------------------------------------------------- import copy import AnalysisGen import PointsGen # short-hand identifiers Anal = AnalysisGen Points = PointsGen def execute() : print(" Demonstrate Python Generics") print("----------------------------", "\n") Anal.showNote( " Python introduced generics and type hints\n"\ " to check for type errors using mypy, a static\n"\ " type checking tool." ) print() Anal.showNote("Type Hints", "\n") d1: float = 3.1415927 Anal.showType(d1, "d1") s1: str = "a string" Anal.showType(s1, "s1") Anal.showOp("s2 = s1") s2: str = s1 Anal.showIdent(s1, "s1") Anal.showIdent(s2, "s2") #print("s2 = {}".format(s2)) Anal.showOp("s2 += \" and more\"") s2 += " and more" Anal.showIdent(s2, "s2") Anal.showIdent(s1, "s1") # print("s2 = {}".format(s2)) # print("s1 = {}".format(s1)) print() Anal.showNote( "Assignment, in Python, assigns references not\n"\ "values. So s1 and s2 share same heap instance\n"\ "But strings are immutable. So when a change is\n"\ "made to one, that creates a new instance without\n"\ "changing the original." ) print() Anal.showNote("Generics", "\n") l1: list[str] = ["you", "me", "them", "us"] Anal.showType(l1, "l1", Anal.nl) print("l1 = ", l1) Anal.showOp("l2 = l1") l2 = l1 Anal.showOp('l2.append("everyone")') l2.append("everyone") print("l2 = ", l2) print("l1 = ", l1) Anal.showOp('l2[1] = "myself"') l2[1] = "myself" print("l2 = ", l2) print("l1 = ", l1) Anal.showNote( "Changes to target of assignment affect source\n"\ "except for immutable strings, as we are required\n"\ "to create new string instead of modify.\n"\ "\"caveat emptor\"" ) print() Anal.showNote("user defined type","\n") Anal.showOp("p1 = Points.PointN[float](8)") p1: Points.PointN = Points.PointN[float](10) # p1.append("3") p1[1] = 1.5 p1[3] = -2.0 index = p1.len() - 1 p1[index] = 42 Anal.showTypeEnum(p1, "p1", 0, 5) Anal.showOp("Anal.showTypeShowable(p1, \"p1\", nl)") Anal.showTypeShowable(p1, "p1", Anal.nl) print("len(p1) = {}".format(len(p1))) print("p1.len() = {}".format(p1.len())) print() p1[0] = 2 p1[1] = -3.5 p1[2] = -42 Anal.showOp("p1.show('p1')") p1.show("p1") # Anal.showTypeEnum(p1, "p1", 0, 7) Anal.showOp("p2 = p1") p2 = p1 p2.show("p2") Anal.showOp("p2[1] = 13") p2[1] = 13 p2.show("p2") p1.show("p1") Anal.showNote( "Reference assigned, not value.\nSo change"\ " in p2 changed source p1." ) print() Anal.showOp("p3 = copy.deepcopy(p2)") p3 = copy.deepcopy(p2) p3.show("p3"); p2.show("p2") Anal.showOp("p3[2] = 12") p3[2] = 12 p3.show("p3") p2.show("p2") Anal.showNote( "p3[2] reference assigned, not value. But no\n"\ "change in p2 since p3 is deep clone of p2." ) print() # reference behavior - new child object Anal.showOp("t5 = (1, 2, 3)") t5 = (1, 2, 3) Anal.showIdent(t5, "t5") Anal.showOp("t6 = [1, t5, \"weird\"]") t6 = [1, t5, "weird"] Anal.showIdent(t6, "t6") Anal.showType(t6, "t6") print("-- t5 = 1 + 1j : new object --") t5 = 1 + 1j Anal.showIdent(t5, "t5") Anal.showIdent(t6, "t6") Anal.showNote( "new object for t5, t6 not affected", Anal.nl ) # # reference behavior - iterate over children print("-- iterate over t6 children --") for i in t6: Anal.showIdent(i, "elem") # print("\n-- iterate over t6 methods --") # print(dir(t6)) print("\nThat's all folks!\n") execute()
Source Code - PointsGen.py
#---------------------------------------------------------- # Py_Generic::PointsGen.py # PointN[T] # Point in N dimensional hyperspace with type T # coordinates. #---------------------------------------------------------- import AnalysisGen from typing import TypeVar, Generic # Generic point class with N coordinates # Python doesn't need generics to support types # defined at translation time, but they were # introduced to enable translation time type # error detection. T = TypeVar('T', bound=int | float) class PointN(Generic[T]): # supports constructor notation def __init__(self, n: int) -> None: # p = PointN<double>(8) self.coors:list[T] = [] for i in range(n): self.coors.append(0) def append(self, t: T): self.coors.append(t) def len(self) -> int: return len(self.coors) def __len__(self) -> int: # len(p) return len(self.coors) def __getitem__(self, key: int) -> T: # value = p[1] return self.coors[key] def __setitem__(self, key: int, val: T): # p[1] = value self.coors[key] = val # show named value of PointN[T] instance def show(self, name, left = 0, width = 7) : print(AnalysisGen.indent(left), name, ' ', sep='') print(AnalysisGen.indent(left), "{", sep='') print(AnalysisGen.fold(self.coors, left+2, width)) print(AnalysisGen.indent(left), "}", sep = '')
Source Code - AnalysisGen.py
#---------------------------------------------------------- # Py_Generic::AnalysisGen.py # - Collection of display and analysis functions #---------------------------------------------------------- import sys import collections from typing import TypeVar from collections.abc import Sequence T = TypeVar('T') # Coll = Union[Iterable, Sized] = 'Coll' # Python requires definition before use ordering # - no link phase to find definitions import copy nl = "\n" # show name, type, value, and size of a Python instance def showType(t:T, nm:str, suffix = "") : print(nm, type(t), "dynamic") print("value: ", t, ', size: ', sys.getsizeof(t), suffix) # generate indent string with n spaces def indent(n:int): tmpStr = "" for i in range(n): tmpStr += ' ' return tmpStr # fold indexable into rows of width elements indented by # left spaces def fold(enum: Sequence, left:int, width:int) -> str: tmpStr = indent(left) for i in range(len(enum)): tmpStr += str(enum[i]) + ", " if(((i + 1) % width) == 0 and i != 0): tmpStr += "\n" + indent(left) rIndex = tmpStr.rindex(',') tmpStr = tmpStr[:rIndex] return tmpStr # show name, type, value, and size of a Python instance def showTypeEnum(enum, nm, left = 2, width = 7, suffix = "") : # topStr = indent(left) + nm + type(enum) + "dynamic" print(indent(left),nm, ' ', type(enum), ' ', "dynamic", sep='') print(indent(left), "{", sep='') print(fold(enum, left+2, width)) print(indent(left), "}", sep = '') print(indent(left), "size: ", sys.getsizeof(enum), suffix, sep='') # same as showType except uses class method to show value def showTypeShowable(t, nm, suffix = ""): print(type(t), "dynamic") t.show(nm) # show Python id, unique for each instance def showIdent(t, name, suffix = "") : print(name, '"{}"'.format(t), id(t), suffix) # show emphasized note def showNote(text, suffix = "", n: int = 50) : tmpStr = "" for i in range(n): tmpStr += '-' print(tmpStr) print(text) print(tmpStr, suffix) # show delineated string to announce a program operation def showOp(text): print("--- {} ---".format(text))
Output
Demonstrate Python Generics ---------------------------- -------------------------------------------------- Python introduced generics and type hints to check for type errors using mypy, a static type checking tool. -------------------------------------------------- -------------------------------------------------- Type Hints -------------------------------------------------- d1dynamic value: 3.1415927 , size: 24 s1 dynamic value: a string , size: 57 --- s2 = s1 --- s1 "a string" 2287189174896 s2 "a string" 2287189174896 --- s2 += " and more" --- s2 "a string and more" 2287189186064 s1 "a string" 2287189174896 -------------------------------------------------- Assignment, in Python, assigns references not values. So s1 and s2 share same heap instance But strings are immutable. So when a change is made to one, that creates a new instance without changing the original. -------------------------------------------------- -------------------------------------------------- Generics -------------------------------------------------- l1 dynamic value: ['you', 'me', 'them', 'us'] , size: 88 l1 = ['you', 'me', 'them', 'us'] --- l2 = l1 --- --- l2.append("everyone") --- l2 = ['you', 'me', 'them', 'us', 'everyone'] l1 = ['you', 'me', 'them', 'us', 'everyone'] --- l2[1] = "myself" --- l2 = ['you', 'myself', 'them', 'us', 'everyone'] l1 = ['you', 'myself', 'them', 'us', 'everyone'] -------------------------------------------------- Changes to target of assignment affect source except for immutable strings, as we are required to create new string instead of modify. "caveat emptor" -------------------------------------------------- -------------------------------------------------- user defined type -------------------------------------------------- --- p1 = Points.PointN[float](8) --- p1 dynamic { 0, 1.5, 0, -2.0, 0, 0, 0, 0, 0, 42 } size: 48 --- Anal.showTypeShowable(p1, "p1", nl) --- dynamic p1 { 0, 1.5, 0, -2.0, 0, 0, 0, 0, 0, 42 } len(p1) = 10 p1.len() = 10 --- p1.show('p1') --- p1 { 2, -3.5, -42, -2.0, 0, 0, 0, 0, 0, 42 } --- p2 = p1 --- p2 { 2, -3.5, -42, -2.0, 0, 0, 0, 0, 0, 42 } --- p2[1] = 13 --- p2 { 2, 13, -42, -2.0, 0, 0, 0, 0, 0, 42 } p1 { 2, 13, -42, -2.0, 0, 0, 0, 0, 0, 42 } -------------------------------------------------- Reference assigned, not value. So change in p2 changed source p1. -------------------------------------------------- --- p3 = copy.deepcopy(p2) --- p3 { 2, 13, -42, -2.0, 0, 0, 0, 0, 0, 42 } p2 { 2, 13, -42, -2.0, 0, 0, 0, 0, 0, 42 } --- p3[2] = 12 --- p3 { 2, 13, 12, -2.0, 0, 0, 0, 0, 0, 42 } p2 { 2, 13, -42, -2.0, 0, 0, 0, 0, 0, 42 } -------------------------------------------------- p3[2] reference assigned, not value. But no change in p2 since p3 is deep clone of p2. -------------------------------------------------- --- t5 = (1, 2, 3) --- t5 "(1, 2, 3)" 2287189172608 --- t6 = [1, t5, "weird"] --- t6 "[1, (1, 2, 3), 'weird']" 2287192885120 t6 dynamic value: [1, (1, 2, 3), 'weird'] , size: 80 -- t5 = 1 + 1j : new object -- t5 "(1+1j)" 2287188549968 t6 "[1, (1, 2, 3), 'weird']" 2287192885120 -------------------------------------------------- new object for t5, t6 not affected -------------------------------------------------- -- iterate over t6 children -- elem "1" 2287187525872 elem "(1, 2, 3)" 2287189172608 elem "weird" 2287189178928 That's all folks! C:\github\JimFawcett\Bits\Python\Py_Generic >
Build
C:\github\JimFawcett\Bits\Python\Py_Generic > python Py_Generic.py
2.0 VS Code View
3.0 References
Reference | Description |
---|---|
Python Type Hints | Syntax for type hinting - requires static analysis tool mypy.py to catch type errors. |
Python Tutorial - w3schools | Interactive examples |
Python Reference - docs.python.org | Semi-formal syntax reference |