View on GitHub

Methodics of Python Intro

Practical materials for learning Python

Главная прелесть питона — duck typing. Про него мы много раз говорили, там ноль проблем сдомашками (30 строк на домашку) итд

Но

Все ошибки рантаймовые, с ними трудно работать. Чем больше программа, тем больше процент того, что мы не то передадим, не так обратимся итд

Часть прагматики растворяется в коде, с этим проблема, хорошо бы знать, что не надо передавать в функции


Аннотации — такие описания типов, с помощью которых можно коммуницировать с пользователем

>>> import annotationlib  
>>> class C:  
...     A: int = 2
...     B: float
...     def __init__(self, param: int = None, signed: bool = True) -> None:
...         if param is not None:
...             self.A = param if signed else abs(param)
...     def mult(self, mlt: int) -> str:
...         return self.A * mlt
...
>>> c = C(100)  
>>> s = C("QWE")  
>>> c.mult(3)  
300  
>>> c.mult("q")  
'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'  
>>> s.mult(3)  
'QWEQWEQWE'  
>>>
>>> C.__annotations__  
{'A': <class 'int'>, 'B': <class 'float'>}  
>>> C.A  
2  
>>> c.A  
100  
>>> s.A  
'QWE'  
>>> C.B  
Traceback (most recent call last):  
 File "<python-input-13>", line 1, in <module>
   C.B
AttributeError: type object 'C' has no attribute 'B'  
>>>

Аннотацию можно получить и по-другому

>>> C.__annotate_func__(annotationlib.Format.VALUE)  
{'A': <class 'int'>, 'B': <class 'float'>}  
>>>
>>> annotationlib.get_annotations(C)  
{'A': <class 'int'>, 'B': <class 'float'>}  
>>> annotationlib.get_annotations(C, format=annotationlib.Format.STRING)  
{'A': 'int', 'B': 'float'}  
>>> annotationlib.get_annotations(c.__init__)  
{'param': <class 'int'>, 'signed': <class 'bool'>, 'return': None}  
>>> annotationlib.get_annotations(s.mult)  
{'mlt': <class 'int'>, 'return': <class 'str'>}  
>>>

Аннотации — это только подсказки и не более. Но в них самые настоящие типы, с ними можно работать


До 3.14 питона нельзя было делать аннотации на себя же. Теперь с помощью отложенного аннотирования так можно

>>> class C:  
...     next: C
...
Traceback (most recent call last):  
 File "<python-input-0>", line 1, in <module>
   class C:
       next: C
 File "<python-input-0>", line 2, in C
   next: C
         ^
NameError: name 'C' is not defined  
>>>
>>> class C:  
...     next: C
...
>>>

Можно даже так, только вывод формата аннотации будет особенный

>>> class C:  
...     next: D
...
>>> annotationlib.get_annotations(C)  
Traceback (most recent call last):  
 File "<python-input-40>", line 1, in <module>
   annotationlib.get_annotations(C)
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
 File "/usr/lib64/python3.14/annotationlib.py", line 958, in get_annotations
   ann = _get_dunder_annotations(obj)
 File "/usr/lib64/python3.14/annotationlib.py", line 1133, in _get_dunder_annotations
   ann = _BASE_GET_ANNOTATIONS(obj)
 File "<python-input-39>", line 2, in __annotate__
   next: D
         ^
NameError: name 'D' is not defined  
>>> annotationlib.get_annotations(C, format=annotationlib.Format.STRING)  
{'next': 'D'}  
>>>

Есть формат отложенного вычисления

>>> res = annotationlib.get_annotations(C, format=annotationlib.Format.FORWARDREF)  
>>> res  
{'next': ForwardRef('D', is_class=True, owner=<class '__main__.C'>)}  
>>> res.evaluate()  
Traceback (most recent call last):  
 File "<python-input-46>", line 1, in <module>
   res.evaluate()
   ^^^^^^^^^^^^
AttributeError: 'dict' object has no attribute 'evaluate'  
>>> res['next'].evaluate()  
Traceback (most recent call last):  
 File "<python-input-47>", line 1, in <module>
   res['next'].evaluate()
   ~~~~~~~~~~~~~~~~~~~~^^
 File "/usr/lib64/python3.14/annotationlib.py", line 202, in evaluate
   raise NameError(_NAME_ERROR_MSG.format(name=arg), name=arg)
NameError: name 'D' is not defined  
>>> class D: pass  
...
>>> res['next'].evaluate()  
<class '__main__.D'>  
>>>

Есть ещё отложенное вычисление типов через type

>>> type integer = int  
>>> integer  
integer  
>>> type(integer)  
<class 'typing.TypeAliasType'>  
>>> integer.evaluate_value()  
<class 'int'>  
>>> annotationlib.call_evaluate_function(integer.evaluate_value, format=annotationlib.Format.STRING)  
'int'  
>>>
>>> type C = QQ  
>>> C.evaluate_value()  
Traceback (most recent call last):  
 File "<python-input-59>", line 1, in <module>
   C.evaluate_value()
   ~~~~~~~~~~~~~~~~^^
 File "<python-input-58>", line 1, in C
   type C = QQ
            ^^
NameError: name 'QQ' is not defined  
>>> annotationlib.call_evaluate_function(C.evaluate_value, format=annotationlib.Format.STRING)  
'QQ'  
>>> annotationlib.call_evaluate_function(C.evaluate_value, format=annotationlib.Format.FORWARDREF)  
ForwardRef('QQ')  
>>>

В целом, нигде не было сказано, что в аннотациях обязательно должны быть выражения, и оно должно на ходу вычисляться. Вот оно и не вычисляется.


Аннотации настолько оторваны от всего остального, что внутри функции почти нельзя читать её аннотацию.


Модуль typing и статическая типизация

У них есть отдельное сообщество даже. Отдельный ман итд.

Если коротко: есть модель статического описания через аннотации, под неё написана куча автопроверок итд. Зачем — см пункты из начала.


Интересное — составные типы

>>> def fun(var: list[int]) -> int:  
...     return sum(val)
...
>>> import annotationlib  
>>> annotationlib.get_annotations(fun)  
{'var': list[int], 'return': <class 'int'>}  
>>> list[int]  
list[int]  
>>> type(list[int])  
<class 'types.GenericAlias'>  
>>> def fun(var: list[int|float]) -> int:  
...     return sum(val)
...
>>> tuple[int]  
tuple[int]  
>>>
>>> import typing  
>>> typing.get_args(annotationlib.get_annotations(fun))  
()  
>>> annotationlib.get_annotations(fun)  
{'var': list[int | float], 'return': <class 'int'>}  
>>> annotationlib.get_annotations(fun)['val']  
Traceback (most recent call last):  
 File "<python-input-76>", line 1, in <module>
   annotationlib.get_annotations(fun)['val']
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^
KeyError: 'val'  
>>> annotationlib.get_annotations(fun)['var']  
list[int | float]  
>>> typing.get_args(annotationlib.get_annotations(fun)['var'])  
(int | float,)  
>>>

Все эти типы называются просто TypeAlias, потому что тянуть все эти гирлянды из типов — самоубийство

>>> type PATH = list[tuple[int, int]]  
>>> from math import dist  
>>> from itertools import pairwise  
>>>
>>> def length(p: PATH) -> int:  
...     return sum(dist(a, b) for a, b in pairwise(p))
...
>>> length([(0,0), (3, 4), (-1,1)])  
10.0  
>>>

Абстрактные классы

Набор базовых классов, от которых, конечно, никто не наследовался, но с ними проходит isinstance

>>> from collections.abc import Iterable  
>>> from collections.abc import Sequence  
>>>
>>> isinstance([1, 2, 3], Iterable)  
True  
>>> isinstance([1, 2, 3], Sequence)  
True  
>>> isinstance((c for c in "QWE"), Sequence)  
False  
>>> isinstance((c for c in "QWE"), Iterable)  
True  
>>>

Тут просто сравнение методов


Есть отдельная библиотека numbers для проверки числовых типов

>>> import numbers  
>>> def classify(num):  
...     match num:
...         case numbers.Rational(numerator=n, denominator=d):
...             print(f'{n}/{d}')
...
>>> classify(234)  
234/1  
>>> from fractions import Fraction  
>>> classify(Fraction("1.23"))  
123/100  
>>>
>>> def classify(num):  
...     match num:
...         case numbers.Rational(numerator=n, denominator=d):
...             print(f'{n}/{d}')
...         case numbers.Real(real=a):
...             print(f"Real {a}")
...
>>> classify(.33)  
Real 0.33  
>>> def classify(num):  
...     match num:
...         case numbers.Rational(numerator=n, denominator=d):
...             print(f'{n}/{d}')
...         case numbers.Real(real=a):
...             print(f"Real {a}")
...         case numbers.Number() as a:
...             print(f"Unknown number {a}")
...
>>> classify(3 + 5j)  
Unknown number (3+5j)  
>>>

Дженерики

Псевдонимы мы посмотрели, а можно ещё писать шаблоны, как в плюсах

>>> def negate[T](value: T) -> T:  
...         return -value
...
>>> negate(123)  
-123  
>>>

MyPy

Он, по сути, вызывает все те инструменты, которые мы посмотрели и тыкает нас за несоблюдение оного