Proponuję czytanie PEP 483 i PEP 484 i oglądania tej prezentacji Guido typu podpowiedzi.
W skrócie : Podpowiedzi typu są dosłownie tym, co oznaczają słowa, podpowiedzisz, jakiego typu obiektów używasz .
Ze względu na dynamiczny charakter Pythona szczególnie trudne jest wnioskowanie lub sprawdzanie typu używanego obiektu. Fakt ten utrudnia programistom zrozumienie, co dokładnie dzieje się w kodzie, którego nie napisali, a co najważniejsze, w przypadku narzędzi do sprawdzania typów znalezionych w wielu IDE [PyCharm, PyDev przychodzą na myśl], które są ograniczone ze względu na fakt, że nie mają żadnego wskaźnika tego, jakiego typu są obiekty. W rezultacie próbują wywnioskować ten typ z (jak wspomniano w prezentacji) około 50% wskaźnikiem sukcesu.
Aby wziąć dwa ważne slajdy z prezentacji Podpowiedzi typu:
Dlaczego warto pisać wskazówki?
- Pomaga w sprawdzaniu typów: Wskazując na typ obiektu, który ma być obiektem, sprawdzający typ może łatwo wykryć, na przykład, że przekazujesz obiekt o typie, którego się nie spodziewasz.
- Pomaga w dokumentacji: trzecia osoba przeglądająca twój kod będzie wiedziała, czego się spodziewać, a więc, jak go używać, nie otrzymując go
TypeErrors
.
- Pomaga IDE w opracowywaniu dokładniejszych i bardziej niezawodnych narzędzi: Środowiska programistyczne będą lepiej dostosowane do sugerowania odpowiednich metod, gdy będą wiedzieć, jakiego typu jest Twój obiekt. Prawdopodobnie doświadczyłeś tego z pewnym IDE w pewnym momencie, uderzając
.
i mając wyskakujące metody / atrybuty, które nie są zdefiniowane dla obiektu.
Dlaczego warto korzystać ze statycznych kontrolerów typu?
- Znajdź błędy wcześniej : Wierzę, że to oczywiste.
- Im większy projekt, tym bardziej go potrzebujesz : znowu, ma sens. Języki statyczne zapewniają niezawodność i kontrolę, której brakuje dynamicznym językom. Im większa i bardziej złożona aplikacja, tym większa kontrola i przewidywalność (z punktu widzenia zachowania), której potrzebujesz.
- Duże zespoły przeprowadzają już analizę statyczną : zgaduję, że weryfikuje to pierwsze dwa punkty.
Na zakończenie tego krótkiego wprowadzenia : Jest to funkcja opcjonalna i, o ile rozumiem, została wprowadzona w celu czerpania korzyści z pisania statycznego.
Na ogół nie musisz się tym martwić i zdecydowanie nie musisz go używać (szczególnie w przypadkach, w których używasz Pythona jako pomocniczego języka skryptowego). Powinien być pomocny przy tworzeniu dużych projektów, ponieważ zapewnia bardzo potrzebną niezawodność, kontrolę i dodatkowe możliwości debugowania .
Wpisz podpowiedź z mypy :
Aby uzupełnić tę odpowiedź, uważam, że odpowiednia byłaby mała demonstracja. Będę korzystać mypy
z biblioteki, która zainspirowała wskazówki typu, gdy są one przedstawione w PEP. Jest to napisane głównie dla każdego, kto wpadnie na to pytanie i zastanawia się, od czego zacząć.
Zanim to zrobię, powtórzę: PEP 484 niczego nie wymusza; po prostu określa kierunek adnotacji funkcji i proponuje wytyczne, w jaki sposób można / należy wykonać sprawdzanie typu. Możesz dodawać adnotacje do swoich funkcji i podpowiadać dowolną liczbę rzeczy; twoje skrypty będą nadal działać bez względu na obecność adnotacji, ponieważ sam Python ich nie używa.
W każdym razie, jak zauważono w PEP, typy podpowiedzi powinny zasadniczo przyjmować trzy formy:
- Adnotacje funkcyjne. ( PEP 3107 )
- Pliki pośredniczące dla wbudowanych / modułów użytkownika.
- Specjalne
# type: type
uwagi, które uzupełniają dwie pierwsze formy. (Zobacz: Co to są adnotacje zmienne w Pythonie 3.6? Do aktualizacji Pythona 3.6 w celu uzyskania # type: type
komentarzy)
Dodatkowo, będziesz chciał użyć wskazówek typu w połączeniu z nowym typing
modułem wprowadzonym w Py3.5
. W nim zdefiniowano wiele (dodatkowych) ABC (abstrakcyjnych klas podstawowych) wraz z funkcjami pomocniczymi i dekoratorami do użycia w sprawdzaniu statycznym. Większość ABCs
w collections.abc
są wliczone, ale w Generic
formie, w celu umożliwienia subskrypcji (poprzez określenie __getitem__()
metody).
Dla każdego zainteresowanego bardziej dogłębnym wyjaśnieniem, mypy documentation
jest napisany bardzo ładnie i ma wiele próbek kodu demonstrujących / opisujących funkcjonalność swojego kontrolera; zdecydowanie warto to przeczytać.
Adnotacje funkcyjne i specjalne komentarze:
Po pierwsze, interesujące jest obserwowanie niektórych zachowań, które możemy uzyskać, używając specjalnych komentarzy. # type: type
Podczas przypisywania zmiennych można dodawać specjalne komentarze, aby wskazać typ obiektu, jeśli nie można go bezpośrednio wywnioskować. Proste przypisania są na ogół łatwo wywnioskowane, ale inne, takie jak listy (w odniesieniu do ich zawartości), nie mogą.
Uwaga: jeśli chcemy użyć dowolnej pochodnej Containers
i musimy określić zawartość tego kontenera, musimy użyć ogólnych typów z typing
modułu. Te wspierają indeksowanie.
# 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']
Jeśli dodamy te polecenia do pliku i uruchomimy je za pomocą naszego interpretera, wszystko będzie działać dobrze i print(a)
po prostu wydrukuje zawartość listy a
. Te # type
uwagi zostały odrzucone, traktowany jako zwykły komentarze, które nie mają dodatkowe znaczenie semantyczne .
Z mypy
drugiej strony, uruchamiając to , otrzymujemy następujące odpowiedzi:
(Python3)jimmi@jim: mypy typeHintsCode.py
typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"
Wskazując, że lista str
obiektów nie może zawierać int
, co statycznie rzecz biorąc, jest dźwiękiem. Można to naprawić, przestrzegając typu a
i tylko dodając str
obiekty lub zmieniając typ zawartości, a
aby wskazać, że dowolna wartość jest akceptowalna (Intuicyjnie wykonywane z List[Any]
po Any
zaimportowaniu z typing
).
Adnotacje do funkcji są dodawane w postaci param_name : type
po każdym parametrze w sygnaturze funkcji, a typ zwracany jest określany za pomocą -> type
notacji przed dwukropkiem końcowym funkcji; wszystkie adnotacje są przechowywane w __annotations__
atrybucie dla tej funkcji w przydatnej formie słownika. Korzystając z trywialnego przykładu (który nie wymaga dodatkowych typów z typing
modułu):
def annotated(x: int, y: str) -> bool:
return x < y
annotated.__annotations__
Atrybut ma teraz następujące wartości:
{'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}
Jeśli jesteśmy kompletnym noobie lub znamy się na Py2.7
koncepcjach i w związku z tym nie jesteśmy świadomi TypeError
czającego się w porównaniu annotated
, możemy wykonać kolejną kontrolę statyczną, wykryć błąd i zaoszczędzić nam trochę kłopotów:
(Python3)jimmi@jim: mypy typeHintsCode.py
typeFunction.py: note: In function "annotated":
typeFunction.py:2: error: Unsupported operand types for > ("str" and "int")
Przechwytywanie funkcji z niepoprawnymi argumentami również zostanie złapane:
annotated(20, 20)
# mypy complains:
typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str"
Można je rozszerzyć na praktycznie każdy przypadek użycia, a wykryte błędy wykraczają poza zwykłe wywołania i operacje. Rodzaje, które możesz sprawdzić, są naprawdę elastyczne, a ja jedynie podałem mały potencjał jego możliwości. Spojrzenie w typing
module, dokumentach PEP lub mypy
dokumentach da Ci pełniejszy obraz oferowanych możliwości.
Pliki pośredniczące:
Pliki pośredniczące mogą być używane w dwóch różnych nie wykluczających się wzajemnie przypadkach:
- Musisz wpisać sprawdź moduł, dla którego nie chcesz bezpośrednio zmieniać sygnatur funkcji
- Chcesz pisać moduły i sprawdzać typ, ale dodatkowo chcesz oddzielić adnotacje od treści.
Pliki pośredniczące (z rozszerzeniem .pyi
) to opisany interfejs modułu, którego tworzysz / chcesz używać. Zawierają podpisy funkcji, które chcesz sprawdzić, z treścią odrzuconych funkcji. Aby to zrozumieć, biorąc pod uwagę zestaw trzech losowych funkcji w module o nazwie 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)
Możemy utworzyć plik pośredniczący randfunc.pyi
, w którym możemy wprowadzić pewne ograniczenia, jeśli chcemy to zrobić. Minusem jest to, że ktoś przeglądający źródło bez kodu pośredniczącego nie otrzyma tak naprawdę adnotacji, gdy będzie próbował zrozumieć, co powinno zostać przekazane.
W każdym razie struktura pliku pośredniczącego jest dość uproszczona: dodaj wszystkie definicje funkcji z pustymi obiektami ( pass
wypełnionymi) i dostarcz adnotacje na podstawie twoich wymagań. Załóżmy, że chcemy pracować tylko z int
typami dla naszych kontenerów.
# 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
Ta combine
funkcja wskazuje, dlaczego możesz chcieć używać adnotacji w innym pliku, czasem zaśmiecają kod i zmniejszają czytelność (duże nie-nie dla Pythona). Możesz oczywiście użyć aliasów typu, ale to kiedyś bardziej myli, niż pomaga (więc używaj ich mądrze).
Powinno to zaznajomić Cię z podstawowymi pojęciami typu Wskazówki w Pythonie. Mimo, że używano sprawdzania typów
mypy
, powinieneś stopniowo zacząć widzieć więcej z nich wyskakujących okienek, niektóre wewnętrznie w IDE ( PyCharm ,), a inne jako standardowe moduły python. Spróbuję dodać dodatkowe warcaby / powiązane pakiety z poniższej listy, kiedy je znajdę (lub jeśli będzie to sugerowane).
Warcaby, które znam :
- Mypy : jak opisano tutaj.
- PyType : Google używa innej notacji niż to, co zbieram, prawdopodobnie warte obejrzenia.
Powiązane pakiety / projekty :
- maszynopis: oficjalne repozytorium Pythona zawierające asortyment plików pośredniczących dla standardowej biblioteki.
typeshed
Projekt jest rzeczywiście jednym z najlepszych miejsc, gdzie można zobaczyć, jak wyglądają na typ podpowiedzi mogą być wykorzystane w projekcie własnych. Weź Chodźmy jako przykład na __init__
dunders z Counter
klasy w odpowiednim .pyi
pliku:
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: ...
Gdzie _T = TypeVar('_T')
służy do definiowania klas ogólnych . W przypadku Counter
klasy widzimy, że nie może ona przyjmować żadnych argumentów w swoim inicjalizatorze, uzyskać pojedynczego Mapping
z dowolnego typu int
lub brać Iterable
żadnego dowolnego typu.
Uwaga : zapomniałem wspomnieć o tym, że typing
moduł został wprowadzony tymczasowo . Od PEP 411 :
W pakiecie tymczasowym można zmodyfikować jego interfejs API przed „stopniowaniem” do stanu „stabilnego”. Z jednej strony ten stan zapewnia pakietowi korzyści wynikające z bycia formalnie częścią dystrybucji Pythona. Z drugiej strony główny zespół programistów wyraźnie stwierdza, że nie złożono żadnych obietnic dotyczących stabilności interfejsu API pakietu, które mogą ulec zmianie w następnej wersji. Chociaż jest to uważane za mało prawdopodobne, takie pakiety mogą nawet zostać usunięte ze standardowej biblioteki bez okresu amortyzacji, jeśli obawy dotyczące ich API lub konserwacji okażą się uzasadnione.
Więc weź rzeczy tutaj ze szczyptą soli; Wątpię, czy zostanie to usunięte lub zmienione w znaczący sposób, ale nigdy nie wiadomo.
** Zupełnie inny temat, ale poprawny w zakresie wskazówek dotyczących typów PEP 526
:: Składnia adnotacji zmiennych to próba zastąpienia # type
komentarzy przez wprowadzenie nowej składni, która pozwala użytkownikom opisywać typy zmiennych w prostych varname: type
instrukcjach.
Zobacz Co to są adnotacje o zmiennych w Pythonie 3.6? , jak wcześniej wspomniano, na krótkie wprowadzenie do nich.