2009-08-17 42 views
13

Tôi đã cố tạo một trình trang trí có thể được sử dụng với cả hàm và phương thức trong python. Điều này trên riêng của nó không phải là khó khăn, nhưng khi tạo một trang trí mà có đối số, nó có vẻ là.Sử dụng cùng một trang trí (với đối số) với các hàm và phương thức

class methods(object): 
    def __init__(self, *_methods): 
     self.methods = _methods 

    def __call__(self, func): 
     def inner(request, *args, **kwargs): 
      print request 
      return func(request, *args, **kwargs) 
     return inner 

    def __get__(self, obj, type=None): 
     if obj is None: 
      return self 
     new_func = self.func.__get__(obj, type) 
     return self.__class__(new_func) 

Đoạn mã trên kết thúc tốt đẹp các chức năng/phương pháp đúng, nhưng trong trường hợp của một phương pháp, lập luận request là dụ nó đang hoạt động trên, không phải là phi tự số đầu tiên.

Có cách nào để biết liệu trang trí đang được áp dụng cho một hàm thay vì phương thức hay không và xử lý tương ứng?

Trả lời

12

Để mở rộng theo phương pháp __get__. Điều này có thể được tổng quát thành một trang trí trang trí.

class _MethodDecoratorAdaptor(object): 
    def __init__(self, decorator, func): 
     self.decorator = decorator 
     self.func = func 
    def __call__(self, *args, **kwargs): 
     return self.decorator(self.func)(*args, **kwargs) 
    def __get__(self, instance, owner): 
     return self.decorator(self.func.__get__(instance, owner)) 

def auto_adapt_to_methods(decorator): 
    """Allows you to use the same decorator on methods and functions, 
    hiding the self argument from the decorator.""" 
    def adapt(func): 
     return _MethodDecoratorAdaptor(decorator, func) 
    return adapt 

Bằng cách này bạn chỉ có thể làm cho trang trí của bạn tự động thích ứng với điều kiện nó được sử dụng trong.

def allowed(*allowed_methods): 
    @auto_adapt_to_methods 
    def wrapper(func): 
     def wrapped(request): 
      if request not in allowed_methods: 
       raise ValueError("Invalid method %s" % request) 
      return func(request) 
     return wrapped 
    return wrapper 

Chú ý rằng các chức năng bao bọc được gọi là trên tất cả các cuộc gọi chức năng, do đó, không làm bất cứ thứ gì đắt tiền ở đó.

Cách sử dụng của trang trí:

class Foo(object): 
    @allowed('GET', 'POST') 
    def do(self, request): 
     print "Request %s on %s" % (request, self) 

@allowed('GET') 
def do(request): 
    print "Plain request %s" % request 

Foo().do('GET') # Works 
Foo().do('POST') # Raises 
+0

Bạn nên thêm 'update_wrapper (self, func)' vào đầu * của '_MethodDecoratorAdaptor .__ init__' (trong đó update_wrapper là từ vui mô-đun ctools). Điều này làm cho các trang trí kết quả giữ lại các thuộc tính tùy chỉnh trên các hàm/các callables mà chúng trang trí, trong khi vẫn giữ chúng tương thích. – spookylukey

+5

Tôi đã phát hiện ra rằng phương pháp này chỉ hoạt động trong một số trường hợp và rất khó gỡ lỗi khi nó không hoạt động. http://groups.google.com/group/django-developers/msg/f36976f5cfbcbeb3 – spookylukey

+0

@spookylukey Trên thực tế, cách xử lý này ở Django cũng khá sạch sẽ. – astrojuanlu

4

Bộ trang trí luôn được áp dụng cho đối tượng hàm - có trang trí print loại đối số của nó và bạn sẽ có thể xác nhận điều đó; và nó thường sẽ trả về một đối tượng chức năng, quá (mà đã là một trang trí với các thích hợp __get__! -) mặc dù có những ngoại lệ cho sau này.

tức là, trong các mã:

class X(object): 

    @deco 
    def f(self): pass 

deco(f) được gọi là trong cơ thể lớp, và, trong khi bạn vẫn ở đó, f là một chức năng, không phải là một thể hiện của một loại phương pháp. (Phương pháp được sản xuất và trả lại trong f 's __get__ khi sau này f được truy cập dưới dạng thuộc tính của X hoặc một phiên bản của nó).

Có lẽ bạn có thể giải thích rõ hơn về việc sử dụng đồ chơi bạn muốn cho người trang trí của mình, vì vậy chúng tôi có thể trợ giúp thêm ...?

Sửa: đây đi cho trang trí với các đối số cũng vậy, tức là

class X(object): 

    @deco(23) 
    def f(self): pass 

sau đó nó deco(23)(f) đó được gọi là trong cơ thể lớp, f vẫn là một đối tượng chức năng khi thông qua như là đối số để bất cứ điều gì có thể được gọi deco(23) trả về, và rằng callable vẫn nên trả về một đối tượng hàm (thường - với các ngoại lệ ;-).

4

Vì bạn đã xác định __get__ để sử dụng trang trí của mình trên Phương pháp ràng buộc, bạn có thể chuyển cờ cho biết liệu nó có đang được sử dụng trên phương pháp hoặc chức năng hay không.

class methods(object): 
    def __init__(self, *_methods, called_on_method=False): 
     self.methods = _methods 
     self.called_on_method 

    def __call__(self, func): 
     if self.called_on_method: 
      def inner(self, request, *args, **kwargs): 
       print request 
       return func(request, *args, **kwargs) 
     else: 
      def inner(request, *args, **kwargs): 
       print request 
       return func(request, *args, **kwargs) 
     return inner 

    def __get__(self, obj, type=None): 
     if obj is None: 
      return self 
     new_func = self.func.__get__(obj, type) 
     return self.__class__(new_func, called_on_method=True) 
1

Giải pháp một phần (cụ thể) tôi đã đưa ra dựa vào xử lý ngoại lệ. Tôi đang cố tạo một trình trang trí để chỉ cho phép các phương thức HttpRequest nhất định, nhưng làm cho nó hoạt động với cả hai hàm là các khung nhìn và các phương thức là các khung nhìn.

Vì vậy, lớp này sẽ làm những gì tôi muốn:

class methods(object): 
    def __init__(self, *_methods): 
     self.methods = _methods 

    def __call__(self, func): 
     @wraps(func) 
     def inner(*args, **kwargs): 
      try: 
       if args[0].method in self.methods: 
        return func(*args, **kwargs) 
      except AttributeError: 
       if args[1].method in self.methods: 
        return func(*args, **kwargs) 
      return HttpResponseMethodNotAllowed(self.methods) 
     return inner 

Dưới đây là hai trường hợp sử dụng: trang trí một chức năng:

@methods("GET") 
def view_func(request, *args, **kwargs): 
    pass 

và trang trí method của một class:

class ViewContainer(object): 
    # ... 

    @methods("GET", "PUT") 
    def object(self, request, pk, *args, **kwargs): 
     # stuff that needs a reference to self... 
     pass 

Có giải pháp nào tốt hơn là sử dụng xử lý ngoại lệ không?

0

Đây là một cách tổng quát tôi thấy để phát hiện xem một callable trang trí là một chức năng hoặc các phương pháp:

import functools 

class decorator(object): 

    def __init__(self, func): 
    self._func = func 
    self._obj = None 
    self._wrapped = None 

    def __call__(self, *args, **kwargs): 
    if not self._wrapped: 
     if self._obj: 
     self._wrapped = self._wrap_method(self._func) 
     self._wrapped = functools.partial(self._wrapped, self._obj) 
     else: 
     self._wrapped = self._wrap_function(self._func) 
    return self._wrapped(*args, **kwargs) 

    def __get__(self, obj, type=None): 
    self._obj = obj 
    return self 

    def _wrap_method(self, method): 
    @functools.wraps(method) 
    def inner(self, *args, **kwargs): 
     print('Method called on {}:'.format(type(self).__name__)) 
     return method(self, *args, **kwargs) 
    return inner 

    def _wrap_function(self, function): 
    @functools.wraps(function) 
    def inner(*args, **kwargs): 
     print('Function called:') 
     return function(*args, **kwargs) 
    return inner 

Ví dụ sử dụng:

class Foo(object): 
    @decorator 
    def foo(self, foo, bar): 
    print(foo, bar) 

@decorator 
def foo(foo, bar): 
    print(foo, bar) 

foo(12, bar=42)  # Function called: 12 42 
foo(12, 42)   # Function called: 12 42 
obj = Foo() 
obj.foo(12, bar=42) # Method called on Foo: 12 42 
obj.foo(12, 42)  # Method called on Foo: 12 42 
Các vấn đề liên quan