Apa petunjuk jenis di Python 3.5?

250

Salah satu fitur yang paling banyak dibicarakan di Python 3.5 adalah mengetikkan petunjuk .

Contoh petunjuk jenis disebutkan dalam artikel ini dan yang satu ini juga menyebutkan untuk menggunakan petunjuk jenis secara bertanggung jawab. Dapatkah seseorang menjelaskan lebih banyak tentang mereka dan kapan mereka harus digunakan dan kapan tidak?

Vaulstein
sumber
4
Anda harus melihat PEP 484 yang terhubung dari changelog resmi .
Stefan
1
@AvinashRaj: Diskusi yang bagus tentang rilis sedang berlangsung di sini
Vaulstein
1
Sangat disayangkan bahwa case use C-API benar-benar diabaikan oleh PEP 484 ini, terutama tipe petunjuk untuk Cython dan Numba.
denfromufa
2
Terkait erat: Apa anotasi variabel dalam Python 3.6? .
Dimitris Fasarakis Hilliard

Jawaban:

343

Saya sarankan membaca PEP 483 dan PEP 484 dan menonton presentasi ini oleh Guido pada Type Hinting.

Singkatnya : Mengisyaratkan mengetik secara literal artinya, Anda mengisyaratkan jenis objek yang Anda gunakan .

Karena sifat dinamis Python, menyimpulkan atau memeriksa jenis objek yang digunakan sangat sulit. Fakta ini menyulitkan pengembang untuk memahami apa yang sebenarnya terjadi dalam kode yang belum mereka tulis dan, yang paling penting, untuk alat pengecekan tipe yang ditemukan di banyak IDE [PyCharm, PyDev datang ke pikiran] yang terbatas karena fakta bahwa mereka tidak memiliki indikator apa pun jenis objeknya. Akibatnya mereka resor untuk mencoba menyimpulkan tipe dengan (seperti yang disebutkan dalam presentasi) sekitar 50% tingkat keberhasilan.


Untuk mengambil dua slide penting dari presentasi Type Hinting:

Mengapa Mengetik Petunjuk?

  1. Membantu Pemeriksa Jenis: Dengan memberi petunjuk jenis apa yang Anda inginkan objek menjadi pemeriksa jenis dapat dengan mudah mendeteksi jika, misalnya, Anda melewati objek dengan jenis yang tidak diharapkan.
  2. Membantu dengan dokumentasi: Orang ketiga yang melihat kode Anda akan tahu apa yang diharapkan di mana, ergo, cara menggunakannya tanpa mendapatkannya TypeErrors.
  3. Membantu IDE mengembangkan alat yang lebih akurat dan kuat: Lingkungan Pengembangan akan lebih cocok untuk menyarankan metode yang tepat ketika tahu apa jenis objek Anda. Anda mungkin pernah mengalami ini dengan beberapa IDE di beberapa titik, memukul .dan memiliki metode / atribut yang muncul yang tidak didefinisikan untuk objek.

Mengapa menggunakan Pemeriksa Tipe Statis?

  • Temukan bug lebih cepat : Ini jelas, saya percaya.
  • Semakin besar proyek Anda semakin Anda membutuhkannya : Sekali lagi, masuk akal. Bahasa statis menawarkan kekokohan dan kontrol yang kurang dimiliki bahasa dinamis. Aplikasi Anda yang lebih besar dan lebih kompleks menjadi lebih banyak kontrol dan prediktabilitas (dari aspek perilaku) yang Anda butuhkan.
  • Tim besar sudah menjalankan analisis statis : Saya kira ini memverifikasi dua poin pertama.

Sebagai catatan penutup untuk pengantar kecil ini : Ini adalah fitur opsional dan, dari apa yang saya mengerti, ini telah diperkenalkan untuk menuai beberapa manfaat dari pengetikan statis.

Anda umumnya tidak perlu khawatir tentang hal itu dan pasti tidak perlu menggunakannya (terutama dalam kasus di mana Anda menggunakan Python sebagai bahasa scripting tambahan). Seharusnya bermanfaat ketika mengembangkan proyek-proyek besar karena menawarkan ketahanan yang sangat dibutuhkan, kontrol dan kemampuan debugging tambahan .


Ketik Petunjuk dengan mypy :

Untuk membuat jawaban ini lebih lengkap, saya pikir sedikit demonstrasi akan cocok. Saya akan menggunakan mypy, perpustakaan yang menginspirasi Petunjuk Jenis saat mereka disajikan dalam PEP. Ini terutama ditulis untuk siapa saja yang menabrak pertanyaan ini dan bertanya-tanya harus mulai dari mana.

Sebelum saya melakukannya, izinkan saya mengulangi yang berikut: PEP 484 tidak menegakkan apa pun; itu hanya menetapkan arah untuk penjelasan fungsi dan mengusulkan pedoman untuk bagaimana pemeriksaan tipe dapat / harus dilakukan. Anda dapat membuat anotasi fungsi Anda dan memberi petunjuk sebanyak mungkin hal yang Anda inginkan; skrip Anda akan tetap berjalan terlepas dari keberadaan anotasi karena Python sendiri tidak menggunakannya.

Bagaimanapun, sebagaimana dicatat dalam PEP, tipe-tipe yang mengisyaratkan umumnya harus mengambil tiga bentuk:

  • Anotasi fungsi. ( PEP 3107 )
  • Stub file untuk modul built-in / pengguna.
  • # type: typeKomentar khusus yang melengkapi dua bentuk pertama. (Lihat: Apa anotasi variabel dalam Python 3.6? Untuk pembaruan Python 3.6 untuk # type: typekomentar)

Selain itu, Anda ingin menggunakan petunjuk jenis bersamaan dengan typingmodul baru yang diperkenalkan Py3.5. Di dalamnya, banyak (tambahan) ABC (Abstract Base Classes) didefinisikan bersama dengan fungsi pembantu dan dekorator untuk digunakan dalam pemeriksaan statis. Kebanyakan ABCsdi collections.abcdimasukkan tapi dalam Genericbentuk untuk memungkinkan berlangganan (dengan mendefinisikan __getitem__()metode).

Bagi siapa pun yang tertarik dengan penjelasan yang lebih mendalam tentang ini, mypy documentationini ditulis dengan sangat baik dan memiliki banyak sampel kode yang menunjukkan / menggambarkan fungsi pemeriksa mereka; itu pasti layak dibaca.

Anotasi fungsi dan komentar khusus:

Pertama, menarik untuk mengamati beberapa perilaku yang bisa kita dapatkan ketika menggunakan komentar khusus. # type: typeKomentar khusus dapat ditambahkan selama penugasan variabel untuk menunjukkan jenis objek jika seseorang tidak dapat disimpulkan secara langsung. Penugasan sederhana umumnya mudah disimpulkan tetapi yang lain, seperti daftar (yang berkaitan dengan isinya), tidak bisa.

Catatan: Jika kita ingin menggunakan turunan apa pun Containersdan perlu menentukan konten untuk wadah itu, kita harus menggunakan tipe generik dari typingmodul. Ini mendukung pengindeksan.

# 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']

Jika kita menambahkan perintah ini ke file dan menjalankannya dengan juru bahasa kita, semuanya berfungsi dengan baik dan print(a)hanya mencetak isi daftar a. The # typekomentar telah dibuang, diperlakukan sebagai komentar polos yang tidak memiliki makna semantik tambahan .

Dengan menjalankan ini dengan mypy, di sisi lain, kami mendapat respons berikut:

(Python3)jimmi@jim: mypy typeHintsCode.py
typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"

Menunjukkan bahwa daftar strobjek tidak dapat berisi int, yang, secara statis, adalah suara. Ini dapat diperbaiki dengan mematuhi jenis adan hanya menambahkan strobjek atau dengan mengubah jenis konten auntuk menunjukkan bahwa nilai apa pun dapat diterima (Dilakukan secara intuitif dengan List[Any]setelah Anydiimpor dari typing).

Anotasi fungsi ditambahkan dalam formulir param_name : typesetelah setiap parameter dalam tanda tangan fungsi Anda dan tipe pengembalian ditentukan menggunakan -> typenotasi sebelum tanda titik dua fungsi; semua anotasi disimpan dalam __annotations__atribut untuk fungsi itu dalam bentuk kamus yang praktis. Menggunakan contoh sepele (yang tidak memerlukan jenis tambahan dari typingmodul):

def annotated(x: int, y: str) -> bool:
    return x < y

The annotated.__annotations__atribut sekarang memiliki nilai-nilai berikut:

{'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}

Jika kita benar-benar noobie, atau kita terbiasa dengan Py2.7konsep dan akibatnya tidak mengetahui TypeErrormengintai dalam perbandingan annotated, kita dapat melakukan pemeriksaan statis lain, menangkap kesalahan dan menyelamatkan kita dari beberapa masalah:

(Python3)jimmi@jim: mypy typeHintsCode.py
typeFunction.py: note: In function "annotated":
typeFunction.py:2: error: Unsupported operand types for > ("str" and "int")

Antara lain, memanggil fungsi dengan argumen yang tidak valid juga akan ketahuan:

annotated(20, 20)

# mypy complains:
typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str"

Ini pada dasarnya dapat diperluas ke kasus penggunaan apa saja dan kesalahan yang ditangkap meluas lebih jauh dari panggilan dasar dan operasi. Jenis-jenis yang dapat Anda periksa benar-benar fleksibel dan saya hanya memberi sedikit kemungkinan. Melihat typingmodul, PEPs atau mypydokumen akan memberi Anda ide yang lebih komprehensif dari kemampuan yang ditawarkan.

File rintisan:

File rintisan dapat digunakan dalam dua kasus berbeda yang tidak saling eksklusif:

  • Anda perlu mengetikkan centang pada modul yang Anda tidak ingin langsung mengubah tanda tangan fungsi
  • Anda ingin menulis modul dan memeriksa jenis tetapi juga ingin memisahkan anotasi dari konten.

File rintisan apa (dengan ekstensi .pyi) adalah antarmuka beranotasi dari modul yang Anda buat / ingin gunakan. Mereka berisi tanda tangan dari fungsi yang ingin Anda ketik-periksa dengan tubuh fungsi dibuang. Untuk merasakan hal ini, diberikan satu set tiga fungsi acak dalam sebuah modul bernama 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)

Kita dapat membuat file rintisan randfunc.pyi, di mana kita dapat menempatkan beberapa batasan jika kita ingin melakukannya. Kelemahannya adalah seseorang yang melihat sumber tanpa rintisan tidak akan benar-benar mendapatkan bantuan anotasi ketika mencoba memahami apa yang seharusnya diteruskan ke mana.

Bagaimanapun, struktur file rintisan cukup sederhana: Tambahkan semua definisi fungsi dengan badan kosong ( passdiisi) dan berikan penjelasan berdasarkan kebutuhan Anda. Di sini, mari kita asumsikan kita hanya ingin bekerja dengan inttipe untuk Kontainer kita.

# 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

The combineFungsi memberikan indikasi mengapa Anda mungkin ingin menggunakan anotasi dalam file yang berbeda, mereka beberapa kali mengacaukan kode dan mengurangi keterbacaan (besar tidak-tidak untuk Python). Anda tentu saja bisa menggunakan alias ketik tetapi kadang-kadang membingungkan lebih dari itu membantu (jadi gunakan dengan bijak).


Ini akan membuat Anda terbiasa dengan konsep dasar Tip Petunjuk dalam Python. Meskipun pemeriksa tipe yang digunakan adalah mypyAnda harus secara bertahap mulai melihat lebih banyak dari mereka yang muncul, beberapa secara internal di IDE ( PyCharm ,) dan lainnya sebagai modul python standar. Saya akan mencoba dan menambahkan checker tambahan / paket terkait dalam daftar berikut kapan dan jika saya menemukannya (atau jika disarankan).

Dam yang saya tahu :

  • Mypy : seperti yang dijelaskan di sini.
  • PyType : Oleh Google, menggunakan notasi berbeda dari yang saya kumpulkan, mungkin layak untuk dilihat.

Paket / Proyek Terkait :

  • diketik: Repo Python resmi menampung bermacam-macam file rintisan untuk pustaka standar.

The typeshedproyek sebenarnya adalah salah satu tempat terbaik yang Anda dapat melihat untuk melihat bagaimana jenis mengisyaratkan mungkin digunakan dalam proyek Anda sendiri. Mari kita mengambil sebagai contoh yang __init__Dunders dari Counterkelas di yang sesuai .pyiberkas:

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: ...

Di mana _T = TypeVar('_T')digunakan untuk mendefinisikan kelas generik . Untuk Counterkelas kita dapat melihat bahwa ia tidak dapat mengambil argumen di penginisialisasi, mendapatkan satu Mappingdari jenis apa pun ke int atau mengambil Iterablejenis apa pun.


Perhatikan : Satu hal yang saya lupa sebutkan adalah bahwa typingmodul telah diperkenalkan secara sementara . Dari PEP 411 :

Paket sementara mungkin API-nya dimodifikasi sebelum "lulus" menjadi "stabil". Di satu sisi, keadaan ini memberikan paket dengan manfaat menjadi bagian resmi dari distribusi Python. Di sisi lain, tim pengembangan inti secara eksplisit menyatakan bahwa tidak ada janji yang dibuat berkaitan dengan stabilitas API paket, yang dapat berubah untuk rilis berikutnya. Meskipun dianggap sebagai hasil yang tidak mungkin, paket-paket seperti itu bahkan dapat dihapus dari perpustakaan standar tanpa periode penghentian jika kekhawatiran mengenai API atau pemeliharaannya terbukti beralasan.

Jadi bawalah semuanya ke sini dengan sejumput garam; Saya ragu apakah itu akan dihapus atau diubah secara signifikan tetapi orang tidak akan pernah tahu.


** Topik lain sama sekali tetapi valid dalam lingkup type-hints:: PEP 526Sintaks untuk Anotasi Variabel adalah upaya untuk mengganti # typekomentar dengan memperkenalkan sintaks baru yang memungkinkan pengguna untuk membubuhi keterangan jenis variabel dalam varname: typepernyataan sederhana .

Lihat Apa anotasi variabel dalam Python 3.6? , seperti yang disebutkan sebelumnya, untuk intro kecil tentang ini.

Dimitris Fasarakis Hilliard
sumber
3
"Karena sifat Python yang sangat dinamis, menyimpulkan atau memeriksa jenis objek yang digunakan sangat sulit." Anda mengacu pada pemeriksaan statis, bukan?
bsam
53

Menambahkan ke jawaban rumit Jim:

Periksa typingmodul - modul ini mendukung petunjuk jenis seperti yang ditentukan oleh PEP 484 .

Misalnya, fungsi di bawah ini mengambil dan mengembalikan nilai tipe strdan dijelaskan sebagai berikut:

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

The typingModul juga mendukung:

  1. Ketik aliasing .
  2. Ketik mengisyaratkan untuk fungsi panggilan balik .
  3. Generik - Kelas dasar abstrak telah diperluas untuk mendukung langganan untuk menunjukkan tipe yang diharapkan untuk elemen kontainer.
  4. Jenis generik yang ditentukan pengguna - Kelas yang ditentukan pengguna dapat didefinisikan sebagai kelas generik.
  5. Jenis apa pun - Setiap jenis adalah subtipe dari Any.
Ani Menon
sumber
26

PyCharm 5 yang baru dirilis mendukung isyarat tipe. Dalam posting blog mereka tentang hal itu (lihat petunjuk jenis Python 3.5 di PyCharm 5 ) mereka menawarkan penjelasan yang bagus tentang jenis petunjuk apa dan tidak disertai dengan beberapa contoh dan ilustrasi tentang cara menggunakannya dalam kode Anda.

Selain itu, didukung dalam Python 2.7, seperti yang dijelaskan dalam komentar ini :

PyCharm mendukung modul pengetikan dari PyPI untuk Python 2.7, Python 3.2-3.4. Untuk 2,7 Anda harus memasukkan petunjuk jenis dalam file stub * .pyi karena anotasi fungsi ditambahkan dalam Python 3.0 .

tsvenson
sumber
0

Petunjuk jenis adalah tambahan baru untuk bahasa dinamis di mana selama beberapa dekade orang bersumpah konvensi penamaan semudah bahasa Hongaria (label objek dengan huruf pertama b = boolian, c = karakter, d = kamus, i = integer, l = daftar, n = numerik , s = string, t = tuple) tidak diperlukan, terlalu rumit, tetapi sekarang telah memutuskan bahwa, oh tunggu ... terlalu banyak kesulitan untuk menggunakan bahasa (tipe ()) untuk mengenali objek, dan IDE mewah kami butuh bantuan melakukan apa pun yang rumit, dan nilai-nilai objek yang ditetapkan secara dinamis membuat mereka benar-benar tidak berguna bagaimanapun, sedangkan konvensi penamaan sederhana bisa menyelesaikan semua itu, untuk setiap pengembang, hanya dengan pandangan sekilas.

Noah F. SanTsorvutz
sumber
Sejujurnya, ini terdengar lebih seperti kata-kata kasar daripada jawaban.
Dimitris Fasarakis Hilliard
-1

Tipe-petunjuk adalah untuk pemeliharaan dan jangan ditafsirkan oleh Python. Dalam kode di bawah ini, baris def add(self, ic:int)tersebut tidak menghasilkan kesalahan sampai return...baris berikutnya :

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'
 
Leon Chang
sumber