by Bernat Gabor /
@gjbernat /
Bloomberg
https://gaborbernat.github.io/type-hint-recr-2022
Copyright of the above image owned by Bernat Gabor
from typing import TypedDict
def send_request(request_data : Any,
headers: dict[str, str] | None,
user_id: UserId | None = None,
as_json: bool = True):
"""this is a great sender"""
def send_request(request_data : Any,
headers: dict[str, str] | None,
user_id: UserId | None = None,
as_json: bool = True):
"""this is a great sender"""
def send_request(request_data : Any,
headers: dict[str, str] | None,
user_id: UserId | None = None,
as_json: bool = True):
"""this is a great sender"""
def send_request(request_data : Any,
headers: dict[str, str] | None,
user_id: UserId | None = None,
as_json: bool = True):
"""this is a great sender"""
def send_request(request_data : Any,
headers: dict[str, str] | None,
user_id: UserId | None = None,
as_json: bool = True):
"""this is a great sender"""
Copyright of the above image owned by Bernat Gabor
Copyright of the above image owned by Bernat Gabor
Copyright of the above image owned by Bernat Gabor
from datetime import datetime
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
signup_ts: datetime = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "broken",
"friends": [1, 2, "not a number"],
}
try:
user = User(**external_data)
except ValidationError as e:
print(e.json())
from datetime import datetime
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
signup_ts: datetime = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "broken",
"friends": [1, 2, "not a number"],
}
try:
user = User(**external_data)
except ValidationError as e:
print(e.json())
from datetime import datetime
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
signup_ts: datetime = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "broken",
"friends": [1, 2, "not a number"],
}
try:
user = User(**external_data)
except ValidationError as e:
print(e.json())
from datetime import datetime
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
signup_ts: datetime = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "broken",
"friends": [1, 2, "not a number"],
}
try:
user = User(**external_data)
except ValidationError as e:
print(e.json())
Facebook - pyre
# Avoid passing user input to system calls (safety check)
def call(cmd: list[str]) -> None:
subprocess.check_call(cmd)
user_input: list[str] = input().split(" ")
call(user_input)
Facebook - pyre
# Avoid passing user input to system calls (safety check)
def call(cmd: list[str]) -> None:
subprocess.check_call(cmd)
def ensure_safe(args: list[str]) -> list[str]:
assert args == [sys.executable, "--version"]
return args
user_input: list[str] = input().split(" ")
call(user_input) # do not allow this
Facebook - pyre
# Avoid passing user input to system calls (safety check)
def call(cmd: list[str]) -> None:
subprocess.check_call(cmd)
def ensure_safe(args: list[str]) -> list[str]:
assert args == [sys.executable, "--version"]
return args
user_input: list[str] = input().split(" ")
call(ensure_safe(user_input)) # this is now safe
Facebook - pyre
# Avoid passing user input to system calls (safety check)
from typing import NewType, cast
SafeStr = NewType("SafeStr", str)
def call(cmd: list[SafeStr]) -> None:
subprocess.check_call(cmd)
def ensure_safe(args: list[str]) -> list[SafeStr]:
assert args == [sys.executable, "--version"]
return [cast(SafeStr, s) for s in args]
user_input: list[str] = input().split(" ")
call(ensure_safe(user_input)) # this is now safe
essentially treated as comment during script evaluation
essentially treated as comment during script evaluation
Improve the developer experience, not performance
Improve the developer experience, not performance
Improve the developer experience, and performance
# tests/test_magic_field.py
f = MagicField(name=1, MagicType.DEFAULT)
f.names()
bernat@uvm ~/python-magic (master●)$ mypy --ignore-missing-imports tests/test_magic_field.py
tests/test_magic_field.py:21: error: Argument 1 to "MagicField" has incompatible type "int";
expected "Union[str, bytes]"
tests/test_magic_field.py:22: error: "MagicField" has no attribute "names"; maybe "name" or "_name"?
# tests/test_magic_field.py
f = MagicField(name=1, MagicType.DEFAULT)
f.names()
bernat@uvm ~/python-magic (master●)$ mypy --ignore-missing-imports tests/test_magic_field.py
tests/test_magic_field.py:21: error: Argument 1 to "MagicField" has incompatible type "int";
expected "Union[str, bytes]"
tests/test_magic_field.py:22: error: "MagicField" has no attribute "names"; maybe "name" or "_name"?
# tests/test_magic_field.py
f = MagicField(1, MagicType.DEFAULT)
f.names()
bernat@uvm ~/python-magic (master●)$ mypy --ignore-missing-imports tests/test_magic_field.py
tests/test_magic_field.py:21: error: Argument 1 to "MagicField" has incompatible type "int";
expected "Union[str, bytes]"
tests/test_magic_field.py:22: error: "MagicField" has no attribute "names"; maybe "name" or "_name"?
# tests/test_magic_field.py
f = MagicField(1, MagicType.DEFAULT)
f.names()
bernat@uvm ~/python-magic (master●)$ mypy --ignore-missing-imports tests/test_magic_field.py
tests/test_magic_field.py:21: error: Argument 1 to "MagicField" has incompatible type "int";
expected "Union[str, bytes]"
tests/test_magic_field.py:22: error: "MagicField" has no attribute "names"; maybe "name" or "_name"?
def greeting(name: str) -> str:
value : str = "Hello"
return f"{value} {name}""
def greeting(name: str) -> str:
value : str = "Hello"
return f"{value} {name}""
def greeting(name: str) -> str:
value : str = "Hello"
return f"{value} {name}""
class A:
def __init__() -> None:
self.elements : list[int] = []
def add(element: int) -> None:
self.elements.append(element)
from __future__ import annotations
from typing import List
class A:
def __init__():
# type: () -> None
self.elements = [] # type: List[int]
def add(element):
# type: (List[int]) -> None
self.elements.append(element)
class A:
def __init__() -> None:
self.elements = []
def add(element):
self.elements.append(element)
# a.pyi alongside a.py
class A:
elements: list[int] = ...
def __init__() -> None: ...
def add(element: int) -> None: ...
class A:
def __init__() -> None:
self.elements = []
def add(element):
self.elements.append(element)
# a.pyi alongside a.py - ellipse for body stub
class A:
elements: list[int] = ...
def __init__() -> None: ...
class A:
def __init__():
self.elements = []
def add(element):
"""
:param List[int] element: the element to add
:rtype: None
"""
self.elements.append(element)
t : tuple[int, float] = 0, 1.2
d : dict[str, int] = {"a": 1, "b": 2}
d : MutableMapping[str, int] = {"a": 1, "b": 2}
l : list[int] = [1, 2, 3]
i : Iterable[str] = ["1", "2", "3"]
Vector = list[float] # implicit
Vector: TypeAlias[list[float]] # explicit
UserId = NewType("UserId", int)
some_id = UserId(524313)
class Employee(NamedTuple):
name: str
id: int
Union[None, int, str]
None | int | str
float | None # Optional[float]
# Callable[[Arg1Type, Arg2Type], ReturnType]
def feeder(get_next_item: Callable[[], str]) -> None:
T = TypeVar("T")
class Magic(Generic[T]):
def __init__(self, value: T) -> None:
self.value : T = value
def square_values(vars: Iterable[Magic[int]]) -> None:
v.value = v.value * v.value
def foo(item: Any) -> int:
item.bar()
nominal (main) vs structural typing (support)
KEY = TypeVar("KEY", contravariant=true)
class MagicGetter(Protocol[KEY], Sized):
def __getitem__(self, item: KEY) -> int: ...
def func_int(param: MagicGetter[int]) -> int:
return param["a"] * 2
def func_str(param: MagicGetter[str]) -> str:
return f"{param['a']}"
nominal (main) vs structural typing (support)
KEY = TypeVar("KEY", contravariant=true)
class MagicGetter(Protocol[KEY], Sized):
def __getitem__(self, item: KEY) -> int: ...
def func_int(param: MagicGetter[int]) -> int:
return param["a"] * 2
def func_str(param: MagicGetter[str]) -> str:
return f"{param['a']}"
def magic(i: str | int) -> str | int:
return i * 2
def other_func() -> int:
result = magic(2)
assert isinstance(result, int)
return result
def magic(i: str | int) -> Any:
return i * 2
def other_func() -> int:
result = magic(2)
return result
from typing import overload
@overload
def magic(i: int) -> int:
...
@overload
def magic(i: str) -> str:
...
def magic(i: str | int) -> str | int:
return i * 2
def other_func() -> int:
result = magic(2)
return result
from typing import overload
@overload
def magic(i: int) -> int: # pylint: disable=function-redefined
...
@overload
def magic(i: str) -> str: # pylint: disable=function-redefined
...
def magic(i: str | int) -> str | int:
return i * 2
def other_func() -> int:
result = magic(2)
return result
from abc import ABCMeta, abstractmethod
class A(metaclass=ABCMeta):
@abstractmethod
def func(self, key: str | int) -> str:
raise NotImplementedError
class B(A):
def func(self, key: int) -> str:
return str(key)
class C(A):
def func(self, key: str) -> str:
return key
test.py:12: error: Argument 1 of "func" incompatible with supertype "A"
test.py:17: error: Argument 1 of "func" incompatible with supertype "A"
specialization can handle more
from abc import ABCMeta, abstractmethod
class A(metaclass=ABCMeta):
@abstractmethod
def func(self, key: str | int) -> str:
raise NotImplementedError
class B(A):
def func(self, key: str | int | bool) -> str:
return str(key)
class C(A):
def func(self, key: str | int | list[int]) -> str:
return key
class A:
@classmethod
def magic(cls, a: int) -> "A":
return cls()
class B(A):
@classmethod
def magic(cls, a: int, b: bool) -> "B":
return cls()
elements: list[type[A]] = [A, B]
print([e.magic(1) for e in elements])
TypeError: B.magic() missing 1 required positional argument: 'b'
test.py:9: error: Signature of "magic" incompatible with supertype "A"
class A:
@classmethod
def magic(cls, a: int) -> "A":
return cls()
class B(A):
@classmethod
def magic(cls, a: int, b: bool = False) -> "B":
return cls()
elements: list[type[A]] = [A, B]
print([e.magic(1) for e in elements])
class A:
def __init__(self, a: int) -> None:
pass
class B(A):
def __init__(self, a: int, b: bool) -> None:
super().__init__(a)
elements: list[type[A]]= [A, B]
print([e(1) for e in elements])
TypeError: B.__init__() missing 1 required positional argument: 'b'
Copyright of the above image owned by Bernat Gabor
from typing import cast
a = [4]
reveal_type(a) # -> error: Revealed type is 'builtins.list[builtins.int*]'
b = cast(list[str], a) # passes fine (no runtime check)
reveal_type(c) # -> error: Revealed type is 'builtins.list[builtins.str]'
reveal_locals()
# note: Revealed local types are:
# note: a: builtins.list[builtins.int*]
# note: b: builtins.list[builtins.str]
x = confusing_function() # type: ignore # see mypy/issues/1167
@overload
def open(fn: str, mode: Literal["r", "w"]) -> IO[str]: pass
@overload
def open(fn: str, mode: Literal["rb", "wb"]) -> IO[bytes]: pass
def open(fn: str, mode: str):
...
with open("/etc/passwd", "r") as ft:
lines: str = ft.read()
with open("/bin/sh", "rb") as fb:
data: bytes = fb.read()
@overload
def open(fn: str, mode: Literal["r", "w"]) -> IO[str]: pass
@overload
def open(fn: str, mode: Literal["rb", "wb"]) -> IO[bytes]: pass
def open(fn: str, mode: str):
...
with open("/etc/passwd", "r") as ft:
lines: str = ft.read()
with open("/bin/sh", "rb") as fb:
data: bytes = fb.read()
@overload
def open(fn: str, mode: Literal['r', 'w']) -> IO[str]: pass
@overload
def open(fn: str, mode: Literal['rb', 'wb']) -> IO[bytes]: pass
def open(fn: str, mode: str):
...
with open("/etc/passwd", "r") as ft:
lines: str = ft.read()
with open("/bin/sh", "rb") as fb:
data: bytes = fb.read()
from typing import TypedDict
class Movie(TypedDict, total=True):
name: str
year: int
movie: Movie = {
"name": "Blade Runner",
"year": 1982,
}
from typing import TypedDict
class Movie(TypedDict, total=True):
name: str
year: int
movie: Movie = {
"name": "Blade Runner",
"year": 1982,
}
from typing import TypedDict
class Movie(TypedDict, total=True):
name: str
year: int
movie: Movie = {
"name": "Blade Runner",
"year": 1982,
}
RATE: Final[int] = 3000
RATE = 2500 # ERROR -- cannot set Final variable
# Final class variable
class User:
DEFAULT_ID: Final[int] = 0
class BusinessUser(User):
DEFAULT_ID = 1 # ERROR -- cannot set inherited Final class variable
RATE: Final[int] = 3000
RATE = 2500 # ERROR -- cannot set Final variable
# Final class variable
class User:
DEFAULT_ID: Final[int] = 0
class BusinessUser(User):
DEFAULT_ID = 1 # ERROR -- cannot set inherited Final class variable
RATE: Final[int] = 3000
RATE = 2500 # ERROR -- cannot set Final variable
# Final class variable
class User:
DEFAULT_ID: Final[int] = 0
class BusinessUser(User):
DEFAULT_ID = 1 # ERROR -- cannot set inherited Final class variable
# python 3.5 or later
from typing import Dict, List
a: List[int] = [1, 2]
b : Dict[int, str] = {}
# python 3.9 or later
a: list[int] = [1, 2]
b : dict[int, str] = {}
# python 3.7 or later
from __future__ import annotations
a: list[int] = [1, 2]
b : dict[int, str] = {}
# python3.5 or later
from typing import Optional, Union
a : Union[str, int] = "1"
b : Optional[int] = 2
# python3.10 or later
a : str | int = "1"
b : int | None = 2
# python3.7 or later
from __future__ import annotations
a : str | int = "1"
b : int | None = 2
class A:
def __init__():
self.elements = []
def add(element):
"""
:param List[int] element: the element to add
:rtype: None
"""
self.elements.append(element)
pip install sphinx-autodoc-types>=1.17
# conf.py
extensions = ["sphinx_autodoc_typehints"]
Copyright of the above image owned by Bernat Gabor
Copyright of the above image owned by Bernat Gabor
Bloomberg
© 2022 Bloomberg Finance L.P.
All rights reserved.