Как получить аннотации переменных Python? - PullRequest
7 голосов
/ 10 июля 2019

Как определить класс / модуль с аннотированными полями, как я могу получить аннотации, как в функциях?

class Test:
  def __init__(self):
    self.x : int
t = Test()

Теперь мне нужно 'int' от getattr(t,'x')

Ответы [ 3 ]

4 голосов
/ 17 июля 2019

В базовом Python нет возможности делать то, что вы хотите, не меняя определения Test.Минимальным изменением было бы аннотировать атрибут на уровне класса:

class Test:
    x: int
    def __init__(self):
        # define self.x or not, but it needn't be annotated again

Это на самом деле прекрасно; по умолчанию предполагается, что аннотации в области видимости ссылаются на атрибуты экземпляр , а не класс атрибуты (назначение значения в области видимости класса создает атрибут класса, ноаннотировать это не надо);Вы должны явно использовать typing.ClassVar, чтобы указать, что аннотированный тип предназначен только для атрибута класса. Раздел PEP 526 по аннотациям переменных класса и экземпляра определяет это поведение;на них можно положиться, а не просто на случай реализации.

Как только вы это сделаете, typing.get_type_hints вернет {'x': int} как для Test, так и t в вашем примере.

Хотя этого достаточно само по себе, я отмечу, что во многих таких случаях в настоящее время, если в любом случае вы аннотируете, вы можете упростить свой код с помощью dataclasses модуль , получение аннотаций и основных функций, определенных для вас, с минимальным набором текста.Простой код замены для вашего дела будет выглядеть следующим образом:

import dataclasses

@dataclasses.dataclass
class Test:
    x: int

Хотя в вашем деле не представлен полный набор функций (в основном это просто замена __init__ декоратором), он все же делает больше, чем кажется на первый взгляд,В дополнение к определению __init__ для вас (ожидается получение аргумента x, который аннотируется как int), а также подходящих __repr__ и __eq__, вы можете легко определить значения по умолчанию (простоназначьте значение по умолчанию в точке аннотации или для более сложных или изменяемых случаев, вместо этого присвойте dataclasses.field), и вы можете передать аргументы dataclass, чтобы он создавал сортируемые или неизменные экземпляры.

В вашем случаеглавное преимущество заключается в устранении избыточности;x аннотируется и ссылается ровно один раз, а не аннотируется один раз на уровне класса, а затем используется (и, возможно, снова аннотируется) во время инициализации.

1 голос
/ 17 июля 2019

Если вы используете mypy, вы можете использовать reveal_type() для проверки аннотации типа любого выражения.

Обратите внимание, чтоэто будет работать только при запуске mypy над файлом, а не при обычном запуске файла.

test.py:

import sys
from typing import Dict, Optional


class Test:
    def __init__(self) -> None:
        self.x: Optional[Dict[str, int]]


test = Test()
if "mypy" in sys.modules:
    reveal_type(test.x)
else:
    print("not running with mypy")

Пример при запуске над ним mypy:

$ mypy test.py
test.py:10: error: Revealed type is 'Union[builtins.dict[builtins.str, builtins.int], None]'

А при нормальном запуске:

$ python3 test.py
not running with mypy
1 голос
/ 10 июля 2019

Я не уверен, что вы можете легко получить аннотации self.x.

Принимая ваш код:

class Test:
    def __init__(self):
        self.x: int = None

t = Test()

Я пытался найти __annotations__ в Test и t (где я и ожидал), без особой удачи.

Однако, вы могли бы сделать этот обходной путь:

class Test:
    x: int
    def __init__(self):
        # annotation from here seems to be unreachable from `__annotations__`
        self.x: str

t = Test()

print(Test.__annotations__)
# {'x': <class 'int'>}
print(t.__annotations__)
# {'x': <class 'int'>}

РЕДАКТИРОВАТЬ

Если вы хотите иметь возможность проверить тип self.x в mypy, проверьте ответ от @ ruohola .


РЕДАКТИРОВАТЬ 2

Обратите внимание, чтоmypy (по крайней мере v.0.560) делает запутанным, комментируя x как от class, так и от __init__, то есть похоже, что аннотация self.x смело игнорируется:

import sys

class Test:
    x: str = "0"
    def __init__(self):
        self.x: int = 1

t = Test()

print(Test.x, t.x)
# 0 1
print(Test.x is t.x)
# False

if "mypy" in sys.modules:
    reveal_type(t.x)
    # from mypyp: annotated_self.py:14: error: Revealed type is 'builtins.str'
    reveal_type(Test.x)
    # from mypy: annotated_self.py:15: error: Revealed type is 'builtins.str'

Test.x = 2
# from mypy: annotated_self.py:17: error: Incompatible types in assignment (expression has type "int", variable has type "str")

t.x = "3"
# no complaining from `mypy`
t.x = 4
# from mypy: annotated_self.py:19: error: Incompatible types in assignment (expression has type "int", variable has type "str")

print(Test.x, t.x)
# 2 4
...