Can a Python decorator of an instance method access the class?
嗨,我有一些大致如下的东西。基本上,我需要从定义中的实例方法上使用的修饰器访问实例方法的类。
1 2 3 4 5 6 7 8 9 10 | 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中获取类——但是这些问题依赖于一个解决方案,该解决方案通过抓取第一个参数在运行时获取实例。在我的例子中,我将根据从类中收集到的信息调用该方法,因此我不能等待调用的到来。
谢谢您。
如果您使用的是python 2.6或更高版本,那么您可以使用类修饰器,可能类似这样(警告:未测试的代码)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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"属性将该方法标记为感兴趣的方法-函数和方法也是对象,因此可以向它们附加额外的元数据。
类被创建后,类修饰器将遍历所有方法,并对已标记的方法执行所需的任何操作。
如果您希望所有方法都受到影响,那么您可以省去方法修饰器,只使用类修饰器。
正如其他人指出的,类在调用decorator时没有被创建。但是,可以使用decorator参数对函数对象进行注释,然后在元类的
正如马克所说:
此代码显示了如何使用自动后处理功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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__) |
产量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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} |
请注意,在本例中:
希望这有帮助
正如Ants所指出的,您无法从类内获取对类的引用。但是,如果您想区分不同的类(而不是操作实际的类类型对象),可以为每个类传递一个字符串。也可以使用类样式的装饰器将任何其他参数传递给装饰器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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() |
印刷品:
1 2 3 4 | before instantiating Bar() calling b.foo() decorating function in Bar in foo |
另外,请参见BruceEckel关于装饰师的页面。
由于python 3.6,您可以使用
1 2 3 4 5 6 7 8 9 10 11 | class class_decorator: def __init__(self, fn): self.fn = fn def __set_name__(self, owner, name): # do something with owner, i.e. print(f"decorating {self.fn} and using {owner}") self.fn.class_name = owner.__name__ # then replace ourself with the original method setattr(owner, name, self.fn) |
注意,它在类创建时被调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> class A: ... @class_decorator ... def hello(self, x=42): ... return x ... decorating <function A.hello at 0x7f9bedf66bf8> and using <class '__main__.A'> >>> A.hello <function __main__.A.hello(self, x=42)> >>> A.hello.class_name 'A' >>> a = A() >>> a.hello() 42 |
如果您想了解更多关于如何创建类的信息,特别是在调用
下面是一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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" |
输出是:
1 2 3 4 5 6 | >>> import Test >>> a = Test() >>> a.bar() starting dec bar ending dec |
Flask Classy所做的是创建一个临时缓存,它存储在方法上,然后使用其他方法(事实上,Flask将使用
您可以重用这个模式,这次使用一个元类,以便在导入时包装该方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 |
在实际类上(可以使用元类执行相同的操作):
1 2 3 4 5 6 7 8 9 10 11 | @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/apigoy/flask-classy/blob/master/flask-classy.py
问题是,当调用decorator时,类还不存在。试试这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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() |
此程序将输出:
1 2 3 4 | 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 |
如你所见,你将不得不想出一个不同的方法来做你想做的。
这是一个古老的问题,但遇到了金星人。http://venusian.readthedocs.org/en/latest/最新版本/
它似乎具有修饰方法的能力,并且在这样做的同时允许您访问类和方法。注:调用
无论哪种方式…下面的示例是完整的,应该运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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() |
函数不知道它在定义点是否是一个方法,当装饰代码运行时。只有通过类/实例标识符访问它时,它才可能知道它的类/实例。为了克服这个限制,您可以使用描述符对象进行修饰,以将实际的修饰代码延迟到访问/调用时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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) |
这允许您修饰单个(静态类)方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
现在您可以使用decorator代码进行自省…
1 2 3 4 5 6 7 8 9 10 11 12 | >>> 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 |
…对于改变功能行为:
1 2 3 4 5 6 7 8 9 | >>> 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'} |
您将可以在装饰器应该返回的装饰方法中访问调用该方法的对象的类。像这样:
1 2 3 4 5 6 | 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类,可以执行以下操作:
1 2 3 | >>> obj = ModelA() >>> obj.a_method() My class is <class '__main__.ModelA'> |