Комментарии 13
Для меня было открытием понятие "forward reference". https://legacy.python.org/dev/peps/pep-0484/#forward-references
Оно в вашем посте используется, но не упоминается напрямую.
class A:
def b() -> B:
return B()
class B:
def a() -> A:
return A()
a = A()
b = B()
Этот код работать не будет:
Traceback (most recent call last):
File "_tmp/class.py", line 1, in <module>
class A:
File "_tmp/class.py", line 2, in A
def b() -> B:
NameError: name 'B' is not defined
А вот это сработает:
class A:
def b() -> "B":
return B()
class B:
def a() -> "A":
return A()
a = A()
b = B()
Интересно, что даже вот такое не будет работать:
class A:
def b() -> A:
return A()
a = A()
Собственно поэтому в посте и используются констукции с кавычками:
T = ty.TypeVar('T')
Если кому интересно — вот почему нет доступа к __orig_class__
: в typing.py#L670 сначала создается объект, а потом уже устанавливается атрибут __orig_class__
Так что никаких хитростей, просто не предусмотрели что эта информация будет кому-то нужна.
На строчку
foo = {}
ругается, что error: Need type annotation for variable, а когда добавляешь очевидный хинт
foo: typing.Dict = {}
ругаться перестает.
Вот зачем
1) ругаться, если и так понятно, что это dict
2) допускать подавление ошибки таким топорным способом?
Вопрос конечно очень простой и рядом не стоит с дженериками, но из-за таких простых вещей инструмент кажется каким-то дико недоработанным и прикасаться к нему вообще не хочется.
Но я почти уверен, что дело в том, что в первом случае MyPy воспринимает выражение как untyped assignement in type context, хоть и выражается короче. В общем и целом, он думает, что вы забыли.
Во втором случае тип резолвится к `Dict[Any, Any]`. В стандартной поставке это OK, но можно настроить, чтобы он ругался на все места, где фигурирует Any
Но здесь — я как начал читать, у меня просто глаза на лоб полезли и волосы зашевелились. И это в интерпретируемом-то языке такая жесть.
Благо, что можно не использовать всю "такую жесть", например, с дженериками. Если не хочется упарываться по типизации всего и вся, достаточно указания базовых типов в аннотациях аргументов и переменных где это действительно нужно, например, для помощи линтерам и автокомплитерам.
И кстати, автор забыл упомянуть, что хоть тайпхинты и typing — это в основном не про рантайм, но свой оверхед всё это добро всё же накладывает, причем в 3.7 этот модуль ускоряли.
в принципе, согласен. И Вы правы, но не так уж и накладно:
import cProfile
from pyksp.base_types import Var, Type
def test(n):
for i in range(n):
o = Var[int](local=True, name=f'name{i}')
isinstance(o, Type[int])
cProfile.run('test(100000)')
2800010 function calls (2600010 primitive calls) in 1.610 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 1.610 1.610 <string>:1(<module>)
300000 0.052 0.000 0.144 0.000 abc.py:137(__instancecheck__)
100003 0.021 0.000 0.041 0.000 abc.py:141(__subclasscheck__)
100000 0.024 0.000 0.024 0.000 abstract.py:523(__init__)
100000 0.039 0.000 0.131 0.000 abstract.py:611(__init__)
100000 0.322 0.000 1.065 0.000 base_types.py:292(__call__)
100000 0.031 0.000 0.031 0.000 base_types.py:340(__getitem__)
100000 0.079 0.000 0.254 0.000 base_types.py:350(__instancecheck__)
100000 0.018 0.000 0.018 0.000 base_types.py:365(__getitem__)
100000 0.240 0.000 0.491 0.000 base_types.py:450(__init__)
100000 0.048 0.000 0.048 0.000 base_types.py:473(_after_init)
100000 0.031 0.000 0.038 0.000 base_types.py:477(_get_type_prefix)
100000 0.064 0.000 0.555 0.000 base_types.py:588(__init__)
1 0.194 0.194 1.610 1.610 simple_test.py:5(test)
100000 0.090 0.000 0.105 0.000 typing.py:806(__new__)
100000 0.010 0.000 0.010 0.000 typing.py:890(cast)
100000 0.015 0.000 0.015 0.000 {built-in method __new__ of type object at 0x61D68078}
300000 0.092 0.000 0.092 0.000 {built-in method _abc._abc_instancecheck}
100003 0.020 0.000 0.020 0.000 {built-in method _abc._abc_subclasscheck}
1 0.000 0.000 1.610 1.610 {built-in method builtins.exec}
400000/200000 0.163 0.000 0.394 0.000 {built-in method builtins.isinstance}
300000 0.056 0.000 0.098 0.000 {built-in method builtins.issubclass}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
[Finished in 1.8s]
Может быть кто-то знает как заставить MyPy понимать Enum'ы? Чтобы передавался Атрибут класса как тип, а не возвращаемое им значение?
К примеру, поведение в случаях 1 и 2 неверно.
class Some(Enum):
ONE: int
TWO: text
# 1 в этом случае тип принимаемой переменной определиться как int
def func(some: Some.ONE): pass
# 2 в этом случае, тип определиться как целый объект Some
def func(some: Some): pass
Как сделать, что бы MyPy понимал что принимают что я Some.ONE это отдельный тип?
не совсем понимаю, почему он должен воспринимать атрибут как тип. Это было бы как раз неправильное поведение))
Я в таких случаях обычно делаю что-то вроде этого:
from enum import Enum
import typing as ty
ONET = ty.NewType('ONET', int)
class Some(Enum):
ONE: ONET
TWO: text
# 1 в этом случае тип принимаемой переменной определиться как int
def func(some: ONET):
reveal_type(some) # Revealed type is 'pyksp.simple_test.ONET'
# 2 в этом случае, тип определиться как целый объект Some
def func(some: Some):
pass
Ну а если он должен быть любым int так и делайте int. Или если хотите иметь возможность заменить тип внутри класса и во всех аннотациях с ним связанных – делаете алиас
Несколько подводных камней статической типизации в Python