实例方法的修饰符可以访问该类吗?

我有一些大致如下的东西。基本上,我需要从实例方法定义中使用的修饰符访问实例方法的类。

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’对象没有属性‘ im _ class’

我发现了类似的问题/答案-Python 修饰器使函数忘记它属于一个类在 Python 装饰器中获取类-但是它们依赖于一种解决方案,通过抓取第一个参数在运行时抓取实例。在我的例子中,我将根据从它的类中收集到的信息来调用该方法,因此我不能等待调用的到来。

81419 次浏览

您将可以访问装饰器应该返回的修饰方法中正在调用其方法的对象的类。像这样:

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'>

问题是,当这个装饰器被调用时,这个类还不存在:

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

正如你所看到的,你必须找到一种不同的方式来做你想做的事情。

正如 Ant 所指出的,您不能从类中获得对该类的引用。但是,如果您有兴趣区分不同的类(而不是操作实际的类类型对象) ,那么可以为每个类传递一个字符串。您还可以使用类风格的装饰器将任何其他参数传递给装饰器。

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

还有 参见布鲁斯 · 埃克尔关于室内设计师的主页。

如果您正在使用 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。

高级酒壶所做的就是创建一个存储在方法上的临时缓存,然后使用其他东西(事实上 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

这里有一个简单的例子:

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

这是一个老问题,但碰到了金星

它似乎具有修饰方法的能力,并在这样做的同时让您既可以访问类又可以访问方法。 请注意,调用 setattr(ob, wrapped.__name__, decorated)不是使用 venusian 的典型方法,并且有点违背了这个目的。

无论哪种方式... 下面的例子是完整的,应该运行。

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. 我们有两个后处理选项: 在类定义结束时自动执行,或者在应用程序运行之前执行。我更喜欢使用基类的第一个选项,但是您也可以遵循第二种方法。

这段代码显示了如何使用自动后处理:

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}

请注意,在这个例子中:

  1. 我们可以用任意参数注释任何函数。
  2. 每个类都有自己的公开方法。
  3. 我们也可以继承公开的方法。
  4. 方法可以在更新暴露特性时重写。

希望这个能帮上忙

函数不知道在装饰器代码运行时它是否是定义点的方法。只有当通过类/实例标识符访问它时,它才可能知道它的类/实例。为了克服这个限制,您可以通过描述符对象来延迟实际的装饰代码,直到访问/调用时间:

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'}

由于 python 3.6,您可以使用 object.__set_name__以一种非常简单的方式来实现这一点。文档指出,__set_name__是“在创建所属类 主人时调用的”。 这里有一个例子:

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)

注意,它在类创建时被调用:

>>> 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

如果您想知道更多关于类是如何创建的,特别是什么时候调用 __set_name__,您可以参考 关于“创建类对象”的文档

正如其他答案所指出的,装饰符是一个函数式的东西,您不能访问这个方法所属的类,因为这个类还没有被创建。然而,使用装饰符来“标记”函数,然后再使用元类技术来处理方法,这是完全可以接受的,因为在 __new__阶段,类已经由它的元类创建。

下面是一个简单的例子:

我们使用 @field将该方法标记为一个特殊字段,并在元类中对其进行处理。

def field(fn):
"""Mark the method as an extra field"""
fn.is_field = True
return fn


class MetaEndpoint(type):
def __new__(cls, name, bases, attrs):
fields = {}
for k, v in attrs.items():
if inspect.isfunction(v) and getattr(k, "is_field", False):
fields[k] = v
for base in bases:
if hasattr(base, "_fields"):
fields.update(base._fields)
attrs["_fields"] = fields


return type.__new__(cls, name, bases, attrs)


class EndPoint(metaclass=MetaEndpoint):
pass




# Usage


class MyEndPoint(EndPoint):
@field
def foo(self):
return "bar"


e = MyEndPoint()
e._fields  # {"foo": ...}

我只想添加我的示例,因为它包含了所有我能想到的从修饰方法访问类的方法。它使用了@tyrion 所建议的描述符。装饰符可以获取参数并将它们传递给描述符。它既可以处理类中的方法,也可以处理没有类的函数。

import datetime as dt
import functools


def dec(arg1):
class Timed(object):
local_arg = arg1
def __init__(self, f):
functools.update_wrapper(self, f)
self.func = f


def __set_name__(self, owner, name):
# doing something fancy with owner and name
print('owner type', owner.my_type())
print('my arg', self.local_arg)


def __call__(self, *args, **kwargs):
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
        

def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
return Timed


class Test(object):
def __init__(self):
super(Test, self).__init__()


@classmethod
def my_type(cls):
return 'owner'


@dec(arg1='a')
def decorated(self, *args, **kwargs):
print(self)
print(args)
print(kwargs)
return dict()


def call_deco(self):
self.decorated("Hello", world="World")


@dec(arg1='a function')
def another(*args, **kwargs):
print(args)
print(kwargs)
return dict()


if __name__ == "__main__":
t = Test()
ret = t.call_deco()
another('Ni hao', world="shi jie")
    

@ Asterio Gonzalez

我更喜欢你的方法,但是为了遵守新的元类处理,Python 3需要稍作改动(还有,一些 print 语句缺少括号) :

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Aug  9 15:27:30 2021


@author: yves
"""


def expose(**kw):
"Note that using **kw you can tag the function with any parameters"
def wrap(func):
name = func.__name__
assert not name.startswith('_'), "Only public methods can be exposed"


meta = func.__meta__ = kw
meta['exposed'] = None
return func


return wrap


class ExposableMetaclass(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 Exposable(metaclass=ExposableMetaclass):
"Base class to expose instance methods"
_exposable_ = None  # Not necessary, just for pylint


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_)

满足我的需求!