Yanıtlar:
Bir dekoratör kullandığınızda, bir işlevi başka bir işlevle değiştirirsiniz. Başka bir deyişle, bir dekoratörünüz varsa
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
o zaman dediğin zaman
@logged
def f(x):
"""does some math"""
return x + x * x
tam olarak söylemekle aynı
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
ve işleviniz işlev f
ile değiştirilir with_logging
. Ne yazık ki, bu demek ki
print(f.__name__)
yazdırılacaktır with_logging
çünkü bu yeni işlevinizin adıdır. Aslında, eğer doktora bakarsanız, f
boş olacaktır çünkü with_logging
doktora olmadığı için yazdığınız doktora artık orada olmayacaktır. Ayrıca, bu işlev için pydoc sonucuna bakarsanız, tek bir argüman almak olarak listelenmez x
; bunun yerine alma olarak listelenir *args
ve **kwargs
çünkü with_logging bunu alır.
Bir dekoratör kullanmak her zaman bir işlevle ilgili bu bilgileri kaybetmek anlamına geliyorsa, bu ciddi bir sorun olacaktır. Bu yüzden var functools.wraps
. Bu dekoratör kullanılan bir işlevi alır ve vb, fonksiyon adının üzerine kopyalama docstring'ini argümanlar liste işlevselliğini ekler Ve bu yana wraps
kendisini bir dekoratör olan, aşağıdaki kod doğru şeyi yapar:
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
bu iş için ihtiyacımız var , sadece dekoratör deseninin bir parçası olmamalı? ne zaman olacağını değil @wraps kullanmak istiyor?
@wraps
Kopyalanan değerler üzerinde değişik tipte modifikasyon veya ek açıklama yapmak için kendi sürümlerini yapan bazı dekoratörler yazdım . Temel olarak, açık bir şekilde örtükten daha iyi olan ve özel durumların kuralları ihlal edecek kadar özel olmadığı Python felsefesinin bir uzantısıdır. (Kod çok daha basittir ve bir @wraps
tür özel devre dışı bırakma mekanizması kullanmak yerine manuel olarak sağlanması gerekip gerekmediğini anlamak daha kolaydır .)
Dekoratörlerim için çok işlevler yerine sınıflar kullanıyorum. Bir nesne bir işlevden beklenen tüm aynı özniteliklere sahip olmayacağı için bu konuda bazı sorunlar yaşıyordum. Örneğin, bir nesnenin özniteliği olmaz __name__
. Django "nesne hiçbir özniteliği ' __name__
'" hata bildiriyor nerede izlemek oldukça zor bu belirli bir sorun vardı . Ne yazık ki, sınıf tarzı dekoratörler için, @wrap'ın işi yapacağına inanmıyorum. Bunun yerine böyle bir temel dekoratör sınıfı oluşturduk:
class DecBase(object):
func = None
def __init__(self, func):
self.__func = func
def __getattribute__(self, name):
if name == "func":
return super(DecBase, self).__getattribute__(name)
return self.func.__getattribute__(name)
def __setattr__(self, name, value):
if name == "func":
return super(DecBase, self).__setattr__(name, value)
return self.func.__setattr__(name, value)
Bu sınıf, dekore edilen işleve yapılan tüm öznitelikleri temsil eder. Böylece, 2 bağımsız değişkenin şu şekilde belirtildiğini kontrol eden basit bir dekoratör oluşturabilirsiniz:
class process_login(DecBase):
def __call__(self, *args):
if len(args) != 2:
raise Exception("You can only specify two arguments")
return self.func(*args)
@wraps
dediği gibi @wraps
, sadece bir kolaylık fonksiyonudur functools.update_wrapper()
. Sınıf dekoratörü durumunda, update_wrapper()
doğrudan __init__()
yönteminizden arayabilirsiniz . Yani, oluşturmak gerekmez DecBase
hiç, sadece üzerinde içerebilir __init__()
ait process_login
hat: update_wrapper(self, func)
. Bu kadar.
Python 3.5+ itibariyle:
@functools.wraps(f)
def g():
pass
İçin bir takma addır g = functools.update_wrapper(g, f)
. Tam olarak üç şey yapar:
__module__
, __name__
, __qualname__
, __doc__
, ve __annotations__
sıfatları f
üzerinde g
. Bu varsayılan liste içerisindedir WRAPPER_ASSIGNMENTS
, listeyi functools kaynağında görebilirsiniz .__dict__
ait g
tüm unsurları ile f.__dict__
. ( WRAPPER_UPDATES
kaynağa bakın)__wrapped__=f
üzerinde yeni bir özellik ayarlıyorg
Sonuç g
olarak, aynı ada, öğretiye, modül adına ve imzasına sahip olarak görünür f
. Tek sorun, imza ile ilgili olarak bunun gerçek olmadığıdır: sadece inspect.signature
varsayılan olarak sarıcı zincirleri takip eder. Dokümandainspect.signature(g, follow_wrapped=False)
açıklandığı gibi kullanarak kontrol edebilirsiniz . Bunun can sıkıcı sonuçları var:
Signature.bind()
.Şimdi functools.wraps
ve dekoratörler arasında biraz karışıklık var , çünkü dekoratörleri geliştirmek için çok sık kullanılan bir durum fonksiyonları sarmaktır. Ancak her ikisi de tamamen bağımsız kavramlardır. Farkı anlamak istiyorsanız, her ikisi için de yardımcı kütüphaneler uyguladım: dekoratörleri kolayca yazmak için decopatch ve imza koruyucu bir değiştirme sağlamak için makefun@wraps
. Not makefun
ünlü ile aynı kanıtlanmış hile dayanır decorator
kütüphanede.
Bu, tamamlamalarla ilgili kaynak koddur:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
Önkoşul: Dekoratörleri nasıl kullanacağınızı ve özellikle sargılarla bilmelisiniz. Bu yorum biraz açıklıyor veya bu bağlantı da oldukça iyi açıklıyor.
Ne zaman kullanırsak Örneğin: @wraps ve ardından kendi sarmalayıcı fonksiyonumuz. Bu linkte verilen detaylara göre,
functools.wraps, bir sarmalayıcı işlevi tanımlanırken update_wrapper () işlevini işlev dekoratörü olarak çağırmak için kolaylık işlevidir.
Kısmen eşdeğerdir (update_wrapper, wrapped = wrapped, atanan = atanan, updated = updated).
Yani @wraps dekoratör aslında functools.partial (func [, * args] [, ** anahtar kelimeler]) çağrısı yapar.
Functools.partial () tanımı
Partial () işlevi, bir işlevin bağımsız değişkenlerinin ve / veya anahtar sözcüklerinin bir kısmını "imzalı" bir şekilde kısaltılmış işlev uygulaması için kullanılır. Örneğin, partial argümanı varsayılan olarak iki olduğunda int () işlevi gibi davranan bir callable oluşturmak için partial () kullanılabilir:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
Bu da beni şu sonuca getiriyor: @wraps, partial () işlevini çağırır ve sarma işlevinizi parametre olarak iletir. Sondaki kısmi (), basitleştirilmiş sürümü, yani sarma işlevinin kendisini değil, sarma işlevinin içindekinin nesnesini döndürür.
Kısacası, functools.wraps sadece normal bir işlevdir. Bu resmi örneği ele alalım . Kaynak kodun yardımıyla , uygulama ve çalışan adımlar hakkında daha fazla ayrıntıyı aşağıdaki gibi görebiliriz:
sargı = O1 .__ arama __ (sargı)
Uygulanmasını kontrol edilmesi __call__ , bu aşamada, (sol taraf) sonra görüyoruz sarma nesne ile sonuçlanmıştır olur (* self.args, * bağımsız değişken, ** yeni anahtar kelimeleri) self.func oluşturulmasını kontrol edilmesi O1 içinde __New__ biz biliyorum self.func fonksiyonudur update_wrapper . Bu parametre kullanır * args , sağ taraftaki sarmalayıcı onun 1 parametresi olarak. Update_wrapper öğesinin son adımına bakıldığında , sağ taraftaki sarıcı döndürüldüğünde, bazı öznitelikler gerektiği gibi değiştirilir.