2012-04-16 34 views
64

Tôi có một trang trí như dưới đâyLàm thế nào để vượt qua các đối số thêm vào trang trí python?

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

Tôi muốn nâng cao trang trí này để chấp nhận một lập luận như dưới đây

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

Nhưng mã này cung cấp cho các lỗi,

TypeError: myDecorator() takes exactly 2 arguments (1 given) 

Tại sao chức năng không tự động được chuyển? Làm cách nào để chuyển một cách rõ ràng chức năng đến chức năng trang trí?

+2

Balki: hãy tránh sử dụng boolean như là đối số của bạn, nó không phải là một cách tiếp cận gd và giảm mã của readliability –

+7

@KitHo - đó là một lá cờ boolean, vì vậy sử dụng một giá trị boolean là cách tiếp cận đúng. – AKX

+0

@KitHo - "gd" là gì? Liệu nó có tốt không"? –

Trả lời

107

Vì bạn đang gọi trang trí giống như một chức năng, nó cần phải trở lại một chức năng đó là trang trí thực tế:

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 

Chức năng bên ngoài sẽ được cung cấp bất kỳ đối số bạn vượt qua một cách rõ ràng, và phải trả lại bên trong chức năng. Hàm bên trong sẽ được truyền hàm để trang trí và trả về hàm đã sửa đổi.

Thông thường bạn muốn người trang trí thay đổi hành vi chức năng bằng cách gói nó trong một hàm bao bọc. Dưới đây là một ví dụ rằng tùy chọn bổ sung khai thác gỗ khi hàm được gọi:

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 

Các functools.wraps bản cuộc gọi thứ như tên và docstring với chức năng bao bọc, để làm cho nó nhiều hơn tương tự như chức năng ban đầu.

Ví dụ sử dụng:

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

Và sử dụng ['functools.wraps'] (http://docs.python.org/library/functools.html#functools.wraps) được khuyến khích - nó giữ nguyên tên ban đầu, docstring, vv của hàm được bọc. – AKX

+0

@AKX: Cảm ơn, tôi đã thêm điều này vào ví dụ thứ hai. – interjay

+1

Vì vậy, về cơ bản trang trí luôn luôn chỉ có một đối số là hàm. Nhưng người trang trí có thể là một giá trị trả về của một hàm có thể lấy các đối số. Điều này có đúng không? – balki

34

Chỉ cần để cung cấp một quan điểm khác nhau: cú pháp

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

tương đương với

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

Đặc biệt, expr có thể bất cứ điều gì bạn thích, miễn là nó đánh giá đến một cuộc gọi. Trong cụ thể cụ thể, expr có thể là một nhà máy trang trí: bạn cung cấp cho nó một số thông số và nó mang lại cho bạn một trang trí. Vì vậy, có thể là một cách tốt hơn để hiểu tình hình của bạn là như

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

mà sau đó có thể được rút ngắn xuống còn

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

Tất nhiên, vì nó trông như decorator_factory là một trang trí, người ta có xu hướng đặt tên cho nó để phản ánh điều đó. Điều này có thể gây nhầm lẫn khi bạn cố gắng thực hiện theo các mức độ bất công.

14

Chỉ muốn thêm một số mẹo hữu ích sẽ cho phép tạo đối số trang trí tùy chọn. Nó cũng sẽ alows để tái sử dụng trang trí và giảm làm tổ

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) 
1

Chỉ cần một cách khác để làm trang trí. Tôi tìm cách này dễ nhất để quấn quanh đầu tôi.

import functools 

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): 
     @functools.wraps(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() 
Các vấn đề liên quan