Я б запропонував прочитати PEP 483 та PEP 484 і переглянути цю презентацію від Guido щодо натяку на тип.
Коротше кажучи : підказка типу - це буквально те, що означають слова, ви натякаєте на тип об’єктів, які ви використовуєте .
Зважаючи на динамічний характер Python, особливо важко зробити висновок або перевірити тип використовуваного об'єкта. Цей факт розробникам важко зрозуміти, що саме відбувається в коді, який вони не написали, і, що найголовніше, для інструментів перевірки типів, знайдених у багатьох ІДЕ [PyCharm, PyDev приходять на думку], які обмежені через те, що у них немає жодного показника того, який тип об’єктів є. Як результат, вони вдаються до спроб зробити висновок про тип (як згадується у презентації) приблизно 50% успішності.
Щоб взяти два важливі слайди з презентації Type Hinting:
Чому введіть підказки?
- Допомагає перевірки типів : натякаючи на тип, який ви хочете, щоб об’єкт був перевіряючим типом, можна легко виявити, якщо, наприклад, ви передаєте об'єкт типу, який не очікується.
- Допомога з документацією: Третя особа, яка переглядає ваш код, буде знати, що очікується, де, ерго, як ним користуватися, не отримуючи їх
TypeErrors
.
- Допомагає ІДЕ розробити більш точні та надійні інструменти: Середовища розробки будуть більш підходящими для пропонування відповідних методів, коли знати, що тип вашого об’єкта. Ви, мабуть, відчували це з деяким IDE в якийсь момент, потрапляючи на
.
і маючи методи / атрибути спливаючих, які не визначені для об'єкта.
Навіщо використовувати шашки статичного типу?
- Швидше знайдіть помилок : я вважаю, це зрозуміло.
- Чим більший ваш проект, тим більше вам потрібно : знову ж таки, має сенс. Статичні мови пропонують надійність і контроль, яких не вистачає динамічним мовам. Чим більша і складніша ваша програма, тим більше контролю і передбачуваності (з поведінкового аспекту) вам потрібно.
- Великі команди вже проводять статичний аналіз : я думаю, це підтверджує перші два моменти.
Як заключна примітка до цього невеликого вступу : Це необов'язкова функція, і, наскільки я розумію, вона була введена для того, щоб скористатись деякими перевагами статичного набору тексту.
Зазвичай вам не потрібно про це турбуватися, і, безумовно , не потрібно його використовувати (особливо у випадках, коли ви використовуєте Python як допоміжну мову сценарію). Це має бути корисним при розробці великих проектів, оскільки він пропонує дуже потрібну надійність, контроль та додаткові можливості налагодження .
Тип Підказка з mypy :
Для того, щоб зробити цю відповідь більш повною, я думаю, що невелика демонстрація була б підходящою. Я буду використовувати mypy
бібліотеку, яка надихнула підказки типу, коли вони представлені в PEP. Це в основному написано для тих, хто стикається з цим питанням і цікавиться, з чого почати.
Перш ніж зробити це, дозвольте ще раз зазначити наступне: PEP 484 нічого не застосовує; це просто встановити напрямок для анотацій функції та запропонувати вказівки щодо того, як можна / слід проводити перевірку типу. Ви можете коментувати свої функції та натякати на скільки завгодно речей; ваші сценарії все ще працюватимуть незалежно від наявності приміток, оскільки Python сам їх не використовує.
У будь-якому випадку, як зазначається в ПЕП, типи натяків, як правило, мають три форми:
- Анотації до функцій. ( PEP 3107 )
- Файли заготовки для вбудованих / користувальницьких модулів.
- Спеціальні
# type: type
коментарі, які доповнюють перші дві форми. (Див.: Що таке змінні анотації в Python 3.6? Для оновлення Python 3.6 для # type: type
коментарів)
Крім того, ви хочете використовувати підказки типу в поєднанні з новим typing
модулем, введеним в Py3.5
. У ньому багато (додаткові) АВС (абстрактні базові класи) визначені разом із допоміжними функціями та декораторами для використання у статичній перевірці. Більшість ABCs
у collections.abc
включені, але у Generic
формі, щоб дозволити передплату (шляхом визначення __getitem__()
методу).
Для всіх, хто зацікавлений у більш глибокому поясненні цього, цей текст mypy documentation
написаний дуже добре і містить безліч зразків коду, що демонструють / описують функціональність їх перевірки; це, безумовно, варто прочитати.
Анотації до функцій та спеціальні коментарі:
По-перше, цікаво спостерігати за деякою поведінкою, яку ми можемо отримати, використовуючи спеціальні коментарі. Спеціальні # type: type
коментарі можуть бути додані під час змінних призначень, щоб вказати тип об'єкта, якщо не можна безпосередньо зробити висновок. Прості завдання звичайно легко зробити, але інші, як-от списки (з урахуванням їх змісту), не можуть.
Примітка. Якщо ми хочемо використовувати будь-яку похідну Containers
та потрібно вказати вміст цього контейнера, ми повинні використовувати загальні типи з typing
модуля. Вони підтримують індексацію.
# generic List, supports indexing.
from typing import List
# In this case, the type is easily inferred as type: int.
i = 0
# Even though the type can be inferred as of type list
# there is no way to know the contents of this list.
# By using type: List[str] we indicate we want to use a list of strings.
a = [] # type: List[str]
# Appending an int to our list
# is statically not correct.
a.append(i)
# Appending a string is fine.
a.append("i")
print(a) # [0, 'i']
Якщо ми додамо ці команди до файлу та виконаємо їх за допомогою нашого інтерпретатора, все працює чудово і print(a)
просто надрукує вміст списку a
. Ці # type
коментарі були відкинуті, розглядаються як прості коментарі , які не мають ніякої додаткової смислового навантаження .
Запустивши це mypy
, з іншого боку, ми отримаємо таку відповідь:
(Python3)jimmi@jim: mypy typeHintsCode.py
typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"
Вказуючи на те, що список str
об'єктів не може містити int
, що, статично кажучи, є звуком. Це можна виправити, дотримуючись типу об'єктів, що a
додаються, та лише додавши їх, str
або змінивши тип вмісту, a
щоб вказати, що будь-яке значення є прийнятним (інтуїтивно виконується з List[Any]
після Any
імпортування з typing
).
Анотації до функцій додаються у формі param_name : type
після кожного параметра в підписі функції, а тип повернення задається за допомогою -> type
позначення перед двокрапкою функції, що закінчується; всі анотації зберігаються в __annotations__
атрибуті цієї функції у зручній словниковій формі. Використовуючи тривіальний приклад (який не потребує додаткових типів від typing
модуля):
def annotated(x: int, y: str) -> bool:
return x < y
annotated.__annotations__
Атрибут тепер має наступні значення:
{'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}
Якщо ми повна нобі, або ми знайомі з Py2.7
поняттями і, отже, не знаємо про що TypeError
ховається у порівнянні annotated
, ми можемо провести ще одну статичну перевірку, виявити помилку і врятувати нам певну неприємність:
(Python3)jimmi@jim: mypy typeHintsCode.py
typeFunction.py: note: In function "annotated":
typeFunction.py:2: error: Unsupported operand types for > ("str" and "int")
Крім усього іншого, виклик функції з недійсними аргументами також потрапить:
annotated(20, 20)
# mypy complains:
typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str"
Вони можуть бути розширені до будь-якого випадку використання, а помилки, що потрапляють, поширюються далі, ніж основні дзвінки та операції. Типи, на які ви можете перевірити, є дійсно гнучкими, і я лише надав невеликий пік свого потенціалу. Погляд в typing
модулі, PEPs або в mypy
документах дасть вам більш повне уявлення про пропоновані можливості.
Файли заглушки:
Файли Stub можна використовувати у двох різних не виключають один одного випадках:
- Вам потрібно набрати перевірку модуля, для якого ви не хочете безпосередньо змінювати підписи функцій
- Ви хочете писати модулі та перевіряти тип, але додатково хочете відокремлювати анотації від контенту.
Файли заглушок (з розширенням .pyi
) - це пояснений інтерфейс модуля, який ви створюєте / хочете використовувати. Вони містять підписи функцій, які ви хочете перевірити разом із тілом відкинутих функцій. Щоб відчути це, задавши набір з трьох випадкових функцій в модулі з назвою randfunc.py
:
def message(s):
print(s)
def alterContents(myIterable):
return [i for i in myIterable if i % 2 == 0]
def combine(messageFunc, itFunc):
messageFunc("Printing the Iterable")
a = alterContents(range(1, 20))
return set(a)
Ми можемо створити файл заглушки randfunc.pyi
, в якому ми можемо розмістити деякі обмеження, якщо бажаємо цього зробити. Мінус полягає в тому, що хтось, хто переглядає джерело без заглушки, насправді не отримає допомогу з коментарями, намагаючись зрозуміти, що має бути передано куди.
У будь-якому випадку структура файлу заглушки досить спрощена: додайте всі визначення функцій із порожніми тілами ( pass
заповненими) та надайте анотації на основі ваших вимог. Тут, припустимо, ми хочемо працювати лише з int
типами для наших контейнерів.
# Stub for randfucn.py
from typing import Iterable, List, Set, Callable
def message(s: str) -> None: pass
def alterContents(myIterable: Iterable[int])-> List[int]: pass
def combine(
messageFunc: Callable[[str], Any],
itFunc: Callable[[Iterable[int]], List[int]]
)-> Set[int]: pass
combine
Функція дає уявлення про те , чому ви можете захотіти використовувати анотації в іншому файлі, вони кілька разів не захаращувати код і не знижують читаність (великий ні-ні для Python). Можна, звичайно, використовувати псевдоніми типів, але це колись плутає більше, ніж це допомагає (тому використовуйте їх розумно).
Це має ознайомитись з основними поняттями підказки типу Python. Незважаючи на те, що використовується перевірка типу,
mypy
ви повинні поступово починати бачити більше їх спливаючих вікон, деякі внутрішньо в IDE ( PyCharm ,) та інші як стандартні модулі python. Я спробую додати додаткові шашки / пов'язані пакети у наступний список, коли і якщо я їх знайду (або якщо вони запропоновані).
Шашки, які я знаю :
- Міпі : як описано тут.
- PyType : Google використовує різні позначення від того, що я збираю, ймовірно, варто переглянути.
Пов'язані пакети / проекти :
typeshed
Проект насправді один з кращих місць , де ви можете подивитися , щоб побачити , як тип натякаючи може бути використаний в проекті самостійно. Давайте візьмемо в якості прикладу в __init__
dunders цього Counter
класу у відповідному .pyi
файлі:
class Counter(Dict[_T, int], Generic[_T]):
@overload
def __init__(self) -> None: ...
@overload
def __init__(self, Mapping: Mapping[_T, int]) -> None: ...
@overload
def __init__(self, iterable: Iterable[_T]) -> None: ...
Де _T = TypeVar('_T')
використовується для визначення загальних класів . Для Counter
класу ми бачимо, що він може або не приймати аргументів у своєму ініціалізаторі, отримувати сингл Mapping
від будь-якого типу до int
або приймати Iterable
будь-який тип.
Зауважте : одне, що я забув зазначити, це те, що typing
модуль був представлений на тимчасовій основі . Від PEP 411 :
Тимчасовий пакет може змінити свій API перед "переходом" у "стабільний" стан. З одного боку, цей стан забезпечує пакет переваг бути формально частиною дистрибуції Python. З іншого боку, основна команда розробників чітко заявляє, що не обіцянки щодо стабільності API пакета, які можуть змінитися до наступного випуску, не даються. Хоча це вважається малоймовірним результатом, такі пакети можуть бути навіть вилучені зі стандартної бібліотеки без періоду анулювання, якщо занепокоєння щодо їх API чи технічного обслуговування виявиться обґрунтованим.
Тож візьміть тут речі з дрібкою солі; Я сумніваюся, що це буде вилучено або змінено значними способами, але про це ніколи не можна знати.
** Ще одна тема, але цілком допустима в області підказки про тип PEP 526
: Синтаксис змінних анотацій - це зусилля замінити # type
коментарі, ввівши новий синтаксис, який дозволяє користувачам анотувати тип змінних у простих varname: type
операторах.
Див. Що таке змінні анотації в Python 3.6? , як було сказано раніше, для невеликого вступу до цих питань.