Может ли декоратор Python метода экземпляра получить доступ к классу?
Привет, у меня что-то примерно следующее. В основном мне нужно получить доступ к классу метода экземпляра из декоратора, используемого в методе экземпляра в его определении.
def decorator(view):
# do something that requires view's class
print view.im_class
return view
class ModelA(object):
@decorator
def a_method(self):
# do some stuff
pass
код "как есть" дает
AttributeError: 'function' object has no attribute 'im_class'
Я нашел аналогичный вопрос / ответы -Python decorator заставляет функцию забыть, что она принадлежит классу и получить класс в Python decorator - но они полагаются на решение, которое захватывает экземпляр во время выполнения путем захвата первого параметра. В моем случае я буду вызывать метод, основанный на информации, полученной из его класса, поэтому я не могу дождаться вызова.
спасибо.
10 ответов:
Если вы используете Python 2.6 или более позднюю версию, вы можете использовать декоратор класса, возможно, что-то вроде этого (предупреждение: непроверенный код).
def class_decorator(cls): for name, method in cls.__dict__.iteritems(): if hasattr(method, "use_class"): # do something with the method and class print name, cls return cls def method_decorator(view): # mark the method as something that requires view's class view.use_class = True return view @class_decorator class ModelA(object): @method_decorator def a_method(self): # do some stuff pass
декоратор метода отмечает метод как представляющий интерес, добавляя атрибут "use_class" - функции и методы также являются объектами, поэтому вы можете прикрепить к ним дополнительные метаданные.
после того, как класс был создан декоратор класса затем проходит через все методы и делает все необходимое на методы это было отмечено.
Если вы хотите, чтобы все методы были затронуты, то вы можете оставить метод декоратора и просто использовать класс декоратора.
как указывали другие, класс не был создан во время вызова декоратора. , можно аннотировать объект функции с параметрами декоратора, а затем повторно украсить функцию в метаклассе
__new__
метод. Вам нужно будет получить доступ к функции__dict__
атрибут напрямую, как минимум для меня,func.foo = 1
привел к AttributeError.
Как указано муравьями, вы не можете получить ссылку на класс из класса. Однако, если вы заинтересованы в различении между различными классами ( не манипулируя фактическим объектом типа класса), вы можете передать строку для каждого класса. Вы также можете передать любые другие параметры, которые вам нравятся, декоратору с помощью декораторов класса.
class Decorator(object): def __init__(self,decoratee_enclosing_class): self.decoratee_enclosing_class = decoratee_enclosing_class def __call__(self,original_func): def new_function(*args,**kwargs): print 'decorating function in ',self.decoratee_enclosing_class original_func(*args,**kwargs) return new_function class Bar(object): @Decorator('Bar') def foo(self): print 'in foo' class Baz(object): @Decorator('Baz') def foo(self): print 'in foo' print 'before instantiating Bar()' b = Bar() print 'calling b.foo()' b.foo()
принты:
before instantiating Bar() calling b.foo() decorating function in Bar in foo
проблема в том, что когда декоратор называется класс еще не существует. Попробуйте это:
def loud_decorator(func): print("Now decorating %s" % func) def decorated(*args, **kwargs): print("Now calling %s with %s,%s" % (func, args, kwargs)) return func(*args, **kwargs) return decorated class Foo(object): class __metaclass__(type): def __new__(cls, name, bases, dict_): print("Creating class %s%s with attributes %s" % (name, bases, dict_)) return type.__new__(cls, name, bases, dict_) @loud_decorator def hello(self, msg): print("Hello %s" % msg) Foo().hello()
эта программа выведет:
Now decorating <function hello at 0xb74d35dc> Creating class Foo(<type 'object'>,) with attributes {'__module__': '__main__', '__metaclass__': <class '__main__.__metaclass__'>, 'hello': <function decorated at 0xb74d356c>} Now calling <function hello at 0xb74d35dc> with (<__main__.Foo object at 0xb74ea1ac>, 'World'),{} Hello World
вот простой пример:
def mod_bar(cls): # returns modified class def decorate(fcn): # returns decorated function def new_fcn(self): print self.start_str print fcn(self) print self.end_str return new_fcn cls.bar = decorate(cls.bar) return cls @mod_bar class Test(object): def __init__(self): self.start_str = "starting dec" self.end_str = "ending dec" def bar(self): return "bar"
выход:
>>> import Test >>> a = Test() >>> a.bar() starting dec bar ending dec
что термос-классная создает временный кэш, который он хранит в методе, а затем использует что-то еще (тот факт, что Flask зарегистрирует классы с помощью
register
метод класса) фактически обертывает метод.вы можете использовать этот шаблон, на этот раз с помощью метакласса, так что вы можете обернуть метод во время импорта.
def route(rule, **options): """A decorator that is used to define custom routes for methods in FlaskView subclasses. The format is exactly the same as Flask's `@app.route` decorator. """ def decorator(f): # Put the rule cache on the method itself instead of globally if not hasattr(f, '_rule_cache') or f._rule_cache is None: f._rule_cache = {f.__name__: [(rule, options)]} elif not f.__name__ in f._rule_cache: f._rule_cache[f.__name__] = [(rule, options)] else: f._rule_cache[f.__name__].append((rule, options)) return f return decorator
на самом классе (вы можете сделать то же самое с помощью метакласса):
@classmethod def register(cls, app, route_base=None, subdomain=None, route_prefix=None, trailing_slash=None): for name, value in members: proxy = cls.make_proxy_method(name) route_name = cls.build_route_name(name) try: if hasattr(value, "_rule_cache") and name in value._rule_cache: for idx, cached_rule in enumerate(value._rule_cache[name]): # wrap the method here
источник: https://github.com/apiguy/flask-classy/blob/master/flask_classy.py
Как Марк предлагает:
- любой декоратор вызывается перед построением класса, поэтому он неизвестен декоратору.
- у нас можете tag эти методы и сделать любой необходимый пост-процесс позже.
- у нас есть два варианта постобработки: автоматически в конце определения класса или где-то перед запуском приложения. Я предпочитаю 1-й вариант с использованием базового класса, но вы можете следовать за 2-й подход что ж.
этот код показывает, как это может работать с помощью автоматической постобработки:
def expose(**kw): "Note that using **kw you can tag the function with any parameters" def wrap(func): name = func.func_name assert not name.startswith('_'), "Only public methods can be exposed" meta = func.__meta__ = kw meta['exposed'] = True return func return wrap class Exposable(object): "Base class to expose instance methods" _exposable_ = None # Not necessary, just for pylint class __metaclass__(type): def __new__(cls, name, bases, state): methods = state['_exposed_'] = dict() # inherit bases exposed methods for base in bases: methods.update(getattr(base, '_exposed_', {})) for name, member in state.items(): meta = getattr(member, '__meta__', None) if meta is not None: print "Found", name, meta methods[name] = member return type.__new__(cls, name, bases, state) class Foo(Exposable): @expose(any='parameter will go', inside='__meta__ func attribute') def foo(self): pass class Bar(Exposable): @expose(hide=True, help='the great bar function') def bar(self): pass class Buzz(Bar): @expose(hello=False, msg='overriding bar function') def bar(self): pass class Fizz(Foo): @expose(msg='adding a bar function') def bar(self): pass print('-' * 20) print("showing exposed methods") print("Foo: %s" % Foo._exposed_) print("Bar: %s" % Bar._exposed_) print("Buzz: %s" % Buzz._exposed_) print("Fizz: %s" % Fizz._exposed_) print('-' * 20) print('examine bar functions') print("Bar.bar: %s" % Bar.bar.__meta__) print("Buzz.bar: %s" % Buzz.bar.__meta__) print("Fizz.bar: %s" % Fizz.bar.__meta__)
выход дает:
Found foo {'inside': '__meta__ func attribute', 'any': 'parameter will go', 'exposed': True} Found bar {'hide': True, 'help': 'the great bar function', 'exposed': True} Found bar {'msg': 'overriding bar function', 'hello': False, 'exposed': True} Found bar {'msg': 'adding a bar function', 'exposed': True} -------------------- showing exposed methods Foo: {'foo': <function foo at 0x7f7da3abb398>} Bar: {'bar': <function bar at 0x7f7da3abb140>} Buzz: {'bar': <function bar at 0x7f7da3abb0c8>} Fizz: {'foo': <function foo at 0x7f7da3abb398>, 'bar': <function bar at 0x7f7da3abb488>} -------------------- examine bar functions Bar.bar: {'hide': True, 'help': 'the great bar function', 'exposed': True} Buzz.bar: {'msg': 'overriding bar function', 'hello': False, 'exposed': True} Fizz.bar: {'msg': 'adding a bar function', 'exposed': True}
обратите внимание, что в этом примере:
- мы можем аннотировать любую функцию с любыми произвольными параметрами.
- каждый класс имеет свои собственные открытые методы.
- мы также можем наследовать открытые методы.
- методы могут быть переопределены, поскольку функция exposing является усовершенствованный.
надеюсь, что это помогает
Это старый вопрос, но наткнулся на венерианской. http://venusian.readthedocs.org/en/latest/
Он, кажется, имеет возможность украсить методы и дать вам доступ как к классу, так и к методу при этом. Обратите внимание, что вызов
setattr(ob, wrapped.__name__, decorated)
это не типичный способ использования венерианского и несколько поражает цель.в любом случае... приведенный ниже пример является полным и должен выполняться.
import sys from functools import wraps import venusian def logged(wrapped): def callback(scanner, name, ob): @wraps(wrapped) def decorated(self, *args, **kwargs): print 'you called method', wrapped.__name__, 'on class', ob.__name__ return wrapped(self, *args, **kwargs) print 'decorating', '%s.%s' % (ob.__name__, wrapped.__name__) setattr(ob, wrapped.__name__, decorated) venusian.attach(wrapped, callback) return wrapped class Foo(object): @logged def bar(self): print 'bar' scanner = venusian.Scanner() scanner.scan(sys.modules[__name__]) if __name__ == '__main__': t = Foo() t.bar()
у вас будет доступ к классу объекта, на котором вызывается метод в стиле способ ваш декоратор должен вернуться. Вот так:
def decorator(method): # do something that requires view's class def decorated(self, *args, **kwargs): print 'My class is %s' % self.__class__ method(self, *args, **kwargs) return decorated
используя свой класс ModelA, вот что это делает:
>>> obj = ModelA() >>> obj.a_method() My class is <class '__main__.ModelA'>
функция не знает, является ли это метод в точке определения, когда выполняется код декоратора. Только когда он доступен через идентификатор класса/экземпляра, он может знать свой класс/экземпляр. Чтобы преодолеть это ограничение, вы можете украсить объект дескриптора, чтобы задержать фактический код украшения до времени доступа / вызова:
class decorated(object): def __init__(self, func, type_=None): self.func = func self.type = type_ def __get__(self, obj, type_=None): func = self.func.__get__(obj, type_) print('accessed %s.%s' % (type_.__name__, func.__name__)) return self.__class__(func, type_) def __call__(self, *args, **kwargs): name = '%s.%s' % (self.type.__name__, self.func.__name__) print('called %s with args=%s kwargs=%s' % (name, args, kwargs)) return self.func(*args, **kwargs)
Это позволяет украсить отдельные (статические|класс) методы:
class Foo(object): @decorated def foo(self, a, b): pass @decorated @staticmethod def bar(a, b): pass @decorated @classmethod def baz(cls, a, b): pass class Bar(Foo): pass
теперь вы можете использовать код декоратора для самоанализ...
>>> Foo.foo accessed Foo.foo >>> Foo.bar accessed Foo.bar >>> Foo.baz accessed Foo.baz >>> Bar.foo accessed Bar.foo >>> Bar.bar accessed Bar.bar >>> Bar.baz accessed Bar.baz
...и для изменения поведения функции:
>>> Foo().foo(1, 2) accessed Foo.foo called Foo.foo with args=(1, 2) kwargs={} >>> Foo.bar(1, b='bcd') accessed Foo.bar called Foo.bar with args=(1,) kwargs={'b': 'bcd'} >>> Bar.baz(a='abc', b='bcd') accessed Bar.baz called Bar.baz with args=() kwargs={'a': 'abc', 'b': 'bcd'}