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ć?
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
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; -).
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)
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?
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
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