对函数和方法使用相同的装饰器(带参数)
Posted
技术标签:
【中文标题】对函数和方法使用相同的装饰器(带参数)【英文标题】:Using the same decorator (with arguments) with functions and methods 【发布时间】:2010-11-20 06:49:08 【问题描述】:我一直在尝试创建一个可以与 python 中的函数和方法一起使用的装饰器。这本身并不难,但是当创建一个接受参数的装饰器时,它似乎是。
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)
上面的代码正确地包装了函数/方法,但在方法的情况下,request
参数是它正在操作的实例,而不是第一个非 self 参数。
有没有办法判断装饰器是否应用于函数而不是方法,并进行相应处理?
【问题讨论】:
当然,现在最好的解决方案是使用wrapt
【参考方案1】:
扩展__get__
方法。这可以概括为一个装饰器装饰器。
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
这样你就可以让你的装饰器自动适应它的使用条件。
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
请注意,包装函数在所有函数调用中都会被调用,所以不要在那里做任何昂贵的事情。
装饰器的用法:
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
【讨论】:
您应该将update_wrapper(self, func)
添加到_MethodDecoratorAdaptor.__init__
的开始(其中update_wrapper 来自functools 模块)。这使得生成的装饰器在它们装饰的函数/可调用对象上保留自定义属性,同时保持它们的可组合性。
我发现这种方法只在某些情况下有效,当它不起作用时,调试起来非常困难。 groups.google.com/group/django-developers/msg/f36976f5cfbcbeb3
@spookylukey 实际上在 Django 中的处理方式也很干净。
@Juanlu001 Django 是如何处理这个问题的?
@Satoru.Logic 上面链接的电子邮件和源代码解释了它。【参考方案2】:
装饰器总是应用于函数对象——拥有装饰器print
它的参数类型,您将能够确认;它通常也应该返回一个函数对象(它已经是一个带有正确__get__
!- 的装饰器),尽管后者有例外。
即在代码中:
class X(object):
@deco
def f(self): pass
deco(f)
在类体内被调用,当你还在的时候,f
是一个函数,而不是一个方法类型的实例。 (该方法在f
的__get__
中制造并返回,稍后将f
作为X
的属性或其实例进行访问)。
也许您可以更好地解释一下您想要用于装饰者的玩具用途,以便我们可以提供更多帮助...?
编辑:这也适用于带参数的装饰器,即
class X(object):
@deco(23)
def f(self): pass
那么在类主体中调用的是deco(23)(f)
,f
作为参数传递给任何可调用的deco(23)
返回时仍然是一个函数对象,并且该可调用对象仍应返回一个函数对象(通常 - 与例外;-)。
【讨论】:
【参考方案3】:由于您已经定义了一个 __get__
以在绑定方法上使用您的装饰器,您可以传递一个标志来告诉它是否正在用于方法或函数。
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)
【讨论】:
【参考方案4】:我提出的部分(特定)解决方案依赖于异常处理。我正在尝试创建一个装饰器以仅允许某些 HttpRequest 方法,但使其适用于作为视图的函数和作为视图的方法。
所以,这门课会做我想做的事:
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
以下是两个用例:装饰函数:
@methods("GET")
def view_func(request, *args, **kwargs):
pass
类的装饰方法:
class ViewContainer(object):
# ...
@methods("GET", "PUT")
def object(self, request, pk, *args, **kwargs):
# stuff that needs a reference to self...
pass
有没有比使用异常处理更好的解决方案?
【讨论】:
【参考方案5】:这是我发现的一种检测修饰的可调用对象是函数还是方法的通用方法:
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
示例用法:
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
【讨论】:
以上是关于对函数和方法使用相同的装饰器(带参数)的主要内容,如果未能解决你的问题,请参考以下文章
基于 Python 类的装饰器,带有可以装饰方法或函数的参数