about
Bits Generic Python
05/29/2023
Bits Repo Code Bits Repo Docs

Bits: Python Generics

code, output, and build for Python on Windows, macOS, and Linux

This page is a prototype used to decide which language features to emphasize. It will be replaced with a final version soon.

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  
All of the languages covered in this demonstration support generics with the exception of JavaScript. Three of the languages: C++, Rust, and C# provide generics. Python provides a typing module that provides type hints, checked with a static analysis tool. Each generic function or class is a pattern for defining functions and classes of a specific type. Thus a generic is a pattern for making patterns. Python and JavaScript, are dynamically typed and already support defining functions and classes for multiple types, e.g., no need for generics. This demonstration illustrates use of generic functions and classes, which for C++, Rust, and C#, are processed by the compiler.
The examples below show how to use library and user defined types with emphasis on illustrating syntax and basic operations.

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

d1  dynamic
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

The code for this demo is available in github.com/JimFawcett/Bits. If you click on the Code dropdown you can clone the repository of all code for these demos to your local drive. Then, it is easy to bring up any example, in any of the languages, in VS Code. Here, we do that for Python\Python_Objects. Figure 1. VS Code IDE - Python Generics

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
  bottom refs VS Code bld out anal pts src codeSnaps top
  Next Prev Pages Sections About Keys