Click to edit
Python Master title style
Typing
1. Getting started with Python typing
2. Python typing techniques
3. Defining abstract classes/methods
Annex
• Additional techniques
Section 1: Getting Started with Python
Click to edit Master title style
Typing
• Python is dynamically typed
• Providing type hints
• How to verify Python type usage
• Python types available
Python is Dynamically Typed
Click to edit Master title style
• Python is a dynamically-typed language
• A variable can change its type mid-flight
x = 42
print(x, type(x))
x = "Hello world"
print(x, type(x))
Ex01_PythonIsDynamicallyTyped.py
• Pros of dynamic typing:
• Python code is easy to learn/write
• Cons of dynamic typing:
• Python code can be error-prone at run time
Providing Type Hints
Click to edit Master title style
• You can provide type hints for your variables
• When you declare a variable, specify its intended type too
y: int = 42
y = 43 # OK
y = "Hello" # Not OK
Ex02_ProvidingTypeHints.py
• Pros of type hints:
• Enables static code checkers to spot incorrect type usage
• Cons of dynamic typing:
• More syntax to learn/write
• Not enforced at run time (Python is still dynamically typed!)
How to Verify Python Type Usage
Click to edit Master title style
• There are various static code checkers available, to verify
that your code adheres to the specified types
• We'll use pyright, a popular choice
• Install and run pyright as follows:
pip install pyright
pyright <filename.py>
• For example:
Python Types Available
Click to edit Master title style
• You can define type hints for all the simple Python types:
a: int = 42
b: float = 3.14
c: bool = True
d: list[int] = [10, 20, 30]
e: set[str] = {"Swansea", "Wrexham", "Newport", "Swansea"}
f: dict[str, str] = {"UK": "+44", "DK": "+45", "SE": "+46", "NO": "+47"}
g: tuple[str, float, float] = ("Swansea", 51.62, -3.94)
print(a, b, c, d, e, f, g)
Ex03_PythonTypesAvailable.py
Section 2: Python Typing Techniques
Click to edit Master title style
• Defining type hints for functions
• Type techniques
• Defining type hints for methods
Defining Type Hints for Functions
Click to edit Master title style
• You can define types for function arguments/returns…
• Simple argument / return types:
def calc_discriminant(a: float, b: float, c: float) -> float: ...
• Returning something interesting:
def calc_roots(a: float, b: float, c: float) -> tuple[float, float]: ...
• Receiving variadic positional arguments:
def display_squares(*nums: int) -> None: ...
• Receiving variadic keyword arguments:
def display_named_squares(**kwargs: int) -> None: ...
Ex04_Functions.py
Type Techniques (1 of 2)
Click to edit Master title style
• There are some interesting type techniques you can use...
• Defining a union type
def f1(a: str | int | float) -> str: ...
• Defining an optional parameter:
def f2(fav_football_team: Optional[str] = None) -> None: ...
• Allowing any type of parameter:
def f3(arg: Any) -> None: ...
Ex05a_TypeTechniques.py
• Note:
• Optional and Any are located in the typing module
Type Techniques (2 of 2)
Click to edit Master title style
• You can define the signature of lambdas
• Via Callable in the typing module
from typing import Callable
def apply(a: int, b: int, op: Callable[[int,int], int]) -> int :
print("In apply()")
return op(a, b)
res1 = apply(10, 20, lambda x, y: x + y)
print(res1)
res2 = apply(10, 20, lambda x, y: x - y)
print(res2)
res3 = apply(10, 20, lambda x, y: x * y)
print(res3)
Ex05b_TypeTechniques.py
Defining Type Hints for Methods
Click to edit Master title style
• You can define type hints for methods in a class
• You can use Self as an alias for the current class name
from typing import Self
class Person:
def __init__(self: Self, name: str, age: int) -> None:
self.name = name
self.age = age
def isOlderThan(self: Self, other: Self) -> bool:
return self.age > other.age
• You can also use a class name as a type hint
def display_person(p: Person) -> None:
print(f"{p.name} is {p.age} years old")
Ex06_Methods.py
Section 3: Defining Abstract
Click to edit Master
Classes/Methods title style
• Overview of abstract classes/methods
• Defining an abstract class
• Defining concrete subclasses
Overview of Abstract Classes/Methods
Click to edit Master title style
• Abstract classes and abstract methods are important
concepts in OO programming…
• An abstract class…
• Is a class you can't instantiate
• Instead, you instantiate concrete subclasses
• An abstract method…
• Is a method specified (but not implemented) in an abstract
class
• All concrete subclasses must override/implement the method
Defining an Abstract Class
Click to edit Master title style
• Python has a handy module named abc
• Helps you define abstract classes and abstract methods
• To define an abstract class:
• Inherit from abc.ABC
• Define empty methods, decorated with
@abc.abstractmethod
import abc
class shape(abc.ABC):
@abc.abstractmethod
def area(self: Self) -> float: ...
@abc.abstractmethod
def perimeter(self: Self) -> float: ...
Ex07_AbstractClass.py
Defining Concrete Classes
Click to edit Master title style
• To define a concrete subclass:
• Inherit from a superclass
• Provide an implementation for all abstract methods
👍
class quadrilateral(shape):
def area(self: Self) -> float:
return self.width * self.height
def perimeter(self: Self) -> float:
return 2 * (self.width + self.height)
class circle(shape):
def area(self: Self) -> float:
return pi * self.radius ** 2
👎 def circumference(self: Self) -> float:
return 2 * pi * self.radius
# Oops!
Ex08_ConcreteSubclasses.py
Click to edit Master title style
Summary
• Getting started with Python typing
• Python typing techniques
• Defining abstract classes/methods
Annex: Additional Techniques
Click to edit Master title style
• Defining type hints for class objects
• Creating new types
• Function overloading
Defining Types Hints for Class Objects
Click to edit Master title style
• When you define a class in Python…
• Python creates a class object describing it (class name,
methods, etc.)
• You can pass a class object into functions
• And
from you
typing can Self,
import use Type[classname]
Type to provide a type hint
Ex09_ClassObjects.py
class Person: ...
class Student(Person): ...
class Employee(Person): ...
def create_kind_of_person(cls: Type[Person], name: str, age: int) -> Person:
if age > 125:
raise ValueError("How old???")
return cls(name, age)
p1 = create_kind_of_person(Student, "William", 20)
p2 = create_kind_of_person(Employee, "Kate", 30)
Creating New Types (1 of 2)
Click to edit Master title style
• Consider the following simple class:
class Employee:
def __init__(self: Self, name: str, id: int, salary: int) -> None:
self.name = name
self.id = id
self.salary = salary
def __str__(self: Self) -> str:
return f"[{self.id} {self.name}, earns {self.salary} ]"
emp1 = Employee("Mary", 1, 10000)
emp2 = Employee("Mungo", 2, 20000) Ex08a_NewTypes_Bad.py
emp3 = Employee("Midge", 30000, 3)
Ex10a_NewTypes_Lax.py
• The Employee constructor receives two int arguments
• We could accidentally pass in ints in the wrong order (see
Midge), and this would not be detected as a type error 😢
Creating New Types (2 of 2)
Click to edit Master title style
• You can use Python typing to invent distinct types:
from typing import Self, NewType
Money = NewType(Money', int) # Money is a new type, inherits from int.
PK = NewType('PK', int) # PK is a new type, inherits from int.
class Employee:
def __init__(self: Self, name: str, id: PK, salary: Money) -> None:
self.name = name
self.id = id
self.salary = salary
def __str__(self: Self) -> str:
return f"[{self.id} {self.name}, earns {self.salary} ]"
emp1 = Employee("Mary", PK(1), Money(10000)) # Must specify exact arg types
emp2 = Employee("Mungo", PK(2), Money(20000))
emp3 = Employee("Midge", PK(3), Money(30000))
Ex10b_NewTypes_Strict.py
Function Overloading (1 of 3)
Click to edit Master title style
• Python typing lets you define overloaded functions
• Multiple functions with the same name, but different args
• (The number or type of args must be different)
• Here's a simple example of using overloaded functions:
res1 = seconds_to_midnight(86_395) # Pass in time of day as (seconds)
res2 = seconds_to_midnight(23, 59, 57) # Pass in time of day as (h, m, s)
• Let's see how to do this using Python typing…
Function Overloading (2 of 3)
Click to edit Master title style
• First, specify the function signatures you want to support
• Decorate these functions with @overload
from typing import overload
@overload
def seconds_to_midnight(p1: int) -> int: ...
@overload
def seconds_to_midnight(p1: int, p2: int, p3: int) -> int: ...
• Then, define a single function that satisfies all signatures:
def seconds_to_midnight(
p1: int,
p2: int | None = None,
p3: int | None = None) -> int:
# Implementation to satisfy both overloads. See next slide…
Ex11_FunctionOverloading.py
Function Overloading (3 of 3)
Click to edit Master title style
• The function implementation must figure out which
overload the client called:
def seconds_to_midnight(
p1: int,
p2: int | None = None,
p3: int | None = None) -> int:
NUM_SECS_IN_DAY = 60*60*24
if (p2 is None or p3 is None): seconds_to_midnight(86_395)
return NUM_SECS_IN_DAY - p1
else: seconds_to_midnight(23, 59, 57)
return NUM_SECS_IN_DAY - (p1*60*60 + p2*60 + p3)
Ex11_FunctionOverloading.py