Как стать автором
Обновить

Комментарии 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')

Кстати, никогда не проверял, в аннотациях функции они присутствуют как str, или как ForwardRef объекты… И можно ли что-то с ними делать.

Это будет работать в 3.7 если добавить
from __future__ import annotations

С версии 4.0 будет работать по дефолту

Если кому интересно — вот почему нет доступа к __orig_class__: в typing.py#L670 сначала создается объект, а потом уже устанавливается атрибут __orig_class__


Так что никаких хитростей, просто не предусмотрели что эта информация будет кому-то нужна.

Ну да :)
Обновил "параметризованный дженерик" в статье на такой, который может вытаскивать тип в __init__. Правда, не уверен, что такая реализация не сломается при следующем обновлении...

А может кто-нибудь объяснить вот этот подводный камень:
На строчку
foo = {}

ругается, что error: Need type annotation for variable, а когда добавляешь очевидный хинт
foo: typing.Dict = {}

ругаться перестает.
Вот зачем
1) ругаться, если и так понятно, что это dict
2) допускать подавление ошибки таким топорным способом?
Вопрос конечно очень простой и рядом не стоит с дженериками, но из-за таких простых вещей инструмент кажется каким-то дико недоработанным и прикасаться к нему вообще не хочется.
Я сейчас со 100% уверенностью не отвечу, потому что так ни разу и не занимался настройкой mypy через конфиг-файл. Мне хватает того, что передает ему Anaconda из ST3.
Но я почти уверен, что дело в том, что в первом случае MyPy воспринимает выражение как untyped assignement in type context, хоть и выражается короче. В общем и целом, он думает, что вы забыли.
Во втором случае тип резолвится к `Dict[Any, Any]`. В стандартной поставке это OK, но можно настроить, чтобы он ругался на все места, где фигурирует Any
В Crystal, компилируемом в нормальный машинный код языке, статическая типизация достигается просто и логично, при том. что синтаксис от python-то не сильно отличается (Ruby-like).
Но здесь — я как начал читать, у меня просто глаза на лоб полезли и волосы зашевелились. И это в интерпретируемом-то языке такая жесть.

Благо, что можно не использовать всю "такую жесть", например, с дженериками. Если не хочется упарываться по типизации всего и вся, достаточно указания базовых типов в аннотациях аргументов и переменных где это действительно нужно, например, для помощи линтерам и автокомплитерам.


И кстати, автор забыл упомянуть, что хоть тайпхинты и 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. Или если хотите иметь возможность заменить тип внутри класса и во всех аннотациях с ним связанных – делаете алиас

Вы правы, говорить что поведение неверное не совсем правильно :) Это я на джаву насмотрелся где подобным образом определяется тип enum, тип как объект указывается. Спасибо за пример!

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации