Jakie są wskazówki dotyczące typów w Python 3.5?


250

Jedną z najczęściej omawianych funkcji w Pythonie 3.5 są wskazówki dotyczące typów .

Przykładem podpowiedzi typu jest mowa w tym artykule , a ten jeden , a jednocześnie z podaniem użyć podpowiedzi typu odpowiedzialnie. Czy ktoś może wyjaśnić więcej na ich temat oraz kiedy należy ich używać, a kiedy nie?


4
Powinieneś spojrzeć na PEP 484, który jest powiązany z oficjalnym dziennikiem zmian .
Stefan

1
@AvinashRaj: Dobra dyskusja na temat uwolnień dzieje się tutaj
Vaulstein

1
Szkoda, że ​​przypadek użycia C-API został całkowicie zignorowany przez ten PEP 484, szczególnie podpowiedzi typu dla Cython i Numba.
denfromufa

Odpowiedzi:


343

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?

  1. 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.
  2. 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.
  3. 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ć mypyz 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: typeuwagi, 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: typekomentarzy)

Dodatkowo, będziesz chciał użyć wskazówek typu w połączeniu z nowym typingmoduł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ść ABCsw collections.abcsą wliczone, ale w Genericformie, w celu umożliwienia subskrypcji (poprzez określenie __getitem__()metody).

Dla każdego zainteresowanego bardziej dogłębnym wyjaśnieniem, mypy documentationjest 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: typePodczas 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 Containersi musimy określić zawartość tego kontenera, musimy użyć ogólnych typów z typingmoduł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 # typeuwagi zostały odrzucone, traktowany jako zwykły komentarze, które nie mają dodatkowe znaczenie semantyczne .

Z mypydrugiej 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 strobiektów nie może zawierać int, co statycznie rzecz biorąc, jest dźwiękiem. Można to naprawić, przestrzegając typu ai tylko dodając strobiekty lub zmieniając typ zawartości, aaby wskazać, że dowolna wartość jest akceptowalna (Intuicyjnie wykonywane z List[Any]po Anyzaimportowaniu z typing).

Adnotacje do funkcji są dodawane w postaci param_name : typepo każdym parametrze w sygnaturze funkcji, a typ zwracany jest określany za pomocą -> typenotacji 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 typingmoduł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.7koncepcjach i w związku z tym nie jesteśmy świadomi TypeErrorczają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 typingmodule, dokumentach PEP lub mypydokumentach 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 ( passwypełnionymi) i dostarcz adnotacje na podstawie twoich wymagań. Załóżmy, że chcemy pracować tylko z inttypami 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 combinefunkcja 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.

typeshedProjekt 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 Counterklasy w odpowiednim .pyipliku:

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 Counterklasy widzimy, że nie może ona przyjmować żadnych argumentów w swoim inicjalizatorze, uzyskać pojedynczego Mappingz dowolnego typu int lub brać Iterableżadnego dowolnego typu.


Uwaga : zapomniałem wspomnieć o tym, że typingmoduł 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 # typekomentarzy przez wprowadzenie nowej składni, która pozwala użytkownikom opisywać typy zmiennych w prostych varname: typeinstrukcjach.

Zobacz Co to są adnotacje o zmiennych w Pythonie 3.6? , jak wcześniej wspomniano, na krótkie wprowadzenie do nich.


3
„Ze względu na bardzo dynamiczny charakter Pythona szczególnie trudne jest wnioskowanie lub sprawdzanie typu używanego obiektu”. Masz na myśli sprawdzanie statyczne, prawda?
bsam

53

Dodając do szczegółowej odpowiedzi Jima:

Sprawdź typingmoduł - ten moduł obsługuje wskazówki typu określone w PEP 484 .

Na przykład poniższa funkcja przyjmuje i zwraca wartości typu stri jest opisana w następujący sposób:

def greeting(name: str) -> str:
    return 'Hello ' + name

typingModuł obsługuje również:

  1. Wpisz aliasing .
  2. Wpisz podpowiedź dla funkcji oddzwaniania .
  3. Ogólne - Abstrakcyjne klasy podstawowe zostały rozszerzone o obsługę subskrypcji w celu oznaczenia oczekiwanych typów elementów kontenera.
  4. Typy ogólne zdefiniowane przez użytkownika - klasę zdefiniowaną przez użytkownika można zdefiniować jako klasę ogólną.
  5. Dowolny typ - każdy typ jest podtypem Dowolny.

26

Nowo wydany PyCharm 5 obsługuje podpowiedzi typu. W swoim wpisie na blogu na ten temat (patrz Podpowiedzi typu Python 3.5 w PyCharm 5 ) oferują świetne wyjaśnienie, jakie są podpowiedzi typu i nie są wraz z kilkoma przykładami i ilustracjami, jak używać ich w kodzie.

Dodatkowo jest obsługiwany w Pythonie 2.7, jak wyjaśniono w tym komentarzu :

PyCharm obsługuje moduł pisania z PyPI dla Python 2.7, Python 3.2-3.4. W przypadku wersji 2.7 należy umieszczać wskazówki dotyczące typów w plikach pośrednich * .pyi, ponieważ w Pythonie 3.0 dodano adnotacje funkcji .


0

Podpowiedź typu to najnowszy dodatek do dynamicznego języka, w którym przez dziesięciolecia ludzie przysięgali konwencje nazewnictwa tak proste jak węgierski (etykieta obiektu z pierwszą literą b = boolowska, c = znak, d = słownik, i = liczba całkowita, l = lista, n = numeryczna , s = ciąg, t = krotka) nie były potrzebne, zbyt kłopotliwe, ale teraz zdecydowaliśmy, że, och, czekaj ... używanie języka (type ()) do rozpoznawania obiektów jest zbyt wielkim problemem, a nasze fantazyjne IDE potrzebują pomocy w robieniu wszystkiego, co skomplikowane, a dynamicznie przypisywane wartości obiektów czynią je całkowicie bezużytecznymi, podczas gdy prosta konwencja nazewnictwa mogłaby rozwiązać wszystko dla każdego programisty na pierwszy rzut oka.


Szczerze mówiąc, to brzmi bardziej jak rant niż odpowiedź.
Dimitris Fasarakis Hilliard

-1

Podpowiedzi typów służą do konserwacji i nie są interpretowane przez Python. W poniższym kodzie wiersz def add(self, ic:int)nie powoduje błędu aż do następnego return...wiersza:

class C1:
    def __init__(self):
        self.idn = 1
    def add(self, ic: int):
        return self.idn + ic
    
c1 = C1()
c1.add(2)

c1.add(c1)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 5, in add
TypeError: unsupported operand type(s) for +: 'int' and 'C1'
 
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.