Прохождение Python Type Challenges
Для тренировки и проверки навыков типизации в Python есть интересный челлендж - Python Type Challenges. На сайте можно непосредственно выполнять задания и тут же их проверять.
Всего 4 уровня сложности:
Базовый уровень
- Произвольный тип
- Словарь
- Запрет переопределения
- Именованные аргументы
- Список
- Опциональный аргумент
- Обязательный аргумент
- Возвращаемый тип
- Кортеж
- Пользовательский тип
- Объединение типов
- Переменная
Произвольный тип
Измените функцию foo таким образом, чтобы она принимала
аргумент произвольного типа.
def foo():
...
Решение
from typing import Any
def foo(x: Any):
...
Тестовые примеры:
foo(1)
foo("10")
foo(1, 2) # Ошибочный вариант
Словарь
Измените функцию foo таким образом, чтобы она принимала аргумент типа
dict, ключи и значения которого являются строками.
def foo():
pass
Решение
def foo(x: dict[str, str]):
pass
Тестовые примеры:
foo({"foo": "bar"})
foo({"foo": 1}) # Ошибочный вариант
Запрет переопределения
Аннотируйте переменную my_list таким образом, чтобы ее нельзя было переназначить.
my_list = []
Решение
from typing import Final
my_list: Final[list[int]] = []
Тестовые примеры:
my_list.append(1)
my_list = [] # Ошибочный вариант
my_list = "something else" # Ошибочный вариант
Именованные аргументы
Измените аннотацию функции foo таким образом, чтобы она принимала
произвольное количество именованных целочисленных или строковых аргументов.
def foo(**kwargs):
...
Решение
def foo(**kwargs: int | str):
...
Тестовые примеры:
foo(a=1, b="2")
foo(a=[1]) # Ошибочный вариант
Список
Измените аннотацию функции foo таким образом, чтобы она принимала
список строк.
def foo(x):
pass
Решение
def foo(x: list[str]):
pass
Тестовые примеры:
foo(["foo", "bar"])
foo(["foo", 1]) # Ошибочный вариант
Опциональный аргумент
Измените аннотацию функции foo таким образом, чтобы она принимала
опциональный целочисленный аргумент.
def foo(x):
pass
Решение
def foo(x: int | None = None):
pass
Тестовые примеры:
foo(10)
foo(None)
foo()
foo("10") # Ошибочный вариант
Обязательный аргумент
Измените аннотацию функции foo таким образом, чтобы она принимала
обязательный целочисленный аргумент.
def foo(x):
pass
Решение
def foo(x: int):
pass
Тестовые примеры:
foo(10)
foo("10") # Ошибочный вариант
Возвращаемый тип
Измените аннотацию функции foo, чтобы обозначить возвращаемое
целочисленное значение.
def foo():
return 1
Решение
def foo() -> int:
return 1
Тестовые примеры:
from typing import assert_type
assert_type(foo(), int)
assert_type(foo(), str)
Кортеж
Измените аннотацию функции foo таким образом, чтобы она принимала кортеж
из двух элементов - строки и целого числа.
def foo(x):
pass
Решение
def foo(x: tuple[str, int]):
pass
Тестовые примеры:
foo(("foo", 1))
foo((1, 2)) # Ошибочный вариант
foo(("foo", "bar")) # Ошибочный вариант
foo((1, "foo")) # Ошибочный вариант
Пользовательский тип
Объявите тип Vector, который представляет собой список чисел с плавающей
точкой.
Решение
type Vector = list[int | float]
Тестовые примеры:
def foo(v: Vector):
...
foo([1.1, 2])
foo(1) # Ошибочный вариант
foo(["1"]) # Ошибочный вариант
Объединение типов
Измените аннотацию функции foo таким образом, чтобы она принимала
либо строку либо целое число.
def foo(x):
pass
Решение
def foo(x: str | int):
pass
Тестовые примеры:
foo("foo")
foo(1)
foo([]) # Ошибочный вариант
Переменная
Аннотируйте тип переменной a таким образом, чтобы она могла принимать
только целочисленные значения.
from typing import Any
a: Any
Решение
a: int
Тестовые примеры:
a = 2
a = "1" # Ошибочный вариант
Средний уровень
- Awaitable объект
- Вызываемый объект
- Атрибут класса
- Простой декоратор
- Пустой кортеж
- Универсальная функция 1
- Универсальная функция 2
- Универсальная функция 3
- Атрибут экземпляра
- Литерал
- Строковый литерал
self- Типизированный словарь 1
- Типизированный словарь 2
- Типизированный словарь 3
- Распаковка
Awaitable объект
Измените функцию run_async таким образом, чтобы она принимала awaitable
объект, возвращающий целое число.
def run_async():
...
Решение (вариант 1)
from collections.abc import Awaitable
def run_async(func: Awaitable[int]):
...
Решение (вариант 2)
!!! tip В качестве замены `Awaitable` можно указать любых наследников, например класс `Coroutine`from collections.abc import Coroutine
def run_async(func: Coroutine[None, None, int]):
...
Тестовые примеры:
from asyncio import Queue
queue: Queue[int] = Queue()
queue2: Queue[str] = Queue()
async def async_function() -> int:
return await queue.get()
async def async_function2() -> int:
return await queue2.get()
run_async(async_function())
run_async(1) # Ошибочный вариант
run_async(async_function2()) # Ошибочный вариант
Вызываемый объект
Объявите вызываемый тип, который должен принимать обязательный аргумент
строкового типа и возвращать None (имя аргумента может быть произвольным).
SingleStringInput = ...
Решение
from collections.abc import Callable
SingleStringInput = Callable[[str], None]
Тестовые примеры:
def accept_single_string_input(func: SingleStringInput) -> None:
...
def string_name(name: str) -> None:
...
def string_value(value: str) -> None:
...
def int_value(value: int) -> None:
...
def new_name(name: str) -> str:
return name
accept_single_string_input(string_name)
accept_single_string_input(string_value)
accept_single_string_input(int_value) # Ошибочный вариант
accept_single_string_input(new_name) # Ошибочный вариант
Атрибут класса
Дополните класс Foo атрибутом класса bar целочисленного типа.
class Foo:
...
Решение
from typing import ClassVar
class Foo:
bar: ClassVar[int]
Тестовые примеры:
Foo.bar = 1
Foo.bar = "1" # Ошибочный вариант
Foo().bar = 1 # Ошибочный вариант
Простой декоратор
Измените аннотацию декоратора, который обертывает функцию и возвращает функцию с той же сигнатурой.
from typing import Callable, TypeVar
def decorator(func):
return func
Решение (вариант 1, для версий >= 3.12)
from typing import Callable
def decorator[**P, R](func: Callable[P, R]) -> Callable[P, R]:
return func
Подробнее см. в документации.
Решение (вариант 2)
from typing import Callable
def decorator[T: Callable](func: T) -> T:
return func
Тестовые примеры:
@decorator
def foo(a: int, *, b: str) -> None:
...
@decorator
def bar(c: int, d: str) -> None:
...
foo(1, b="2")
bar(c=1, d="2")
foo(1, "2") # Ошибочный вариант
foo(a=1, e="2") # Ошибочный вариант
decorator(1) # Ошибочный вариант
Пустой кортеж
Измените аннотацию функции foo таким образом, чтобы она принимала пустой кортеж.
def foo(x):
pass
Решение
def foo(x: tuple[()]):
pass
Тестовые примеры:
foo(())
foo((1, )) # Ошибочный вариант
Универсальная функция 1
Измените аннотацию функции add таким образом, чтобы она принимала два
аргумента одинакового типа и возвращала значение того же типа.
def add(a, b):
...
Решение
def add[T](a: T, b: T) -> T:
...
Тестовые примеры:
from typing import List, assert_type
assert_type(add(1, 2), int)
assert_type(add("1", "2"), str)
assert_type(add(["1"], ["2"]), List[str])
assert_type(add(1, "2"), int) # Ошибочный вариант
Универсальная функция 2
Измените аннотацию функции add таким образом, чтобы она принимала два
аргумента одинакового типа и возвращала значение того же типа. Типом может
быть либо строка либо целое число.
def add(a, b):
...
Решение
def add[T: (int, str)](a: T, b: T) -> T:
...
Тестовые примеры:
from typing import List, assert_type
assert_type(add(1, 2), int)
assert_type(add("1", "2"), str)
add(["1"], ["2"]) # Ошибочный вариант
add("1", 2) # Ошибочный вариант
Универсальная функция 3
Измените аннотацию функции add таким образом, чтобы она принимала
аргумент и возвращала значение того же типа. Типом может
быть только целое число или его подклассы.
def add(a):
...
Решение
def add[T: int](a: T) -> T:
...
Тестовые примеры:
from typing import List, assert_type
class MyInt(int):
pass
assert_type(add(1), int)
assert_type(add(MyInt(1)), MyInt)
assert_type(add("1"), str)
add(["1"], ["2"]) # Ошибочный вариант
add("1", 2) # Ошибочный вариант
Атрибут экземпляра
Добавьте в класс Foo атрибут экземпляра bar целочисленного типа.
class Foo:
...
Решение
class Foo:
bar: int
Тестовые примеры:
foo = Foo()
foo.bar = 1
foo.bar = "1" # Ошибочный вариант
Литерал
Измените аннотацию функции foo таким образом, чтобы она принимала
аргумент строкового типа только с двумя значениями left или right.
def foo(direction):
...
Решение
from typing import Literal
def foo(direction: Literal['left', 'right']):
...
Тестовые примеры:
foo("left")
foo("right")
a = "".join(["l", "e", "f", "t"])
foo(a) # Ошибочный вариант
Строковый литерал
Измените аннотацию функции execute_query, которая выполняет SQL запрос,
а также может предотвращать атаки с использованием SQL-инъекций.
from typing import Iterable
def execute_query(sql, parameters: Iterable[str] = ...):
...
Решение
from typing import Iterable, LiteralString
def execute_query(sql: LiteralString, parameters: Iterable[str] = ...):
...
Тестовые примеры:
def query_user(user_id: str):
query = f"SELECT * FROM data WHERE user_id = {user_id}"
execute_query(query) # Ошибочный вариант
def query_data(user_id: int, limit: bool) -> None:
query = """
SELECT
user.name
user.age
FROM data
WHERE user_id = ?
"""
if limit:
query += " LIMIT 1"
execute_query(query, (user_id, ))
self
Измените аннотацию метода return_self таким образом, чтобы она
возвращала экземпляр текущего класса.
import typing
class Foo:
def return_self(self):
...
Решение (вариант 1)
import typing
class Foo:
def return_self(self) -> typing.Self:
...
Решение (вариант 2, устаревший)
from typing import TypeVar
Self = TypeVar("Self", bound="Foo")
class Foo:
def return_self(self: Self) -> Self:
...
Тестовые примеры:
class SubclassOfFoo(Foo):
pass
f: Foo = Foo().return_self()
sf: SubclassOfFoo = SubclassOfFoo().return_self()
sf: SubclassOfFoo = Foo().return_self() # Ошибочный вариант
Типизированный словарь 1
Определите класс Student, который представляет собой словарь с тремя ключами:
nameв виде строкиageв виде целого числаschoolв виде строки
Решение
from typing import TypedDict
class Student(TypedDict):
name: str
age: int
school: str
Тестовые примеры:
a: Student = {"name": "Tom", "age": 15, "school": "Hogwarts"}
assert Student(name="Tom", age=15, school="Hogwarts") == dict(name="Tom", age=15, school="Hogwarts")
a: Student = {"name": 1, "age": 15, "school": "Hogwarts"} # Ошибочный вариант
a: Student = {(1,): "Tom", "age": 2, "school": "Hogwarts"} # Ошибочный вариант
a: Student = {"name": "Tom", "age": "2", "school": "Hogwarts"} # Ошибочный вариант
a: Student = {"name": "Tom", "age": 2} # Ошибочный вариант
Типизированный словарь 2
Определите класс Student, который представляет собой словарь с тремя ключами:
nameв виде строкиageв виде целого числаschoolв виде строки (опциональное поле)
Решение
from typing import TypedDict, NotRequired
class Student(TypedDict):
name: str
age: int
school: NotRequired[str]
Тестовые примеры:
a: Student = {"name": "Tom", "age": 15}
a: Student = {"name": "Tom", "age": 15, "school": "Hogwarts"}
a: Student = {"name": 1, "age": 15, "school": "Hogwarts"} # Ошибочный вариант
a: Student = {(1,): "Tom", "age": 2, "school": "Hogwarts"} # Ошибочный вариант
a: Student = {"name": "Tom", "age": "2", "school": "Hogwarts"} # Ошибочный вариант
a: Student = {"z": "Tom", "age": 2} # Ошибочный вариант
assert Student(name="Tom", age=15) == dict(name="Tom", age=15)
assert Student(name="Tom", age=15, school="Hogwarts") == dict(name="Tom", age=15, school="Hogwarts")
Типизированный словарь 3
Определите класс Person, который представляет собой словарь с пятью
ключами:
- name в виде строки (обязательное)
- age в виде целого числа (опциональное)
- gender в виде строки (опциональное)
- address в виде строки (опциональное)
- email в виде строки (опциональное)
Решение
from typing import TypedDict, Required
class Person(TypedDict, total=False):
name: Required[str]
age: int
gender: str
address: str
email: str
Тестовые примеры:
a: Person = {
"name": "Capy",
"age": 1,
"gender": "Male",
"address": "earth",
"email": "capy@bara.com",
}
a: Person = {"name": "Capy"}
a: Person = {
"age": 1,
"gender": "Male",
"address": "",
"email": "",
} # Ошибочный вариант
Распаковка
Измените аннотацию функции foo таким образом, чтобы она принимала
именованные аргументы name строкового типа и age целочисленного типа.
from typing import TypedDict
class Person(TypedDict):
name: str
age: int
def foo(**kwargs):
...
Решение
from typing import TypedDict, Unpack
class Person(TypedDict):
name: str
age: int
def foo(**kwargs: Unpack[Person]):
...
Тестовые примеры:
person: Person = {
"name": "The Meaning of Life",
"age": 1983,
}
foo(**person)
foo(**{"name": "Brian", "age": 30})
foo(**{"name": "Brian"}) # Ошибочный вариант
person2: dict[str, object] = {"name": "Brian", "age": 30}
foo(**person2) # Ошибочный вариант
foo(**{"name": "Brian", "age": "1979"}) # Ошибочный вариант
Продвинутый уровень
- Протокол буфера
- Протокол вызываемого объекта
- Декоратор с аргументом
- Дескриптор
- Копирование объекта
- Генератор
- Универсальный класс
- Запрет вызова функции 1
- Запрет вызова функции 2
- Перегрузка функций
- Перегрузка функций с литералами
- Декоратор в виде класса
- Протокол
- Рекурсивный тип
- Фабрика
- Типизированный словарь 4
- Сужение типов
typeshed- Вариативные обобщения
Протокол буфера
Измените аннотацию функции foo таким образом, чтобы она принимала все
объекты, реализующие протокол буфера.
def read_buffer(b):
...
Решение
from collections.abc import Buffer
def read_buffer(b: Buffer):
...
Тестовые примеры:
from array import array
class MyBuffer:
def __init__(self, data: bytes):
self.data = data = bytearray(data)
self.view = None
def __buffer__(self, flags: int) -> memoryview:
self.view = memoryview(self.data)
return self.view
read_buffer(b"foo")
read_buffer(memoryview(b"foo"))
read_buffer(array("l", [1, 2, 3, 4, 5]))
read_buffer(MyBuffer(b"foo"))
read_buffer("foo") # Ошибочный вариант
read_buffer(1) # Ошибочный вариант
read_buffer(["foo"]) # Ошибочный вариант
Протокол вызываемого объекта
Определите вызываемый тип, который принимает строковый аргумент с
именем name и возвращает None.
class SingleStringInput:
...
Решение
from typing import Protocol
class SingleStringInput(Protocol):
def __call__(self, name: str) -> None:
...
Тестовые примеры:
def accept_single_string_input(func: SingleStringInput) -> None:
func(name="name")
def string_name(name: str) -> None:
...
def string_value(value: str) -> None:
...
def return_string(name: str) -> str:
return name
accept_single_string_input(string_name)
accept_single_string_input(string_value) # Ошибочный вариант
accept_single_string_input(return_string) # Ошибочный вариант
Декоратор с аргументом
Измените аннотацию декоратора decorator таким образом, чтобы он принимал
и возвращал функцию с той же сигнатурой, а также аргумент message
строкового типа.
def decorator(message):
...
Решение
from collections.abc import Callable
def decorator[T: Callable](message: str) -> T:
...
Тестовые примеры:
@decorator(message="x")
def foo(a: int, *, b: str) -> None:
...
@decorator # Ошибочный вариант
def bar(a: int, *, b: str) -> None:
...
foo(1, b="2")
foo(1, "2") # Ошибочный вариант
foo(a=1, e="2") # Ошибочный вариант
decorator(1) # Ошибочный вариант
Дескриптор
Аннотируйте метод __get__ класса дескриптора.
class Descriptor:
def __get__(self, instance, owner):
...
Решение
from typing import Any, Self, overload
class Descriptor:
@overload
def __get__(self, instance: None, owner: type) -> Self:
"""you don't need to implement this"""
...
@overload
def __get__(self, instance: Any, owner: type) -> str:
"""you don't need to implement this"""
...
def __get__(self, instance: Any | None, owner: type) -> Self | str:
"""you don't need to implement this"""
...
Тестовые примеры:
class TestClass:
a = Descriptor()
def descriptor_self(x: Descriptor) -> None:
...
def string_value(x: str) -> None:
...
descriptor_self(TestClass.a)
string_value(TestClass().a)
descriptor_self(TestClass().a) # Ошибочный вариант
string_value(TestClass.a) # Ошибочный вариант
Копирование объекта
Исправьте ошибку в аннотации типов для метода copy.
class MyClass:
def __init__(self, x: int) -> None:
self.x = x
# TODO: Fix the type hints of `copy` to make it type check
def copy(self) -> MyClass:
copied_object = MyClass(x=self.x)
return copied_object
Решение (вариант 1)
class MyClass:
def __init__(self, x: int) -> None:
self.x = x
# TODO: Fix the type hints of `copy` to make it type check
def copy(self) -> 'MyClass':
copied_object = MyClass(x=self.x)
return copied_object
Решение (вариант 2)
from __future__ import annotations
class MyClass:
def __init__(self, x: int) -> None:
self.x = x
def copy(self) -> MyClass:
copied_object = MyClass(x=self.x)
return copied_object
Решение (вариант 3, но изменен код задачи)
from typing import Self
class MyClass:
def __init__(self, x: int) -> None:
self.x = x
def copy(self) -> Self:
copied_object = self.__class__(x=self.x)
return copied_object
Тестовые примеры:
from typing import assert_type
inst = MyClass(x=1)
assert_type(inst.copy(), MyClass)
Генератор
Аннотируйте генератор gen таким образом, чтобы он выдавал целое число
и принимал строку, но ничего не возвращал.
def gen():
...
Решение
from collections.abc import Generator
def gen() -> Generator[int, str, None]:
...
Тестовые примеры:
from typing import assert_type
generator = gen()
assert_type(next(generator), int)
generator.send("sss")
generator.send(3) # Ошибочный вариант
Универсальный класс
Аннотируйте универсальный класс Stack. Его экземпляр может быть создан
с определенным типом, при этом метод push принимает объект указанного
типа, а pop возвращает объект того же типа.
class Stack:
def __init__(self) -> None:
self.items = []
def push(self, item) -> None:
self.items.append(item)
def pop(self):
return self.items.pop()
Решение
class Stack[T]:
def __init__(self) -> None:
self.items: list[T] = []
def push(self, item: T) -> None:
self.items.append(item)
def pop(self) -> T:
return self.items.pop()
Тестовые примеры:
from typing import assert_type
s = Stack[int]()
s.push(1)
assert_type(s.pop(), int)
s.push("foo") # Ошибочный вариант
assert_type(s.pop(), str) # Ошибочный вариант
Запрет вызова функции 1
Аннотируйте функцию, которую не следует вызывать.
def never_call_me(arg):
pass
Решение
from typing import Never
def never_call_me(arg: Never):
pass
Тестовые примеры:
def int_or_str(arg: int | str) -> None:
never_call_me(arg) # Ошибочный вариант
match arg:
case int():
print("It's an int")
case str():
print("It's a str")
case _:
never_call_me(arg)
См. пример
использования Never для проверки невозможных вариантов исполнения.
Запрет вызова функции 2
Реализуйте функцию stop, чтобы она удовлетворяла типизации.
from typing import Never
def stop() -> Never:
...
Решение
from typing import Never
def stop() -> Never:
raise ValueError("Error!")
Тестовые примеры:
from typing import assert_never
assert_never(stop())
Перегрузка функций
Аннотируйте функцию process таким образом, чтобы:
- когда в качестве
responseпередаютbytesвозвращается строка - когда в качестве
responseпередаютintвозвращается кортеж из целого числа и строки - когда в качестве
responseпередаютNoneвозвращаетсяNone
def process(response: int | bytes | None) -> str | None | tuple[int, str]:
...
Решение
from typing import overload
@overload
def process(response: bytes) -> str:
...
@overload
def process(response: int) -> tuple[int, str]:
...
@overload
def process(response: None) -> None:
...
def process(response: int | bytes | None) -> str | None | tuple[int, str]:
...
Тестовые примеры:
from typing import assert_type
assert_type(process(b"42"), str)
assert_type(process(42), tuple[int, str])
assert_type(process(None), None)
assert_type(process(42), str) # Ошибочный вариант
assert_type(process(None), str) # Ошибочный вариант
assert_type(process(b"42"), tuple[int, str]) # Ошибочный вариант
assert_type(process(None), tuple[int, str]) # Ошибочный вариант
assert_type(process(42), str) # Ошибочный вариант
assert_type(process(None), str) # Ошибочный вариант
Перегрузка функций с литералами
Аннотируйте функцию foo таким образом, чтобы:
- если второй аргумент равен 1, возвращалось целое число
- если второй аргумент равен 2, возвращалась строка
- если второй аргумент равен 3, возвращался список
- в противном случае возвращался первый аргумент
def foo(value, flag):
...
Решение
from typing import overload, Any, Literal
@overload
def foo(value: Any, flag: Literal[1]) -> int:
...
@overload
def foo(value: Any, flag: Literal[2]) -> str:
...
@overload
def foo(value: Any, flag: Literal[3]) -> list[Any]:
...
@overload
def foo[T](value: T, flag: Any) -> T:
...
def foo(value: Any, flag: Any) -> Any:
...
Тестовые примеры:
foo("42", 1).bit_length()
foo(42, 2).upper()
foo(True, 3).append(1)
foo({}, "4").keys()
foo("42", 1).upper() # Ошибочный вариант
foo(42, 2).append(1) # Ошибочный вариант
foo(True, 3).bit_length() # Ошибочный вариант
foo({}, "4").upper() # Ошибочный вариант
Декоратор в виде класса
Аннотируйте класс Wrap, чтобы его можно было вызывать с теми же
аргументами, что и функцию, которую он декорирует.
class Wrap:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
Решение
from collections.abc import Callable
class Wrap[**P, R]:
def __init__(self, func: Callable[P, R]) -> None:
self.func = func
def __call__(self, *args: P.args, **kwargs: P.kwargs) ->R:
return self.func(*args, **kwargs)
Тестовые примеры:
from typing import assert_type
@Wrap
def add(a: int, b: int) -> int:
return a + b
@Wrap
def replace_wildcard(string: str, replacement: str, *, count: int = -1) -> str:
return string.replace("*", replacement, count)
assert_type(add(1, 2), int)
assert_type(replace_wildcard("Hello *", "World"), str)
assert_type(replace_wildcard("Hello *", "World", 1), str)
replace_wildcard("Hello *", 1) # Ошибочный вариант
replace_wildcard("Hello *", "World", 1) # Ошибочный вариант
Протокол
Определите протокол SupportsQuack, который поддерживает метод quack.
from typing import Protocol
class SupportsQuack:
...
Решение
from typing import Protocol
class SupportsQuack(Protocol):
def quack(self) -> None:
...
Тестовые примеры:
class Duck:
def quack(self) -> None:
print("quack!")
class Dog:
def bark(self) -> None:
print("bark!")
duck: SupportsQuack = Duck()
dog: SupportsQuack = Dog() # Ошибочный вариант
Рекурсивный тип
Определите тип Tree, представляющий словарь, ключами которого являются
строки, а значениями тот же тип Tree.
Tree = ...
Решение (python >= 3.12)
type Tree = dict[str, "Tree"]
Решение (python < 3.12)
from typing import TypeAlias
Tree: TypeAlias = dict[str, 'Tree']
Тестовые примеры:
def f(t: Tree):
pass
f({})
f({"foo": {}})
f({"foo": {"bar": {}}})
f({"foo": {"bar": {"baz": {}}}})
f(1) # Ошибочный вариант
f({"foo": []}) # Ошибочный вариант
f({"foo": {1: {}}}) # Ошибочный вариант
Фабрика
Аннотируйте функцию make_object таким образом, чтобы аннотация типов
соответствовала реализации.
def make_object(cls):
return cls()
Решение
def make_object[T](cls: type[T]) -> T:
return cls()
Тестовые примеры:
class MyClass:
pass
def f():
pass
c = make_object(MyClass)
c = make_object(int)
c = make_object(f) # Ошибочный вариант
c = make_object("sss") # Ошибочный вариант
c = make_object(["sss"]) # Ошибочный вариант
Типизированный словарь 4
Определите класс Undergraduate, используя предопределенный класс Student.
У Undergraduate должен быть дополнительный атрибут major строкового типа.
from typing import TypedDict
class Student(TypedDict):
name: str
age: int
school: str
class Undergraduate:
...
Решение
from typing import TypedDict
class Student(TypedDict):
name: str
age: int
school: str
class Undergraduate(Student):
major: str
Тестовые примеры:
a: Undergraduate = {"name": "Tom", "age": 20, "school": "MIT", "major": "Math"}
assert Undergraduate(name="Tom", age=20, school="MIT", major="Math") == {
"name": "Tom",
"age": 20,
"school": "MIT",
"major": "Math",
}
a: Undergraduate = {"name": "Tom", "age": 20, "school": "MIT"} # Ошибочный вариант
Сужение типов
Реализуйте пользовательскую функцию сужения типов is_string, которая
будет проверять что переданное значение является строкой.
from typing import Any
def is_string(value: Any):
return isinstance(value, str)
Решение
from typing import Any, TypeGuard
def is_string(value: Any) -> TypeGuard[str]:
return isinstance(value, str)
Тестовые примеры:
from typing import assert_type
a: str | None = "hello"
if is_string(a):
assert_type(a, str)
else:
assert_type(a, type(None)) # Ошибочный вариант
typeshed
Аннотируйте класс MyContainer таким образом, чтобы он поддерживал
операцию доступа к элементу по его имени через оператор квадратных
скобок, т.е. [x].
class MyContainer:
...
Решение
from _typeshed import SupportsItemAccess
class MyContainer(SupportsItemAccess):
...
Тестовые примеры:
from typing import assert_type
from collections.abc import Mapping
c = MyContainer()
c[1] = 1
c["2"] = 2
print(c[1])
print(c["2"])
del c[1]
del c["2"]
assert_type(c, dict) # Ошибочный вариант
assert_type(c, Mapping) # Ошибочный вариант
Вариативные обобщения
Определите тип Array, который поддерживает операцию поэлементного
сложения двух массивов одинакового размера и типов.
class Array:
def __add__(self, other):
...
Решение
class Array[*Ts]:
def __add__(self, other: "Array[*Ts]") -> "Array[*Ts]":
...
Тестовые примеры:
from typing import assert_type
a: Array[float, int] = Array()
b: Array[float, int] = Array()
assert_type(a + b, Array[float, int])
c: Array[float, int, str] = Array()
assert_type(a + c, Array[float, int, str]) # Ошибочный вариант
Экстремальный уровень
Объединение параметров
Предположим, есть функция foo, первым параметром которой может быть
что угодно. Вы хотите использовать foo, но хотите ограничить первый
аргумент значением Person. Изменение исходного кода foo невозможно,
поэтому вы решаете написать функцию transform, чтобы преобразовать
foo в нужную функцию.
class Person:
pass
def transform(f):
def wrapper(value: Person, *args, **kwargs):
return f(value, *args, **kwargs)
return wrapper
Решение
from collections.abc import Callable
from typing import Concatenate
class Person:
pass
def transform[**P, R](f: Callable[Concatenate[Person, P], R]):
def wrapper(value: Person, *args: P.args, **kwargs: P.kwargs) -> R:
return f(value, *args, **kwargs)
return wrapper
Тестовые примеры:
def foo(value, mode: str) -> None:
pass
foo_with_person = transform(foo)
foo_with_person(Person(), "1")
foo_with_person(Person(), mode="1")
foo_with_person(1, "1") # Ошибочный вариант
foo_with_person(Person(), 1) # Ошибочный вариант
foo_with_person(Person(), "1", 2) # Ошибочный вариант
def foo2(value, mode: str, *, maybe: bool) -> int:
return 1
foo_with_person = transform(foo2)
foo_with_person(Person(), "1", maybe=True)
foo_with_person(value=Person(), mode="1", maybe=True)
foo_with_person(Person(), mode="1", maybe=True)
foo_with_person(Person(), mode="1") # Ошибочный вариант
foo_with_person(Person(), 1, maybe=True) # Ошибочный вариант
foo_with_person(Person(), "1", maybe=1) # Ошибочный вариант
foo_with_person(Person(), "1", True) # Ошибочный вариант
Декоратор-конструктор
Определите декоратор constructor_parameter, который принимает тип Foo
и возвращает функцию-обертку с той же сигнатурой, что и у конструктора Foo.
При этом функция, декорированная с помощью constructor_parameter, может
быть вызвана с экземпляром Foo.
def constructor_parameter[T](class_: T):
...
Декоратор, добавляющий параметр
Аннотируйте декоратор класса Fn, который принимает вызываемый объект f.
Метод transform_callable класса Fn преобразует f в другой вызываемый
объект с дополнительным параметром Any в начале, сохраняя при этом остальные
части сигнатуры функции.
class Fn:
def __init__(self, f):
self.f = f
def transform_callable(self):
...
Решение (python >= 3.12)
from collections.abc import Callable
from typing import Concatenate, Any
class Fn[**P, R]:
def __init__(self, f: Callable[P, R]) -> None:
self.f = f
def transform_callable(self) -> Callable[Concatenate[Any, P], R]:
...
Решение (python < 3.12)
R = TypeVar("R")
P = ParamSpec("P")
class Fn(Generic[R, P]):
def __init__(self, f: Callable[P, R]):
self.f = f
def transform_callable(self) -> Callable[Concatenate[object, P], R]:
...
Тестовые примеры:
from typing import assert_type, Any
@Fn
def example(a: int, b: str, c: float, *, d: bool = False) -> None:
return
assert_type(example.f(1, "1", 1.0, d=False), None)
a: Any = 11111111
b = example.transform_callable()(a, 1, "1", 1.0, d=False)
assert_type(b, None)
example.transform_callable()(1, "1", 1.0, d=False) # Ошибочный вариант
Последовательности
Аннотируйте функции f и g, чтобы тесты прошли успешно.
def f(a):
pass
def g(a):
pass
Решение
from collections.abc import Sequence
def f(a: list[int | str]):
pass
def g(a: Sequence[int | str]):
pass
Тестовые примеры:
l1: list[int] = [1, 2]
f(l1) # Ошибочный вариант
g(l1)
l2: list[int | str] = [1, 2]
f(l2)
g(l2)
f(1) # Ошибочный вариант
f("abc") # Ошибочный вариант
g("abc")
g(b"abc")
g([1, "2"])
g((1, "2", 3))
g(1) # Ошибочный вариант
g({1}) # Ошибочный вариант