Kod yang ditunjukkan dalam artikel pendek ini diambil daripada projek sumber terbuka kecil saya yang direka oleh kontrak, yang menyediakan penghias bertaip. Penghias adalah konsep yang sangat berguna dan anda pasti akan menemui banyak tentangnya dalam talian. Ringkasnya, mereka membenarkan kod dilaksanakan setiap kali (sebelum dan selepas) fungsi yang dihias dipanggil. Dengan cara ini anda boleh mengubah suai parameter fungsi atau mengembalikan nilai, mengukur masa pelaksanaan, menambah pengelogan, melakukan pemeriksaan jenis masa pelaksanaan dan banyak lagi. Ambil perhatian bahawa penghias juga boleh ditulis untuk kelas, menyediakan pendekatan metaprogramming yang lain (seperti yang dilakukan dalam pakej attrs)
Dalam bentuk yang paling mudah, definisi penghias kelihatan seperti kod berikut:
def my_first_decorator(func): def wrapped(*args, **kwargs): # do something before result = func(*args, **kwargs) # do something after return result return wrapped @my_first_decorator def func(a): return a
Kod di atas, kerana apabila fungsi bersarang yang dibalut ditakrifkan, pembolehubah sekelilingnya boleh diakses dalam fungsi dan disimpan dalam memori, selagi fungsi itu digunakan di suatu tempat (Ini dipanggil penutupan dalam bahasa pengaturcaraan berfungsi).
Sangat mudah, tetapi ini mempunyai beberapa kelemahan. Masalah terbesar ialah fungsi yang diubah suai akan kehilangan nama fungsi sebelumnya (anda boleh melihat ini dengan inspect.signature), docstringnya, dan juga namanya Ini adalah masalah dengan alat dokumentasi kod sumber (seperti sphinx), Tetapi ia boleh diselesaikan dengan mudah menggunakan penghias functools.wraps dalam pustaka standard:
from functools import wraps from typing import Any, Callable, TypeVar, ParamSpec P = ParamSpec("P") # 需要python >= 3.10 R = TypeVar("R") def my_second_decorator(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapped(*args: Any, **kwargs: Any) -> R: # do something before result = func(*args, **kwargs) # do something after return result return wrapped @my_second_decorator def func2(a: int) -> int: """Does nothing""" return a print(func2.__name__) # 'func2' print(func2.__doc__) # 'Does nothing'
Dalam contoh ini, saya telah menambah anotasi jenis dan petunjuk jenis diperlukan untuk Python tambahan penting untuk dibuat. Kebolehbacaan yang lebih baik, penyiapan kod dalam IDE, dan kebolehselenggaraan asas kod yang lebih besar hanyalah beberapa contoh. Kod di atas sepatutnya merangkumi kebanyakan kes penggunaan, tetapi penghias tidak boleh diparameterkan. Pertimbangkan untuk menulis penghias yang merekodkan masa pelaksanaan fungsi, tetapi hanya jika ia melebihi beberapa saat tertentu. Nombor ini harus dikonfigurasikan secara individu untuk setiap fungsi yang dihias. Jika tidak dinyatakan, nilai lalai harus digunakan dan penghias hendaklah digunakan tanpa kurungan untuk memudahkan penggunaan:
@time(threshold=2) def func1(a): ... # No paranthesis when using default threshold @time def func2(b): ...
jika anda boleh menggunakannya dalam kurungan kes kedua , atau tidak memberikan nilai lalai untuk parameter sama sekali, maka resipi ini akan mencukupi:
from functools import wraps from typing import Any, Callable, TypeVar, ParamSpec P = ParamSpec("P") # 需要python >= 3.10 R = TypeVar("R") def my_third_decorator(threshold: int = 1) -> Callable[[Callable[P, R]], Callable[P, R]]: def decorator(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> R: # do something before you can use `threshold` result = func(*args, **kwargs) # do something after return result return wrapper return decorator @my_third_decorator(threshold=2) def func3a(a: int) -> None: ... # works @my_third_decorator() def func3b(a: int) -> None: ... # Does not work! @my_third_decorator def func3c(a: int) -> None: ...
Untuk menampung kes ketiga, terdapat pakej, iaitu pembalut dan penghias, yang sebenarnya boleh melakukan lebih daripada cuma Hanya tambah parameter pilihan. Walaupun kualitinya sangat tinggi, mereka memperkenalkan sedikit kerumitan tambahan. Menggunakan fungsi yang dihias bungkus, saya terus menghadapi masalah bersiri apabila menjalankan fungsi pada kelompok jauh. Setahu saya, kedua-duanya tidak ditaip sepenuhnya, jadi penyemak/linters jenis statik (seperti mypy) gagal dalam mod ketat.
Saya terpaksa menyelesaikan masalah ini apabila saya bekerja pada pakej saya sendiri dan memutuskan untuk menulis penyelesaian saya sendiri. Ia menjadi corak yang mudah digunakan semula tetapi sukar untuk ditukar menjadi perpustakaan.
Ia menggunakan penghias yang terlebih muatan daripada perpustakaan standard. Dengan cara ini, penghias yang sama boleh ditentukan untuk digunakan dengan penghias tanpa parameter kami. Selain daripada itu, ia adalah gabungan dua coretan di atas. Satu kelemahan pendekatan ini ialah semua parameter perlu diberikan sebagai hujah kata kunci (ini meningkatkan kebolehbacaan selepas semua)
from typing import Callable, TypeVar, ParamSpec from functools import partial, wraps P = ParamSpec("P") # requires python >= 3.10 R = TypeVar("R @overload def typed_decorator(func: Callable[P, R]) -> Callable[P, R]: ... @overload def typed_decorator(*, first: str = "x", second: bool = True) -> Callable[[Callable[P, R]], Callable[P, R]]: ... def typed_decorator( func: Optional[Callable[P, R]] = None, *, first: str = "x", second: bool = True ) -> Union[Callable[[Callable[P, R]], Callable[P, R]], Callable[P, R]]: """ Describe what the decorator is supposed to do! Parameters ---------- first : str, optional First argument, by default "x". This is a keyword-only argument! second : bool, optional Second argument, by default True. This is a keyword-only argument! """ def wrapper(func: Callable[P, R], *args: Any, **kw: Any) -> R: """The actual logic""" # Do something with first and second and produce a `result` of type `R` return result # Without arguments `func` is passed directly to the decorator if func is not None: if not callable(func): raise TypeError("Not a callable. Did you use a non-keyword argument?") return wraps(func)(partial(wrapper, func)) # With arguments, we need to return a function that accepts the function def decorator(func: Callable[P, R]) -> Callable[P, R]: return wraps(func)(partial(wrapper, func)) return decorator
Kemudian, kami boleh menggunakan Penghias kami tanpa parameter
@typed_decorator def spam(a: int) -> int: return a @typed_decorator(first = "y def eggs(a: int) -> int: return a
Corak ini pasti mempunyai sedikit overhed, tetapi faedahnya melebihi kos.
Teks asal: https://www.php.cn/link/d0f82e1046ccbd597c7f2a7bfba9e7dd
Atas ialah kandungan terperinci Penghias Python ditaip sepenuhnya dengan parameter. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!