Misalkan saya telah menulis seorang dekorator yang melakukan sesuatu yang sangat umum. Misalnya, ini mungkin mengubah semua argumen ke tipe tertentu, melakukan logging, mengimplementasikan memoization, dll.
Berikut ini contohnya:
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
>>> funny_function("3", 4.0, z="5")
22
Semuanya baik-baik saja sejauh ini. Namun, ada satu masalah. Fungsi dekorasi tidak menyimpan dokumentasi dari fungsi aslinya:
>>> help(funny_function)
Help on function g in module __main__:
g(*args, **kwargs)
Untungnya, ada solusi:
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
Kali ini, nama fungsi dan dokumentasinya benar:
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(*args, **kwargs)
Computes x*y + 2*z
Tapi masih ada masalah: tanda tangan fungsi salah. Informasi "* args, ** kwargs" hampir tidak berguna.
Apa yang harus dilakukan? Saya dapat memikirkan dua solusi sederhana namun cacat:
1 - Sertakan tanda tangan yang benar di docstring:
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
Ini buruk karena duplikasi. Tanda tangan tetap tidak akan ditampilkan dengan benar dalam dokumentasi yang dibuat secara otomatis. Sangat mudah untuk memperbarui fungsi dan melupakan tentang mengubah docstring, atau salah ketik. [ Dan ya, saya menyadari fakta bahwa docstring sudah menduplikasi badan fungsi. Harap abaikan ini; funny_function hanyalah contoh acak. ]
2 - Tidak menggunakan dekorator, atau menggunakan dekorator tujuan khusus untuk setiap tanda tangan tertentu:
def funny_functions_decorator(f):
def g(x, y, z=3):
return f(int(x), int(y), z=int(z))
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
Ini berfungsi dengan baik untuk sekumpulan fungsi yang memiliki tanda tangan identik, tetapi tidak berguna secara umum. Seperti yang saya katakan di awal, saya ingin dapat menggunakan dekorator secara umum.
Saya mencari solusi yang sepenuhnya umum, dan otomatis.
Jadi pertanyaannya adalah: apakah ada cara untuk mengedit tanda tangan fungsi yang didekorasi setelah dibuat?
Jika tidak, dapatkah saya menulis dekorator yang mengekstrak tanda tangan fungsi dan menggunakan informasi itu daripada "* kwargs, ** kwargs" saat membuat fungsi yang didekorasi? Bagaimana cara mengekstrak informasi itu? Bagaimana saya harus membangun fungsi yang didekorasi - dengan exec?
Ada pendekatan lain?
inspect.Signature
ditambahkan untuk berurusan dengan fungsi dekorasi.Jawaban:
Pasang modul dekorator :
Sesuaikan definisi dari
args_as_ints()
:Python 3.4+
functools.wraps()
from stdlib mempertahankan tanda tangan sejak Python 3.4:functools.wraps()
tersedia setidaknya sejak Python 2.5 tetapi tidak mempertahankan tanda tangannya di sana:Perhatikan:
*args, **kwargs
alih-alihx, y, z=3
.sumber
functools.wraps()
sudah mempertahankan tanda tangan di Python 3.4+ (seperti yang dikatakan dalam jawaban). Apakah maksud Anda pengaturanwrapper.__signature__
membantu pada versi sebelumnya? (versi mana yang sudah Anda uji?)help()
menunjukkan tanda tangan yang benar pada Python 3.4. Menurut Anda mengapafunctools.wraps()
rusak dan bukan IPython?help()
menghasilkan hasil yang benar, pertanyaannya adalah perangkat lunak apa yang harus diperbaiki:functools.wraps()
atau IPython? Bagaimanapun, menetapkan secara manual__signature__
adalah solusi terbaik - ini bukan solusi jangka panjang.inspect.getfullargspec()
masih tidak mengembalikan tanda tangan yang benar untukfunctools.wraps
python 3.4 daninspect.signature()
sebagai gantinya Anda harus menggunakannya .Ini diselesaikan dengan pustaka standar Python
functools
danfunctools.wraps
fungsi khusus , yang dirancang untuk " memperbarui fungsi pembungkus agar terlihat seperti fungsi yang dibungkus ". Perilakunya tergantung pada versi Python, seperti yang ditunjukkan di bawah ini. Diterapkan pada contoh dari pertanyaan, kodenya akan terlihat seperti:Saat dieksekusi dengan Python 3, ini akan menghasilkan yang berikut:
Satu-satunya kekurangannya adalah bahwa di Python 2, ia tidak memperbarui daftar argumen fungsi. Saat dijalankan dengan Python 2, itu akan menghasilkan:
sumber
Ada modul
decorator
dekorator dengan dekorator yang dapat Anda gunakan:Kemudian tanda tangan dan bantuan metode tersebut dipertahankan:
EDIT: JF Sebastian menunjukkan bahwa saya tidak mengubah
args_as_ints
fungsi - sudah diperbaiki sekarang.sumber
Lihatlah modul dekorator - khususnya dekorator dekorator, yang memecahkan masalah ini.
sumber
Opsi kedua:
$ easy_install wrapt
wrapt mendapat bonus, pertahankan tanda tangan kelas.
sumber
Seperti yang dikomentari di atas dalam jawaban jfs ; jika Anda khawatir dengan tanda tangan dalam hal tampilan (
help
, daninspect.signature
), maka penggunaanfunctools.wraps
tidak masalah.Jika Anda khawatir dengan tanda tangan dalam hal perilaku (khususnya
TypeError
dalam kasus ketidakcocokan argumen),functools.wraps
jangan pertahankan. Anda lebih suka menggunakandecorator
untuk itu, atau generalisasi saya tentang mesin intinya, bernamamakefun
.Lihat juga postingan ini tentang
functools.wraps
.sumber
inspect.getfullargspec
tidak disimpan dengan meneleponfunctools.wraps
.