Bir Python dekoratörüne fazladan argümanları nasıl iletirim?


100

Aşağıdaki gibi bir dekoratörüm var.

def myDecorator(test_func):
    return callSomeWrapper(test_func)
def callSomeWrapper(test_func):
    return test_func
@myDecorator
def someFunc():
    print 'hello'

Bu dekoratörü aşağıdaki gibi başka bir argümanı kabul edecek şekilde geliştirmek istiyorum

def myDecorator(test_func,logIt):
    if logIt:
        print "Calling Function: " + test_func.__name__
    return callSomeWrapper(test_func)
@myDecorator(False)
def someFunc():
    print 'Hello'

Ama bu kod hata veriyor,

TypeError: myDecorator () tam olarak 2 argüman alır (1 verilen)

İşlev neden otomatik olarak geçmiyor? İşlevi açıkça dekoratör işlevine nasıl aktarırım?


3
balki: Lütfen argümanınız olarak boole kullanmaktan kaçının, bu bir gd yaklaşımı değildir ve kodun okunabilirliğini azaltın
Kit Ho

8
@KitHo - bu bir boole bayrağı, dolayısıyla bir boolean değeri kullanmak doğru yaklaşımdır.
AKX

2
@KitHo - "gd" nedir? İyi mi"?
Rob Bednark

Yanıtlar:


173

Dekoratörü bir işlev gibi çağırdığınız için, gerçek dekoratör olan başka bir işlevi döndürmesi gerekir:

def my_decorator(param):
    def actual_decorator(func):
        print("Decorating function {}, with parameter {}".format(func.__name__, param))
        return function_wrapper(func)  # assume we defined a wrapper somewhere
    return actual_decorator

Dış işleve açıkça ilettiğiniz herhangi bir bağımsız değişken verilecek ve iç işlevi döndürmelidir. İç işleve dekore etmek ve değiştirilen işlevi döndürmek için işleve geçilecektir.

Genellikle dekoratörün, onu bir sarmalayıcı işlevine sararak işlev davranışını değiştirmesini istersiniz. Fonksiyon çağrıldığında isteğe bağlı olarak günlük kaydı ekleyen bir örnek aşağıda verilmiştir:

def log_decorator(log_enabled):
    def actual_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if log_enabled:
                print("Calling Function: " + func.__name__)
            return func(*args, **kwargs)
        return wrapper
    return actual_decorator

functools.wrapsSarıcı işlevine isim ve docstringe gibi çağrı kopyalar şeyler orijinal işlevine daha benzer hale getirmek için.

Örnek kullanım:

>>> @log_decorator(True)
... def f(x):
...     return x+1
...
>>> f(4)
Calling Function: f
5

11
Ve kullanılması functools.wrapstavsiye edilir - sarmalanmış işlevin orijinal adını, belge dizesini vb.
AKX

@AKX: Teşekkürler, bunu ikinci örneğe ekledim.
interjay

1
Dolayısıyla, temelde dekoratör her zaman işlev olan yalnızca bir argüman alır. Ancak dekoratör, bağımsız değişkenler alabilen bir işlevin dönüş değeri olabilir. Bu doğru mu?
balki

2
@balki: Evet, bu doğru. İşleri karıştıran şey, birçok insanın dış işlevi de ( myDecoratorburada) bir dekoratör olarak adlandırmasıdır. Bu, bir dekoratör kullanıcısı için uygundur, ancak bir tane yazmaya çalışırken kafa karıştırıcı olabilir.
interjay

1
log_decorator@log_decorator@log_decorator()
Kafamı

46

Sadece farklı bir bakış açısı sağlamak için: sözdizimi

@expr
def func(...): #stuff

eşdeğerdir

def func(...): #stuff
func = expr(func)

Özellikle, exprbir çağrılabilir olarak değerlendirildiği sürece istediğiniz herhangi bir şey olabilir. Gelen özellikle belli, exprbir dekoratör fabrikası olabilir: bunu bazı parametreler vermek ve size bir dekoratör verir. Belki de durumunuzu anlamanın daha iyi bir yolu,

dec = decorator_factory(*args)
@dec
def func(...):

daha sonra kısaltılabilir

@decorator_factory(*args)
def func(...):

Tabii ki, bir dekoratör gibi göründüğü içindecorator_factory , insanlar bunu yansıtacak şekilde adlandırmaya meyillidir. Yönlendirme seviyelerini takip etmeye çalıştığınızda kafa karıştırıcı olabilir.


Teşekkürler, bu neler olup bittiğinin arkasındaki mantığı anlamama gerçekten yardımcı oldu.
Aditya Sriram

26

Sadece dekoratör argümanlarını isteğe bağlı hale getirmeye izin verecek bazı yararlı numaralar eklemek istiyorum. Aynı zamanda dekoratörün yeniden kullanılmasına ve iç içe geçmenin azaltılmasına da izin verir.

import functools

def myDecorator(test_func=None,logIt=None):
    if not test_func:
        return functools.partial(myDecorator, logIt=logIt)
    @functools.wraps(test_func)
    def f(*args, **kwargs):
        if logIt==1:
            print 'Logging level 1 for {}'.format(test_func.__name__)
        if logIt==2:
            print 'Logging level 2 for {}'.format(test_func.__name__)
        return test_func(*args, **kwargs)
    return f

#new decorator 
myDecorator_2 = myDecorator(logIt=2)

@myDecorator(logIt=2)
def pow2(i):
    return i**2

@myDecorator
def pow3(i):
    return i**3

@myDecorator_2
def pow4(i):
    return i**4

print pow2(2)
print pow3(2)
print pow4(2)

16

Dekoratör yapmanın başka bir yolu. Bu şekilde kafamı sarmayı en kolay yol buluyorum.

class NiceDecorator:
    def __init__(self, param_foo='a', param_bar='b'):
        self.param_foo = param_foo
        self.param_bar = param_bar

    def __call__(self, func):
        def my_logic(*args, **kwargs):
            # whatever logic your decorator is supposed to implement goes in here
            print('pre action baz')
            print(self.param_bar)
            # including the call to the decorated function (if you want to do that)
            result = func(*args, **kwargs)
            print('post action beep')
            return result

        return my_logic

# usage example from here on
@NiceDecorator(param_bar='baaar')
def example():
    print('example yay')


example()

teşekkür ederim! Yaklaşık 30 dakikadır bazı akıllara durgunluk veren "çözümlere" bakıyorum ve bu aslında mantıklı olan ilk çözüm.
canhazbits

0

Şimdi function1bir dekoratör ile bir fonksiyon çağırmak istiyorsanız decorator_with_argve bu durumda hem fonksiyon hem de dekoratör argümanlar alırsa ,

def function1(a, b):
    print (a, b)

decorator_with_arg(10)(function1)(1, 2)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.