Используйте комментарии типа для узкого набора уже объявленной переменной Python - PullRequest
1 голос
/ 18 февраля 2020

Как можно использовать комментарии типа в Python, чтобы изменить или сузить тип уже объявленной переменной таким образом, чтобы заставить pycharm или другие системы с поддержкой типов понять новый тип.

Например, у меня может быть два класса:

class A:
   is_b = False
   ...

class B(A):
   is_b = True

   def flummox(self):
       return '?'

и другая функция в другом месте:

def do_something_to_A(a_in: A):
    ...
    if a_in.is_b:
       assert isinstance(a_in, B)  # THIS IS THE LINE...
       a_in.flummox()

Пока у меня есть оператор assert, PyCharm будет понимать, что я сузился a_in до класса B и не жаловался на .flummox(). Без этого появятся ошибки / предупреждения, такие как a_in has no method flummox.

У меня вопрос: есть ли способ PEP 484 (или преемник) показать, что a_in (который мог изначально иметь тип A или B или что-то еще) теперь типа B * 1017? * без утверждения assert . Оператор b_in : B = a_in также дает ошибки типа.

В TypeScript я мог бы сделать что-то вроде этого:

if a_in.is_b:
   const b_in = <B><any> a_in;
   b_in.flummox()

// or

if a_in.is_b:
   (a_in as B).flummox()

Есть две основные причины, по которым я не хочу использовать строку assert, это (1) скорость очень важна для этой части кода, и наличие дополнительного вызова is_instance для каждого запуска строки слишком сильно замедляет его, и (2) стиль кода проекта, который запрещает использование открытых утверждений.

1 Ответ

3 голосов
/ 18 февраля 2020

Пока вы используете Python 3.6+, вы можете «повторно аннотировать» тип переменной произвольно, используя тот же синтаксис, который вы использовали бы для «объявления» типа переменной без ее инициализации ( PEP 526 ).

В приведенном вами примере ожидается следующий фрагмент кода:

def do_something_to_A(a_in: A):
    ...
    if a_in.is_b:
       a_in: B
       a_in.flummox()

Я проверил, что этот метод правильно обнаруживается PyCharm 2019.2.

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

def do_something_with_annotation(a_in: A): 
     if a_in.is_b: 
        a_in: B 
        a_in.flummox() 


def do_something_without_annotation(a_in: A): 
     if a_in.is_b: 
        a_in.flummox() 

dis создает следующий байт-код:

>>> dis.dis(do_something_with_annotation)
  3           0 LOAD_FAST                0 (a_in)
              2 LOAD_ATTR                0 (is_b)
              4 POP_JUMP_IF_FALSE       14

  5           6 LOAD_FAST                0 (a_in)
              8 LOAD_ATTR                1 (flummox)
             10 CALL_FUNCTION            0
             12 POP_TOP
        >>   14 LOAD_CONST               0 (None)
             16 RETURN_VALUE
>>> dis.dis(do_something_without_annotation)
  3           0 LOAD_FAST                0 (a_in)
              2 LOAD_ATTR                0 (is_b)
              4 POP_JUMP_IF_FALSE       14

  4           6 LOAD_FAST                0 (a_in)
              8 LOAD_ATTR                1 (flummox)
             10 CALL_FUNCTION            0
             12 POP_TOP
        >>   14 LOAD_CONST               0 (None)
             16 RETURN_VALUE

В качестве примечания можно также сохранить операторы утверждения и отключите утверждения в производственной среде, вызвав интерпретатор с флагом -O. Это может или не может считаться более читаемым вашими коллегами, в зависимости от того, насколько они знакомы с типом подсказок в Python.

...