Używanie tego samego dekoratora (z argumentami) z funkcjami i metodami

Próbowałem stworzyć dekorator, który może być używany zarówno z funkcjami, jak i metodami w Pythonie. To samo w sobie nie jest takie trudne, ale podczas tworzenia dekoratora, który bierze argumenty, wydaje się być.

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)

Powyższy kod zawija funkcję/metodę poprawnie, ale w przypadku metody argument request jest instancją, na której działa, a nie pierwszym argumentem nie-self.

Czy jest sposób, aby stwierdzić, czy dekorator jest stosowany do funkcji zamiast sposób, i odpowiednio postępować?

Author: Matthew Schinckel, 2009-08-17

5 answers

Aby rozszerzyć podejście __get__. Można to uogólnić na dekoratora dekoratora.

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

W ten sposób możesz po prostu sprawić, że dekorator automatycznie dostosuje się do warunków, w których jest używany.

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

Zauważ, że funkcja wrapper jest wywoływana przy wszystkich wywołaniach funkcji, więc nie rób tam nic kosztownego.

Użycie dekoratora:

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
 15
Author: Ants Aasma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-17 16:25:54

Dekorator jest zawsze stosowany do obiektu funkcji - miej dekorator print Typ jego argumentu i będziesz w stanie to potwierdzić; i powinien on również zwracać obiekt funkcji (który jest już dekoratorem z odpowiednim __get__!- ) chociaż są wyjątki od tego ostatniego.

Czyli w kodzie:

class X(object):

  @deco
  def f(self): pass

deco(f) jest wywoływana wewnątrz ciała klasy i, gdy wciąż tam jesteś, f jest funkcją, a nie instancją typu metody. (Metoda jest wytworzony i zwrócony w f ' s __get__, gdy później {[5] } jest dostępny jako atrybut X lub jego instancja).

Może mógłbyś lepiej wyjaśnić jedną zabawkę, którą chciałbyś użyć dla swojego dekoratora, żebyśmy mogli być bardziej pomocni...?

Edit : dotyczy to również dekoratorów z argumentami, tzn.

class X(object):

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

Wtedy to deco(23)(f) jest wywoływane w ciele klasy, {[5] } jest nadal obiektem funkcji, gdy jest przekazywany jako argument do dowolnego wywołanego deco(23), i że wywołanie powinno nadal zwracać obiekt funkcji (ogólnie -- z wyjątkami; -).

 4
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-17 15:16:03

Ponieważ już definiujesz __get__, aby używać dekoratora w metodzie Bound, możesz przekazać flagę informującą, czy jest ona używana w metodzie lub funkcji.

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
Author: ironfroggy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-17 16:26:31

Częściowe (specyficzne) rozwiązanie, które wymyśliłem, polega na obsłudze WYJĄTKÓW. Próbuję utworzyć dekorator, aby zezwalał tylko na określone metody HttpRequest, ale aby działał zarówno z funkcjami, które są widokami, jak i z metodami, które są widokami.

Więc ta klasa zrobi co chcę:

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

Oto dwa przypadki użycia: dekorowanie funkcji:

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

I metody zdobienia klasy:

class ViewContainer(object):
    # ...

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

Czy jest lepsze rozwiązanie niż używanie wyjątku obsługa?

 1
Author: Matthew Schinckel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-17 15:28:51

Oto ogólny sposób, który znalazłem, aby wykryć, czy dekorowane wywołanie jest funkcją lub metodą:

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

Przykładowe użycie:

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
 0
Author: danijar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-13 14:04:34