Apa kegunaan yang baik untuk "Anotasi Fungsi" Python3

159

Anotasi Fungsi: PEP-3107

Saya berlari melintasi potongan kode yang menunjukkan penjelasan fungsi Python3. Konsepnya sederhana tapi saya tidak bisa memikirkan mengapa ini diimplementasikan dalam Python3 atau ada gunanya bagi mereka. Mungkin SO dapat mencerahkan saya?

Bagaimana itu bekerja:

def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):
    ... function body ...

Segala sesuatu yang mengikuti titik dua setelah argumen adalah 'anotasi', dan informasi yang mengikuti ->adalah anotasi untuk nilai pengembalian fungsi.

foo.func_annotations akan mengembalikan kamus:

{'a': 'x',
 'b': 11,
 'c': list,
 'return': 9}

Apa pentingnya memiliki ini tersedia?

agscala
sumber
6
@ SilentGhost: sayangnya, banyak tautan dengan kasus penggunaan aktual terputus. Apakah ada tempat di mana konten mungkin telah disimpan, atau hilang selamanya?
maks
16
tidak foo.func_annotations harus foo.__annotations__di python3?
zhangxaochen
2
Anotasi tidak memiliki arti khusus. Satu-satunya hal yang dilakukan Python adalah memasukkannya ke dalam kamus anotasi . Tindakan lain terserah Anda.
N Randhawa
apa def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):artinya
Ali SH

Jawaban:

90

Saya pikir ini sebenarnya hebat.

Berasal dari latar belakang akademis, saya dapat memberi tahu Anda bahwa anotasi telah membuktikan diri mereka tak ternilai karena memungkinkan penganalisa statis cerdas untuk bahasa seperti Java. Misalnya, Anda bisa mendefinisikan semantik seperti batasan negara, utas yang diizinkan untuk mengakses, batasan arsitektur, dll., Dan ada beberapa alat yang kemudian dapat membaca ini dan memprosesnya untuk memberikan jaminan di luar apa yang Anda dapatkan dari kompiler. Anda bahkan dapat menulis hal-hal yang memeriksa prasyarat / postkondisi.

Saya merasa sesuatu seperti ini sangat diperlukan dalam Python karena pengetikannya yang lebih lemah, tetapi sebenarnya tidak ada konstruksi yang membuat ini mudah dan bagian dari sintaksis resmi.

Ada kegunaan lain untuk anotasi yang tidak dapat dijamin. Saya bisa melihat bagaimana saya bisa menerapkan alat berbasis Java ke Python. Sebagai contoh, saya memiliki alat yang memungkinkan Anda menetapkan peringatan khusus untuk metode, dan memberi Anda indikasi ketika Anda memanggil mereka bahwa Anda harus membaca dokumentasinya (Misalnya, bayangkan Anda memiliki metode yang tidak boleh dipanggil dengan nilai negatif, tapi itu tidak intuitif dari namanya). Dengan anotasi, saya bisa secara teknis menulis sesuatu seperti ini untuk Python. Demikian pula, alat yang mengatur metode dalam kelas besar berdasarkan tag dapat ditulis jika ada sintaksis resmi.

Uri
sumber
34
ISTM ini adalah manfaat teoretis yang hanya dapat direalisasikan hanya jika pustaka standar dan modul pihak ketiga semuanya menggunakan anotasi fungsi dan menggunakannya dengan makna yang konsisten dan menggunakan sistem anotasi yang dipikirkan dengan baik. Sampai hari itu (yang tidak akan pernah datang), penggunaan utama penjelasan fungsi Python akan menjadi penggunaan satu kali yang dijelaskan dalam jawaban lain. Untuk saat ini, Anda dapat melupakan analisis statis pintar, jaminan kompiler, rantai alat berbasis java, dll.
Raymond Hettinger
4
Bahkan tanpa semuanya menggunakan anotasi fungsi, Anda masih dapat menggunakannya untuk analisis statis dalam kode yang memilikinya pada inputnya dan memanggil kode lain yang juga beranotasi. Dalam proyek atau basis kode yang lebih besar ini masih bisa menjadi badan kode yang sangat berguna untuk melakukan analisis statis berbasis anotasi.
gps
1
AFAICT, Anda dapat melakukan semua ini dengan dekorator, yang ada sebelum anotasi; oleh karena itu, saya masih tidak melihat manfaatnya. Saya memiliki pandangan yang sedikit berbeda tentang pertanyaan ini: stackoverflow.com/questions/13784713/…
allyourcode
9
Maju cepat ke 2015, python.org/dev/peps/pep-0484 dan mypy-lang.org mulai membuktikan bahwa semua penentang salah.
Mauricio Scheffer
1
Itu juga mengungkapkan pengaruh Python pada Swift bahkan lebih.
uchuugaka
92

Anotasi fungsi adalah apa yang Anda buat darinya.

Mereka dapat digunakan untuk dokumentasi:

def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second'):
     ...

Mereka dapat digunakan untuk memeriksa pra-kondisi:

def validate(func, locals):
    for var, test in func.__annotations__.items():
        value = locals[var]
        msg = 'Var: {0}\tValue: {1}\tTest: {2.__name__}'.format(var, value, test)
        assert test(value), msg


def is_int(x):
    return isinstance(x, int)

def between(lo, hi):
    def _between(x):
            return lo <= x <= hi
    return _between

def f(x: between(3, 10), y: is_int):
    validate(f, locals())
    print(x, y)


>>> f(0, 31.1)
Traceback (most recent call last):
   ... 
AssertionError: Var: y  Value: 31.1 Test: is_int

Juga lihat http://www.python.org/dev/peps/pep-0362/ untuk cara menerapkan pengecekan tipe.

Raymond Hettinger
sumber
18
Bagaimana ini lebih baik daripada dokumentasi untuk dokumentasi, atau pemeriksaan tipe eksplisit dalam fungsi? Ini sepertinya menyulitkan bahasa tanpa alasan.
endolith
10
@endolith Kita tentu bisa melakukannya tanpa penjelasan fungsi. Mereka hanya menyediakan cara standar untuk mengakses anotasi. Itu membuat mereka dapat diakses untuk membantu () dan untuk tips-alat dan membuatnya tersedia untuk introspeksi.
Raymond Hettinger
4
Daripada membagikan angka, Anda bisa membuat tipe Massdan Velocitysebagai gantinya.
sayap kanan
1
untuk sepenuhnya menunjukkan ini saya juga harus def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second') -> float:menunjukkan tipe pengembalian. Ini jawaban favorit saya di sini.
Tommy
Menggunakan kode Anda, apakah ada cara untuk memverifikasi returnanotasi? Tampaknya tidak muncul dilocals
user189728
46

Ini adalah jawaban yang terlambat, tetapi AFAICT, penggunaan anotasi fungsi terbaik saat ini adalah PEP-0484 dan MyPy .

Mypy adalah pemeriksa tipe statis opsional untuk Python. Anda bisa menambahkan petunjuk tipe ke program Python Anda menggunakan standar yang akan datang untuk anotasi tipe yang diperkenalkan di Python 3.5 beta 1 (PEP 484), dan menggunakan mypy untuk mengetikkan memeriksanya secara statis.

Digunakan seperti ini:

from typing import Iterator

def fib(n: int) -> Iterator[int]:
    a, b = 0, 1
    while a < n:
        yield a
        a, b = b, a + b
Dustin Wyatt
sumber
2
Contoh lainnya di sini Contoh Mypy dan di sini Bagaimana Anda Dapat Manfaat dari
El Ruso
Lihat juga pytype - penganalisa statis lain yang sedang dibangun dengan PEP-0484 dalam pikiran.
gps
Sayangnya tipe ini tidak diberlakukan. Jika saya mengetik list(fib('a'))dengan fungsi contoh Anda, Python 3.7 dengan senang hati menerima argumen dan mengeluh karena tidak ada cara untuk membandingkan string dan int.
Denis de Bernardy
@DenisdeBernardy Seperti PEP-484 menjelaskan Python hanya menyediakan anotasi jenis. Untuk menegakkan tipe Anda harus menggunakan mypy.
Dustin Wyatt
23

Hanya untuk menambahkan contoh spesifik dari penggunaan yang baik dari jawaban saya di sini , ditambah dengan dekorator mekanisme sederhana untuk metode multimetode dapat dilakukan.

# This is in the 'mm' module

registry = {}
import inspect

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)
        if function is None:
            raise TypeError("no match")
        return function(*args)
    def register(self, types, function):
        if types in self.typemap:
            raise TypeError("duplicate registration")
        self.typemap[types] = function

def multimethod(function):
    name = function.__name__
    mm = registry.get(name)
    if mm is None:
        mm = registry[name] = MultiMethod(name)
    spec = inspect.getfullargspec(function)
    types = tuple(spec.annotations[x] for x in spec.args)
    mm.register(types, function)
    return mm

dan contoh penggunaan:

from mm import multimethod

@multimethod
def foo(a: int):
    return "an int"

@multimethod
def foo(a: int, b: str):
    return "an int and a string"

if __name__ == '__main__':
    print("foo(1,'a') = {}".format(foo(1,'a')))
    print("foo(7) = {}".format(foo(7)))

Hal ini dapat dilakukan dengan menambahkan jenis ke dekorator seperti yang ditunjukkan oleh pos asli Guido , tetapi menganotasi parameter itu sendiri lebih baik karena menghindari kemungkinan kesalahan pencocokan parameter dan jenis.

Catatan : Dalam Python Anda dapat mengakses penjelasan sebagai function.__annotations__bukan function.func_annotationssebagai func_*gaya telah dihapus pada Python 3.

Muhammad Alkarouri
sumber
2
Aplikasi yang menarik, meskipun saya khawatir function = self.typemap.get(types)tidak akan berfungsi ketika subclass terlibat. Dalam hal ini Anda mungkin harus mengulang typemapmenggunakan isinnstance. Saya ingin tahu apakah @overloadmenangani ini dengan benar
Tobias Kienzler
Saya pikir ini rusak jika fungsinya memiliki tipe kembali
zenna
1
Itu __annotations__ adalah dictyang tidak memastikan urutan argumen, sehingga cuplikan ini terkadang gagal. Saya akan merekomendasikan mengubah types = tuple(...)ke spec = inspect.getfullargspec(function)kemudian types = tuple([spec.annotations[x] for x in spec.args]).
xoolive
Anda benar, @xoolive. Mengapa Anda tidak mengedit jawaban untuk menambahkan perbaikan Anda?
Muhammad Alkarouri
@ xoolive: Saya perhatikan. Terkadang editor menggunakan tangan yang kuat dalam mengelola hasil edit. Saya telah mengedit pertanyaan untuk menyertakan perbaikan Anda. Sebenarnya, saya sudah berdiskusi tentang hal ini, tetapi tidak ada cara untuk menolak perbaikan. Ngomong-ngomong, terima kasih atas bantuannya.
Muhammad Alkarouri
22

Uri telah memberikan jawaban yang tepat, jadi ini yang kurang serius: Jadi Anda bisa membuat dokumen Anda lebih singkat.

TUSUKAN
sumber
2
suka. +1. Namun, pada akhirnya, menulis dokumen masih merupakan cara nomor satu untuk membuat kode saya dapat dibaca, namun, jika Anda mengimplementasikan segala jenis pemeriksaan statis atau dinamis, senang memilikinya. Mungkin aku bisa menemukan kegunaannya.
Warren P
8
Saya tidak merekomendasikan menggunakan anotasi sebagai pengganti untuk bagian Args: atau baris @param atau serupa dalam dokumen Anda (format apa pun yang Anda pilih untuk digunakan). Sementara anotasi dokumentasi menjadi contoh yang bagus, ia menodai potensi kekuatan anotasi karena bisa menghalangi penggunaan lain yang lebih kuat. Juga, Anda tidak bisa menghilangkan anotasi saat runtime untuk mengurangi konsumsi memori (python -OO) yang Anda bisa dengan dokumen dan pernyataan pernyataan.
gps
2
@ Gps: Seperti yang saya katakan, itu adalah jawaban yang kurang serius.
JAB
2
Dalam semua keseriusan, ini adalah cara yang jauh lebih baik untuk mendokumentasikan jenis yang Anda harapkan, sambil tetap mematuhi DuckTyping.
Marc
1
@ Gps Saya tidak yakin konsumsi memori docstring adalah sesuatu yang perlu dikhawatirkan dalam 99,999% kasus.
Tommy
13

Pertama kali saya melihat anotasi, saya pikir "hebat! Akhirnya saya bisa ikut serta dalam pengecekan tipe!" Tentu saja, saya tidak memperhatikan bahwa anotasi sebenarnya tidak ditegakkan.

Jadi saya memutuskan untuk menulis dekorator fungsi sederhana untuk menegakkan mereka :

def ensure_annotations(f):
    from functools import wraps
    from inspect import getcallargs
    @wraps(f)
    def wrapper(*args, **kwargs):
        for arg, val in getcallargs(f, *args, **kwargs).items():
            if arg in f.__annotations__:
                templ = f.__annotations__[arg]
                msg = "Argument {arg} to {f} does not match annotation type {t}"
                Check(val).is_a(templ).or_raise(EnsureError, msg.format(arg=arg, f=f, t=templ))
        return_val = f(*args, **kwargs)
        if 'return' in f.__annotations__:
            templ = f.__annotations__['return']
            msg = "Return value of {f} does not match annotation type {t}"
            Check(return_val).is_a(templ).or_raise(EnsureError, msg.format(f=f, t=templ))
        return return_val
    return wrapper

@ensure_annotations
def f(x: int, y: float) -> float:
    return x+y

print(f(1, y=2.2))

>>> 3.2

print(f(1, y=2))

>>> ensure.EnsureError: Argument y to <function f at 0x109b7c710> does not match annotation type <class 'float'>

Saya menambahkannya ke perpustakaan Ensure .

penenun
sumber
Saya memiliki kekecewaan yang sama setelah saya keluar dengan percaya bahwa Python akhirnya memiliki tipe checking. Akhirnya harus melanjutkan implementasi pemeriksaan tipe buatan sendiri.
Hibou57
3

Sudah lama sejak pertanyaan ini diajukan tetapi contoh cuplikan yang diberikan dalam pertanyaan adalah (sebagaimana dinyatakan di sana juga) dari PEP 3107 dan pada akhir contoh PEP contoh Penggunaan juga diberikan yang mungkin menjawab pertanyaan dari sudut pandang PEP dari lihat;)

Berikut ini dikutip dari PEP3107

Gunakan Kasing

Dalam perjalanan membahas anotasi, sejumlah use case telah dimunculkan. Beberapa di antaranya disajikan di sini, dikelompokkan berdasarkan jenis informasi apa yang mereka sampaikan. Juga termasuk contoh produk dan paket yang ada yang dapat menggunakan anotasi.

  • Memberikan informasi pengetikan
    • Pengecekan tipe ([3], [4])
    • Biarkan IDE menunjukkan jenis yang diharapkan dan dikembalikan oleh fungsi ([17])
    • Fungsi overloading / fungsi generik ([22])
    • Jembatan berbahasa asing ([18], [19])
    • Adaptasi ([21], [20])
    • Predikat fungsi logika
    • Pemetaan permintaan basis data
    • Pengurutan parameter RPC ([23])
  • Informasi lainnya
    • Dokumentasi untuk parameter dan nilai kembali ([24])

Lihat PEP untuk informasi lebih lanjut tentang poin-poin spesifik (dan juga referensi mereka)

klaas
sumber
Saya benar-benar akan menghargai jika downvoters meninggalkan setidaknya komentar singkat apa yang menyebabkan downvote. Ini akan sangat membantu (setidaknya saya) banyak untuk meningkatkan.
klaas
2

Python 3.X (hanya) juga menggeneralisasikan definisi fungsi untuk memungkinkan argumen dan mengembalikan nilai yang akan dianotasi dengan nilai objek untuk digunakan dalam ekstensi .

Its META-data untuk menjelaskan, lebih eksplisit tentang nilai-nilai fungsi.

Anotasi dikodekan sebagai :valuesetelah nama argumen dan sebelum default, dan sebagai ->valuesetelah daftar argumen.

Mereka dikumpulkan menjadi __annotations__atribut fungsi, tetapi tidak diperlakukan sebagai istimewa oleh Python itu sendiri:

>>> def f(a:99, b:'spam'=None) -> float:
... print(a, b)
...
>>> f(88)
88 None
>>> f.__annotations__
{'a': 99, 'b': 'spam', 'return': <class 'float'>}

Sumber: Referensi Saku Python, Edisi Kelima

CONTOH:

The typeannotationsmodul menyediakan satu set alat untuk memeriksa jenis dan tipe inferensi kode Python. Ini juga menyediakan serangkaian jenis yang berguna untuk menjelaskan fungsi dan objek.

Alat-alat ini terutama dirancang untuk digunakan oleh analis statis seperti linter, librari pelengkap kode dan IDE. Selain itu, dekorator untuk melakukan pemeriksaan run-time disediakan. Pemeriksaan jenis run-time tidak selalu merupakan ide yang baik dengan Python, tetapi dalam beberapa kasus ini bisa sangat berguna.

https://github.com/ceronman/typeannotations

Bagaimana Mengetik Membantu Menulis Kode Lebih Baik

Mengetik dapat membantu Anda melakukan analisis kode statis untuk mengetahui kesalahan ketik sebelum mengirim kode ke produksi dan mencegah Anda dari beberapa bug yang jelas. Ada alat seperti mypy, yang dapat Anda tambahkan ke kotak alat Anda sebagai bagian dari siklus hidup perangkat lunak Anda. mypy dapat memeriksa tipe yang benar dengan menjalankan basis kode Anda sebagian atau sepenuhnya. mypy juga membantu Anda mendeteksi bug seperti memeriksa tipe None ketika nilainya dikembalikan dari suatu fungsi. Mengetik membantu membuat kode Anda lebih bersih. Alih-alih mendokumentasikan kode Anda menggunakan komentar, tempat Anda menentukan jenis dalam dokumen, Anda dapat menggunakan jenis tanpa biaya kinerja.

Bersihkan Python: Pengkodean Elegan dalam Python ISBN: ISBN-13 (pbk): 978-1-4842-4877-5

PEP 526 - Sintaks untuk Anotasi Variabel

https://www.python.org/dev/peps/pep-0526/

https://www.attrs.org/en/stable/types.html

Demz
sumber
@ BlackJack, "untuk digunakan dalam ekstensi" tidak jelas?
The Demz
Jelas, tetapi tidak menjawab pertanyaan IMHO. Ini seperti menjawab "Apa gunanya kelas dengan baik?" Dengan "Untuk digunakan dalam program." Jelas, benar, tetapi pihak yang meminta tidak benar-benar lebih bijaksana mengenai apa sih manfaat beton yang baik . Jawaban Anda adalah yang tidak bisa lebih umum, dengan contoh yang pada dasarnya sama dengan yang sudah ada dalam pertanyaan .
BlackJack
1

Terlepas dari semua penggunaan yang dijelaskan di sini, penggunaan anotasi yang diberlakukan dan, kemungkinan besar, akan diberlakukan untuk petunjuk jenis .

Ini saat ini tidak diberlakukan dengan cara apa pun tetapi, jika dilihat dari PEP 484, versi Python yang akan datang hanya akan memungkinkan jenis sebagai nilai untuk anotasi.

Mengutip Bagaimana dengan penggunaan anotasi yang ada? :

Kami benar-benar berharap bahwa petunjuk tipe pada akhirnya akan menjadi satu-satunya penggunaan untuk anotasi, tetapi ini akan membutuhkan diskusi tambahan dan periode penghentian setelah peluncuran awal modul pengetikan dengan Python 3.5. PEP saat ini akan memiliki status sementara (lihat PEP 411) hingga Python 3.6 dirilis. Skema yang paling cepat dibayangkan akan memperkenalkan penghentian diam-diam dari anotasi non-tip-petunjuk di 3.6, penghentian penuh dalam 3.7, dan menyatakan petunjuk tipe sebagai satu-satunya penggunaan anotasi yang diizinkan dalam Python 3.8.

Meskipun saya belum melihat adanya penghinaan diam-diam di 3,6, ini bisa sangat baik dinaikkan menjadi 3,7, sebagai gantinya.

Jadi, meskipun mungkin ada beberapa kasus penggunaan yang baik, yang terbaik adalah menyimpannya hanya untuk mengetik petunjuk jika Anda tidak ingin mengubah segalanya di masa depan di mana pembatasan ini ada.

Dimitris Fasarakis Hilliard
sumber
1

Sebagai sedikit jawaban yang tertunda, beberapa paket saya (marrow.script, WebCore, dll.) Menggunakan anotasi yang tersedia untuk menyatakan typecasting (yaitu mengubah nilai yang masuk dari web, mendeteksi argumen mana yang merupakan saklar boolean, dll.) untuk melakukan markup argumen tambahan.

Marrow Script membangun antarmuka baris perintah lengkap untuk fungsi dan kelas yang berubah-ubah dan memungkinkan untuk mendefinisikan dokumentasi, casting, dan nilai-nilai default yang diturunkan dari callback melalui anotasi, dengan dekorator untuk mendukung runtime yang lebih lama. Semua perpustakaan saya yang menggunakan anotasi mendukung formulir:

any_string  # documentation
any_callable  # typecast / callback, not called if defaulting
(any_callable, any_string)  # combination
AnnotationClass()  # package-specific rich annotation object
[AnnotationClass(), AnnotationClass(), …]  # cooperative annotation

Dukungan "Bare" untuk fungsi docstring atau typecasting memungkinkan pencampuran yang lebih mudah dengan perpustakaan lain yang sadar akan anotasi. (Yaitu memiliki pengontrol web menggunakan typecasting yang juga akan diekspos sebagai skrip baris perintah.)

Diedit untuk menambahkan: Saya juga mulai menggunakan paket TypeGuard menggunakan pernyataan pengembangan untuk validasi. Manfaat: ketika dijalankan dengan "optimisasi" diaktifkan ( -O/PYTHONOPTIMIZE env var) cek, yang mungkin mahal (misalnya rekursif) dihilangkan, dengan gagasan bahwa Anda telah menguji aplikasi Anda dalam pengembangan dengan benar sehingga cek seharusnya tidak perlu dalam produksi.

amcgregor
sumber
-2

Anotasi dapat digunakan untuk mudah memodulasi kode. Misalnya modul untuk program yang saya pelihara hanya bisa mendefinisikan metode seperti:

def run(param1: int):
    """
    Does things.

    :param param1: Needed for counting.
    """
    pass

dan kita bisa meminta pengguna untuk sesuatu bernama "param1" yang merupakan "Diperlukan untuk menghitung" dan harus menjadi "int". Pada akhirnya kita bahkan dapat mengubah string yang diberikan oleh pengguna ke tipe yang diinginkan untuk mendapatkan pengalaman bebas yang paling mudah.

Lihat kami fungsi objek metadata untuk kelas open source yang membantu dengan ini dan secara otomatis dapat mengambil nilai-nilai yang diperlukan dan mengkonversikannya ke setiap jenis yang diinginkan (karena anotasi adalah metode konversi). Bahkan IDE menunjukkan pelengkapan otomatis dengan benar dan menganggap bahwa tipe sesuai dengan anotasi - sangat cocok.

Lasse Schuirmann
sumber
-2

Jika Anda melihat daftar manfaat Cython, yang utama adalah kemampuan untuk memberi tahu kompiler jenis objek Python.

Saya bisa membayangkan masa depan di mana Cython (atau alat serupa yang mengkompilasi beberapa kode Python Anda) akan menggunakan sintaksis penjelasan untuk melakukan sihir mereka.

anggota dewan
sumber
The RPython Annotator adalah contoh dari pendekatan yang terasa cocok Pythonic; setelah membuat grafik aplikasi Anda, ia dapat menentukan tipe setiap variabel dan (untuk RPython) menerapkan keamanan tipe tunggal. OTOH membutuhkan "tinju" atau solusi / solusi lain untuk memungkinkan nilai kaya yang dinamis. Siapa saya yang memaksa multiplyfungsi saya hanya bekerja melawan bilangan bulat, padahal 'na' * 8 + ' batman!'sepenuhnya valid? ;)
amcgregor