Python types at PyWeb 2025.05
Python types at PyWeb 2025.05
The Answer
Why would anyone write such obviously incorrect code?
Add
What is the result of this code in Perl / Python / JavaScript / Rust?
fn main() { println!("ok"); println!("{}", "19" + 23); }
print("ok")
print("19" + 23)
{% embed include file="src/examples/python-types-at-pyweb-2025-05/add.js)
use feature 'say';
say "ok";
say "19" + 23;
rustc add.rs
-
The Rust code will have a compilation error.
-
The Python code will have a runtime exception.
-
The Perl code will print the correct answer: 42.
However, in these example the problem is in-front of us. We can easily see it.
Add in function
fn add(x: u32, y: u32) -> u32 { x + y } fn main() { println!("ok"); let first = "19"; let second = 23; println!("{}", add(first, second)); }
def add(x, y):
return x + y
print("ok")
first = "19"
second = 23
print(add(first, second))
def add(x, y):
return x + y
print("ok")
first = "19"
second = 23
print(add(first, second))
use feature 'say';
sub add {
my ($x, $y) = @_;
$x + $y
}
say "ok";
my $first = "19";
my $second = 23;
say add($first, $second);
Much less obvious if we are calling a function with parameters that are not the correct type.
The result is the same as in the previous example.
Shift-left (testing, programming)
- Rust is the shift-left language noticing bugs earlier in the development process.
- How can we get Python to also complain earlier?
Function with type annotation
- We can add type-annotations to the function parameters and even to the return value.
def add(x: int, y: int) -> int:
return x + y
print("ok")
first = "19"
second = 23
print(add(first, second))
- However, Python will disregard these type-annotations and we still get a run-time exception.
Use mypy
- Use some external tool mypy to check the types in your code. (Are there any other tools looking at the type-annotations?)
$ mypy function_with_type.py
function_with_type.py:7: error: Argument 1 to "add" has incompatible type "str"; expected "int" [arg-type]
Found 1 error in 1 file (checked 1 source file)
$ mypy answer.py
answer.py:2:15: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment]
Found 1 error in 1 file (checked 1 source file)
How add type annotation?
- We already saw the two examples:
- Add type annotation to variables.
- Add type annotation to the function signature.
def add(x: int, y: int) -> int:
return x + y
def multiply(x, y):
res: int = x * y
return res
a: int = 23
z: int = add(a, 19)
result = multiply(a, 19)
Infer (deduct) the type
- We can define every variable, but in many cases Python will infer them from other definitions.
def add(x: int, y: int) -> int:
return x + y
def display(text: str):
print(text)
first = 19
second = 23
result = add(first, second)
display(result)
Type in unannotated function
- --check-untyped-def
- --strict
def do_something():
answer :str = 42
print(answer)
do_something()
$ mypy unannotated_function.py
$ mypy --check-untyped-def unannotated_function.py
$ mypy --strict unannotated_function.py
Type in annotated function
def do_something() -> None:
answer: str = 42
print(answer)
do_something()
Built-in types
- str
- int
- float
- bool
- list
- tuple
- dict
- set
anwser: str = "42"
width: int = 17
pi: float = 3.14
good: bool = True
animals: list = ["camel", "snake", "crab"]
language: tuple = ("Perl", "Python", "Rust")
mapping: dict = {
"camel": "Perl",
"snake": "Python",
"crab": "Rust",
}
things: set = {"chair", "table"}
Complex types
- For Python 3.9+
things: list = ["snake", 42]
#things: list[str] = ["snake", 42]
numbers: list[int] = [23, 19, 42]
mapping: dict[str, int] = {
"Perl": 4,
"Python": 6,
"Rust": 4,
"PHP": 3
}
info: tuple[str, int, bool] = ("Python", 3, True)
Either this or that type (Union)
-
Union
-
Python 3.10+
def my_exit(code: str | int):
print(type(code).__name__)
my_exit(3)
my_exit("problem")
my_exit(3.14)
$ python union.py
int
str
float
$ mypy pipe.py
pipe.py:6:9: error: Argument 1 to "my_exit" has incompatible type "float"; expected "str | int" [arg-type]
Found 1 error in 1 file (checked 1 source file)
Either this or that type for Python before 3.10
- Union
from typing import Union
def my_exit(code: Union[str, int]):
print(type(code).__name__)
my_exit(3)
my_exit("problem")
my_exit(3.14)
$ python union.py
int
str
float
$ mypy union.py
union.py:8:9: error: Argument 1 to "my_exit" has incompatible type "float"; expected "str | int" [arg-type]
Found 1 error in 1 file (checked 1 source file)
Optional type (variable can also be None)
- Optional
- split
from typing import Union, Optional
text = "a,b,c"
print(text.split())
print(text.split(None))
print(text.split(","))
#def split(text, sep: str | None=None):
#def split(text, sep: Union[str,None]=None):
def split(text, sep: Optional[str]=None):
return text.split(sep)
print(split(text))
print(split(text, None))
print(split(text, ","))
print(split(text, 42))
Define type alias
LabelType = str
OtherType = str
def f(x: OtherType):
print(x)
txt = "hello"
label: LabelType = txt
print(label)
f(txt)
f(label)
Define complex type alias
ReturnType = dict[str, int]
result: ReturnType = {
"blue": 1,
"green": 0,
}
#result: ReturnType = {
# "blue": "ok",
# "green": 0,
#}
# ------------------------------------------
UnionReturnType = dict[str, str | int]
ret: UnionReturnType = {
"blue": "ok",
"green": 0,
}
Define type for enum and complex dictionary
from typing import Literal
LevelType = Literal["debug", "info", "warning", "error"]
size: LevelType = "debug"
#call it label size = "eror"
# ------------------------------------------
from datetime import datetime
from typing import TypedDict
HistoryType = TypedDict('HistoryType', {
"date" : datetime,
"level": LevelType,
"text": str,
})
event: HistoryType = {
"date": datetime.now(),
"level": "debug",
"text": "Demo typing",
}
mypy generics - plain
- We can use generics to say that we accept any type and we can use it to indicate that some other parameter or the return type will be the same type.
def do_something[T](x: T) -> T:
return x
y = do_something(23)
print(y + 19)
#print(y + "19")
mypy generics - cannot be any type
def add[T](x: T, y: T) -> T:
return x + y
add(2, 3)
mypy generics_cannot_be_any_type.py
mypy generics - limit the types by listing
from typing import TypeVar
T = TypeVar('T', int, str)
def add(x: T, y: T) -> T:
return x + y
add(2, 3)
mypy generics - limit by functionality
- A more generic way to define generics is by creating types that need to have certain functionality
from typing import TypeVar
from typing import Protocol, Self
from abc import abstractmethod
class SupportsAdd(Protocol):
@abstractmethod
def __add__(self, other: Self) -> Self:
pass
T = TypeVar('T', bound=SupportsAdd)
# -------------------------------
def add(x: T, y: T) -> T:
return x + y
z = add(2, 3)
print(z)
# -------------------------------
def adder[T: SupportsAdd](x: T, y: T) -> T:
return x + y
z = adder(3, 4)
print(z)
from typing import TypeVar
from typing import Protocol, Self
from abc import abstractmethod
class SupportsAdd(Protocol):
@abstractmethod
def __add__(self, other: Self) -> Self:
pass
T = TypeVar('T', bound=SupportsAdd)
# -------------------------------
def add(x: T, y: T) -> T:
return x + y
z = add(2, 3)
print(z)
q = add("a", "b")
class Point():
#pass
def __add__(self, other):
pass
p1 = Point()
p2 = Point()
p = add(p1, p2)
# -------------------------------
#def adder[T: SupportsAdd](x: T, y: T) -> T:
# return x + y
#
#z = adder(3, 4)
#print(z)
Two variables of the same and different types
def no_problem[T](x: T, y: T) -> None:
print(type(x).__name__, type(y).__name__)
no_problem(2, 2)
no_problem("hi", "hi")
no_problem("hi", 2)
# -----------------------------
from typing import TypeVar
T = TypeVar('T', int, str)
def the_same(x: T, y: T) -> None:
print(type(x).__name__, type(y).__name__)
the_same(2, 2)
the_same("hi", "hi")
the_same("hi", 2)
# -----------------------------
Q = TypeVar('Q', int, str)
def different(x: T, y: Q) -> None:
print(type(x).__name__, type(y).__name__)
different(2, 2)
different("hi", "hi")
different("hi", 2)
$ mypy generics_two_types.py
generics_two_types.py:10:1: error: Value of type variable "T" of "the_same" cannot be "object" [type-var]
Found 1 error in 1 file (checked 1 source file)
mypy suggestions
- Set
MYPY_CACHE_DIR
environment variable - Create
mypy.ini
{% embed include file="src/examples/python-types-at-pyweb-2025-05/mypy.ini)
The end
- Don't forget to run mypy!
Bloopers
Define the type of variables
def add(x: int, y: int):
return x + y
first = 19
second = 23
result: str = first + second
#result: str = add(first, second)
$ mypy variable_and_function.py
variable_and_function.py:7:15: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment]
Found 1 error in 1 file (checked 1 source file)
$ mypy --strict variable_and_function.py
variable_and_function.py:2:1: error: Function is missing a return type annotation [no-untyped-def]
Found 1 error in 1 file (checked 1 source file)
Define the return type
def add(x: int, y: int) -> int:
return x + y
first = 19
second = 23
result: str = add(first, second)
$ mypy variable_and_function_with_return_type.py
variable_and_function_with_return_type.py:8:15: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment]
Found 1 error in 1 file (checked 1 source file)
Complex types for Python 3.8 and before
- List
- Dict
- Set
- Tuple
from typing import List, Dict, Tuple
things: List = ["snake", 42]
#things: List[str] = ["snake", 42]
numbers: List[int] = [23, 19, 42]
mapping: Dict[str, int] = {
"Perl": 4,
"Python": 6,
"Rust": 4,
"PHP": 3
}
info: Tuple[str, int, bool] = ("Python", 3, True)