代码之家  ›  专栏  ›  技术社区  ›  Matthew Schinckel

对函数和方法使用相同的修饰符(带参数)

  •  14
  • Matthew Schinckel  · 技术社区  · 15 年前

    我一直在尝试创建一个可以在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 参数是它正在操作的实例,而不是第一个非自变量。

    有没有一种方法来判断是否将修饰符应用于函数而不是方法,并进行相应的处理?

    5 回复  |  直到 6 年前
        1
  •  15
  •   SilentGhost    15 年前

    扩展 __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
    
        2
  •  5
  •   Alex Martelli    15 年前

    decorator总是应用于一个函数对象——让decorator print 它的参数的类型,您将能够确认这一点;并且它通常也应该返回一个函数对象(它已经是一个具有适当 __get__ !-)尽管后者也有例外。

    即,在代码中:

    class X(object):
    
      @deco
      def f(self): pass
    

    deco(f) 在教室里,当你还在的时候, f 是函数,而不是方法类型的实例。(该方法制造并返回 F 第二代 以后什么时候 F 作为属性访问 X 或其实例)。

    也许你最好解释一下你想给你的装饰师用一个玩具,这样我们可以提供更多的帮助…?

    编辑 :这也适用于带有参数的装饰器,即

    class X(object):
    
      @deco(23)
      def f(self): pass
    

    然后是 deco(23)(f) 这在课堂上被称为, F 当作为参数传递给任何可调用的对象时仍然是函数对象 deco(23) 返回,并且该可调用对象仍应返回一个函数对象(通常是——有异常;-)。

        3
  •  4
  •   Konrad Rudolph    15 年前

    因为你已经定义了 __get__ 要在绑定方法上使用decorator,可以传递一个标志,告诉它是否在方法或函数上使用。

    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
  •  1
  •   Matthew Schinckel    15 年前

    我提出的部分(特定)解决方案依赖于异常处理。我试图创建一个装饰器,只允许某些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
  •  0
  •   danijar    6 年前

    下面是我发现的一种检测修饰可调用是否是函数或方法的一般方法:

    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