about
Bits: Generic Python
07/25/2024
0
Bits: Python Generics
Python does not support generics, it does support type hints that are checked with mypy.py
Synopsis:
This page demonstrates uses of Python Generic type syntax 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 Python Generics and Abstract Base Classes
T = TypeVar('T')
def function(arg: T) -> T:
return arg
> mypy program.py
T = TypeVar('T', bound=int | float)
class Hello(Generic[T]):
# code to define class operations
Container |
Generic base for containers that supports the |
Iterable |
Base for objects that support iteration, e.g., provide |
Sequence | Objects that support indexing and slicing |
Mapping | Base for dictionaries that supports key-based lookup and iteration. |
2.0 Source Code
- standard Python library collections
- user defined types
Hello[T] andPoints[T] - generic functions
2.1 Demonstrate Standard Library Generics
#-- demonstrate std collection types with generic hints
def demo_std_generics():
Anal.showNote(" demo std generics")
print()
Anal.showOp("list[int]")
l1: list[int] = [1, 2, 3, 2, 1]
print(type(l1))
print(l1)
print()
Anal.showOp("list[str]")
l2: list[str] = ["you", "me", "them", "us"]
print(type(l2))
print(l2)
print()
Anal.showOp("tuple(int, float, str)")
t: tuple[int, float, str] = (42, -0.5, "forty two")
print(type(t))
print(t)
print()
Anal.showOp("dict[str, int]")
d: dict[str, int] = {
"zero":0, "one":1, "two":2, "three":3
}
print(type(d))
print(d)
print()
--------------------------------------------------
demo std generics
--------------------------------------------------
--- list[int] ---
<class 'list'>
[1, 2, 3, 2, 1]
--- list[str] ---
<class 'list'>
['you', 'me', 'them', 'us']
--- tuple(int, float, str) ---
<class 'tuple'>
(42, -0.5, 'forty two')
--- dict[str, int] ---
<class 'dict'>
{'zero': 0, 'one': 1, 'two': 2, 'three': 3}
2.2 Hello
2.2.1 Hello Definition
- Python translator does not support generics. It doesn't need them because all varables are dynamically typed, e.g., assume the type of the data they hold.
- So you can pass any type as an argument of a given function. That will fail at runtime if an expression using the argument cannot be evaluated.
- Python ignores type hints, but those can be used by tool mypy.py to perform static type analyses. With static analysis, based on type hints, you discover errors through simple clear warnings and error messages instead of run-time exceptions, with more verbose error messages.
- Python supports default argument values, as shown below.
- The language provides properties that encapsulate class data while supporting public member syntax.
-
Python does not provide access controls. Data is private, by convention, if its name
begins with an underscore character, e.g.,
_myprivatedata = 1.5; _myprivatedata .
#-- user defined Hello type --#
from typing import TypeVar, Generic
T = TypeVar('T', bound=int | float)
class Hello(Generic[T]):
# supports constructor notation
def __init__(self, t: T = 0) -> None:
self._datum:T = t
# property supports public member syntax while
# keeping _datum encapsulated, e.g., not in
# public interface
@property
def value(self):
return self._datum
@value.setter
def value(self, t:T):
self._datum = t
@value.getter
def value(self):
return self._datum
# show named value of Point[T] instance.
# Uses default value for left.
def show(self, name, left = 0) :
print(AnalysisGen.indent(left), sep='', end='')
print(name, ": ", "Hello { ", self._datum, " }", sep='')
2.2.2 Hello Demonstration
#-- demonstrate user-defined generic types --#
def demo_user_defined_generics():
Anal.showNote(" demo user-defined generics")
print()
Anal.showOp("Hello(42)")
h1: Hello[int] = Hello(42)
print(" ", type(h1))
h1.show("h1", 2)
print()
Anal.showOp("Hello() uses default argument")
h2: Hello[float] = Hello()
h2.show("h2", 2)
print()
Anal.showOp("h2.value = 3.1415927")
h2.value = 3.1415927
h2.show("h2", 2)
print()
/* points code elided */
}
--------------------------------------------------
demo user-defined generics
--------------------------------------------------
--- Hello(42) ---
<class '__main__.Hello'>
h1: Hello { 42 }
--- Hello() uses default argument ---
h2: Hello { 0 }
--- h2.value = 3.1415927 ---
h2: Hello { 3.1415927 }
2.3 Point Class
2.3.1 Point Definition
#----------------------------------------------------------
# Py_Generic::PointsGen.py
# Point[T]
# Point in N dimensional hyperspace with type T
# coordinates.
#----------------------------------------------------------
import datetime
import time
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 Point(Generic[T]):
# supports constructor notation
def __init__(self, n: int) -> None: # p = Point<double>(8)
self.coors:list[T] = []
for i in range(n):
self.coors.append(0)
self._dt: datetime = datetime.datetime.now()
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
@property
def dt(self):
return self._dt
@dt.getter
def dt(self):
return self._dt
def now(self):
_dt = datetime.datetime.now()
# show named value of Point[T] instance
def show(self, name, left = 2, width = 7) :
print(AnalysisGen.indent(left), name, ' ', sep='')
print(AnalysisGen.indent(left), "{", sep='')
print(AnalysisGen.fold(self.coors, left+3, width), sep='')
print(AnalysisGen.indent(left + 2), self.dt)
print(AnalysisGen.indent(left), "}", sep = '')
Point[T]:
Point with n spatial dimensions anddatetime point ⇐ imports ⇐ class definition ⇐ constructor ⇐ indexers ⇐ time property ⇐ display method
2.3.2 Point Demonstration
#-- demonstrate user-defined generic types --#
def demo_user_defined_generics():
Anal.showNote(" demo user-defined generics")
print()
/* Hello code elided */
Anal.showOp("p1 = Points.Point[float](5)")
p1: Points.Point = Points.Point[float](5)
p1[1] = 1.5
p1[3] = -2.0
index = p1.len() - 1
p1[index] = 42
print(" ", type(p1))
p1.show("p1", 2)
print()
}
--------------------------------------------------
demo user-defined generics
--------------------------------------------------
--- p1 = Points.Point[float](5) ---
<class 'PointsGen.Point'>
p1
{
0, 1.5, 0, -2.0, 42
2024-07-24 21:46:01.293971
}
2.4 Generic Functions
2.4.1 Generic Function Definitions
fold(enum: Sequence, left:int, width:int) -> str
showTypeEnum(enum: Sequence, nm: str, left=2, width=7, suffix="")
showTypeShowable(t:T, nm: str, suffix="")
showIdent(t:T, name:str, suffix="")
showNote(text:str, suffix="", n:int=50)
showOp(text: str)
#----------------------------------------------------------
# Py_Generic::AnalysisGen.py
# - Collection of display and analysis functions
#----------------------------------------------------------
import sys
import collections
from collections.abc import Sequence
from typing import TypeVar
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, indnt:int = 2, suffix:str = "") :
print(indent(indnt), nm, ' ', type(t), " dynamic", sep="")
print(indent(indnt), "value: ", t, ', size: ', sys.getsizeof(t), suffix, sep="")
# 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:Sequence, nm:str, left:int = 2, width:int = 7, suffix:str = "") :
# 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:T, nm:str, suffix:str = ""):
print(type(t), "dynamic")
t.show(nm)
# show Python id, unique for each instance
def showIdent(t:T, name:str, suffix:str = "") :
print(name, '"{}"'.format(t), id(t), suffix)
# show emphasized note
def showNote(text:str, suffix:str = "", 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:str):
print("--- {} ---".format(text))
Generic Functions:
⇐ imports ⇐ showTypePython's inferred type, and the value of the
generic parameter t and its size.
returns a string with n spaces, used for format-
ting output.
sequence of values into rows
its argument is an enumberable sequence instead
of a scalar. Values are displayed in rows.
2.4.2 Generic Function Demonstration
def demo_generic_functions():
Anal.showNote(" demo generic functions", "\n")
Anal.showOp("Anal.showType(list[int], name)")
l1:list[int] = [1, 2, 3, 2, 1, 0, -1, -2, -1, 0]
Anal.showType(l1, "l1")
print()
Anal.showOp("showTypeEnum(list[int], name)")
Anal.showTypeEnum(l1, "l1", left = 2, width = 7, suffix = "")
print()
--------------------------------------------------
demo generic functions
--------------------------------------------------
--- Anal.showType(list[int], name) ---
l1 <class 'list'> dynamic
value: [1, 2, 3, 2, 1, 0, -1, -2, -1, 0], size: 136
--- showTypeEnum(list[int], name) ---
l1 <class 'list'> dynamic
{
1, 2, 3, 2, 1, 0, -1,
-2, -1, 0
}
size: 136
2.4.3 Static Analysis
C:\github\JimFawcett\Bits\Python\Py_Generic
> mypy AnalysisGen.py
AnalysisGen.py:55: error: "T" has no attribute "show" [attr-defined]
Found 1 error in 1 file (checked 1 source file)
C:\github\JimFawcett\Bits\Python\Py_Generic
2.5 Program Structure
#----------------------------------------------------------
# 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
#----------------------------------------------------------
# Python std types
# list, tuple, range, dict, set,
import copy
import AnalysisGen
import PointsGen
# short-hand identifiers
Anal = AnalysisGen
Points = PointsGen
#-- demonstrate std collection types with generic hints
def demo_std_generics():
# code elided, shown above
#-- user defined Hello type --#
from typing import TypeVar, Generic
T = TypeVar('T', bound=int | float)
class Hello(Generic[T]):
# code elided, shown above
#-- demonstrate user-defined generic types --#
def demo_user_defined_generics():
# code elided, shown above
def demo_generic_functions():
# code elided, shown above
#-- demonstration starts here --#
def execute() :
Anal.showNote(" Demonstrate Python Generics")
Anal.showNote(
" Python introduced generics and type hints\n"\
" to check for type errors using mypy, a static\n"\
" type checking tool."
)
print()
demo_std_generics()
demo_user_defined_generics()
demo_generic_functions()
print("\nThat's all folks!\n")
execute()
C:\github\JimFawcett\Bits\Python\Py_Generic
> python Py_Generic.py
--------------------------------------------------
Demonstrate Python Generics
--------------------------------------------------
--------------------------------------------------
Python introduced generics and type hints
to check for type errors using mypy, a static
type checking tool.
--------------------------------------------------
--------------------------------------------------
demo std generics
--------------------------------------------------
--- list[int] ---
<class 'list'>
[1, 2, 3, 2, 1]
--- list[str] ---
<class 'list'>
['you', 'me', 'them', 'us']
--- tuple(int, float, str) ---
<class 'tuple'>
(42, -0.5, 'forty two')
--- dict[str, int] ---
<class 'dict'>
{'zero': 0, 'one': 1, 'two': 2, 'three': 3}
--------------------------------------------------
demo user-defined generics
--------------------------------------------------
--- Hello(42) ---
<class '__main__.Hello'>
h1: Hello { 42 }
--- Hello() uses default argument ---
h2: Hello { 0 }
--- h2.value = 3.1415927 ---
h2: Hello { 3.1415927 }
--- p1 = Points.Point[float](5) ---
<class 'PointsGen.Point'>
p1
{
0, 1.5, 0, -2.0, 42
}
--------------------------------------------------
demo generic functions
--------------------------------------------------
--- Anal.showType(list[int], name) ---
l1 <class 'list'> dynamic
value: [1, 2, 3, 2, 1, 0, -1, -2, -1, 0], size: 136
--- showTypeEnum(list[int], name) ---
l1 <class 'list'> dynamic
{
1, 2, 3, 2, 1, 0, -1,
-2, -1, 0
}
size: 136
That's all folks!
C:\github\JimFawcett\Bits\Python\Py_Generic
3.0 Build
C:\github\JimFawcett\Bits\Python\Py_Generic
> python Py_Generic.py
4.0 VS Code View
5.0 References
Reference | Description |
---|---|
Python Type Hints | Syntax for type hinting - requires static analysis tool mypy.py to catch type errors. |
Python Abstract Base Classes | Specification for building Python's equivalent of Interfaces |
Python Tutorial - w3schools | Interactive examples |
Python Reference - docs.python.org | Semi-formal syntax reference |
Hello[T]:
⇐ Type Hints ⇐ "generic" class using type hint ⇐ "private"