Saat Anda menggunakan dekorator, Anda mengganti satu fungsi dengan yang lain. Dengan kata lain, jika Anda memiliki dekorator
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
lalu ketika Anda mengatakannya
@logged
def f(x):
"""does some math"""
return x + x * x
persis sama dengan mengatakan
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
dan fungsi Anda f
diganti dengan fungsi with_logging
. Sayangnya, ini berarti jika Anda mengatakannya
print(f.__name__)
itu akan dicetak with_logging
karena itulah nama fungsi baru Anda. Bahkan, jika Anda melihat docstring untuk f
, itu akan kosong karena with_logging
tidak memiliki docstring, sehingga docstring yang Anda tulis tidak akan ada lagi. Juga, jika Anda melihat hasil pydoc untuk fungsi itu, itu tidak akan terdaftar sebagai mengambil satu argumen x
; alih-alih itu akan terdaftar sebagai pengambilan *args
dan **kwargs
karena itulah yang mengambil with_logging.
Jika menggunakan dekorator selalu berarti kehilangan informasi ini tentang suatu fungsi, itu akan menjadi masalah serius. Itu sebabnya kami punya functools.wraps
. Ini mengambil fungsi yang digunakan dalam dekorator dan menambahkan fungsi menyalin di atas nama fungsi, docstring, daftar argumen, dll. Dan karena wraps
itu sendiri adalah dekorator, kode berikut melakukan hal yang benar:
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print(f.__name__) # prints 'f'
print(f.__doc__) # prints 'does some math'
functools.wraps
pekerjaan ini, bukankah itu hanya menjadi bagian dari pola dekorator? kapan Anda tidak ingin menggunakan @wraps?@wraps
untuk melakukan berbagai jenis modifikasi atau anotasi pada nilai yang disalin. Pada dasarnya, ini merupakan perpanjangan dari filosofi Python yang eksplisit lebih baik daripada kasus implisit dan khusus tidak cukup istimewa untuk melanggar aturan. (Kode ini jauh lebih sederhana dan bahasanya lebih mudah dimengerti jika@wraps
harus disediakan secara manual, daripada menggunakan semacam mekanisme penyisih khusus.)Saya sangat sering menggunakan kelas, bukan fungsi, untuk dekorator saya. Saya mengalami beberapa masalah dengan ini karena suatu objek tidak akan memiliki semua atribut yang sama yang diharapkan dari suatu fungsi. Misalnya, suatu objek tidak akan memiliki atribut
__name__
. Saya punya masalah khusus dengan ini yang cukup sulit dilacak di mana Django melaporkan kesalahan "objek tidak memiliki atribut '__name__
'". Sayangnya, untuk dekorator gaya kelas, saya tidak percaya bahwa @wrap akan melakukan pekerjaan. Saya malah membuat kelas dekorator dasar seperti:Kelas ini proksi semua panggilan atribut ke fungsi yang sedang didekorasi. Jadi, Anda sekarang dapat membuat dekorator sederhana yang memeriksa bahwa 2 argumen ditentukan seperti:
sumber
@wraps
,@wraps
hanyalah fungsi kenyamanan untukfunctools.update_wrapper()
. Dalam hal dekorator kelas, Anda dapat meneleponupdate_wrapper()
langsung dari__init__()
metode Anda . Jadi, Anda tidak perlu membuatDecBase
sama sekali, Anda hanya dapat mencakup pada__init__()
dariprocess_login
baris:update_wrapper(self, func)
. Itu saja.Pada python 3.5+:
Adalah alias untuk
g = functools.update_wrapper(g, f)
. Ia melakukan tiga hal:__module__
,__name__
,__qualname__
,__doc__
, dan__annotations__
atributf
padag
. Daftar default ini adaWRAPPER_ASSIGNMENTS
, Anda dapat melihatnya di sumber functools .__dict__
darig
semua elemen darif.__dict__
. (lihatWRAPPER_UPDATES
di sumber)__wrapped__=f
atribut barug
Konsekuensinya adalah yang
g
muncul memiliki nama yang sama, docstring, nama modul, dan tanda tangan daripadaf
. Satu-satunya masalah adalah bahwa mengenai tanda tangan ini sebenarnya tidak benar: hanya sajainspect.signature
mengikuti rantai pembungkus secara default. Anda dapat memeriksanya dengan menggunakaninspect.signature(g, follow_wrapped=False)
seperti yang dijelaskan dalam dokumen . Ini memiliki konsekuensi yang menjengkelkan:Signature.bind()
.Sekarang ada sedikit kebingungan antara
functools.wraps
dan dekorator, karena kasus penggunaan yang sangat sering untuk mengembangkan dekorator adalah untuk membungkus fungsi. Tetapi keduanya adalah konsep yang sepenuhnya independen. Jika Anda tertarik untuk memahami perbedaannya, saya menerapkan perpustakaan pembantu untuk keduanya: dekopatch untuk menulis dekorator dengan mudah, dan makefun untuk menyediakan pengganti yang mempertahankan tanda tangan@wraps
. Perhatikan bahwamakefun
mengandalkan trik terbukti yang sama daridecorator
perpustakaan terkenal .sumber
ini adalah kode sumber tentang wraps:
sumber
Prasyarat: Anda harus tahu cara menggunakan dekorator dan khusus menggunakan pembungkus. Komentar ini menjelaskannya dengan cukup jelas atau tautan ini juga menjelaskannya dengan cukup baik.
Setiap kali kita menggunakan Untuk misalnya: @wraps diikuti oleh fungsi pembungkus kita sendiri. Sesuai dengan rincian yang diberikan dalam tautan ini , dikatakan demikian
Jadi @wraps dekorator benar-benar memberikan panggilan ke functools.partial (func [, * args] [, ** kata kunci]).
Definisi functools.partial () mengatakan itu
Yang membawa saya pada kesimpulan bahwa, @wraps memberikan panggilan ke parsial () dan melewati fungsi wrapper Anda sebagai parameter untuk itu. Parsial () pada akhirnya mengembalikan versi yang disederhanakan yaitu objek dari apa yang ada di dalam fungsi wrapper dan bukan fungsi wrapper itu sendiri.
sumber
Singkatnya, functools.wraps hanyalah fungsi biasa. Mari kita perhatikan contoh resmi ini . Dengan bantuan kode sumber , kita dapat melihat detail lebih lanjut tentang implementasi dan langkah-langkah yang berjalan sebagai berikut:
Memeriksa implementasi __call__ , kita melihat bahwa setelah langkah ini, (sisi kiri) pembungkus menjadi objek yang dihasilkan oleh self.func (* self.args, * args, ** newkeywords) Memeriksa pembuatan O1 di __new__ , kami tahu self.func adalah fungsi update_wrapper . Ia menggunakan parameter * args , pembungkus sisi kanan , sebagai parameter pertama. Memeriksa langkah terakhir dari update_wrapper , orang dapat melihat pembungkus sisi kanan dikembalikan, dengan beberapa atribut diubah sesuai kebutuhan.
sumber