实例方法的Python装饰器可以访问该类吗?

Car*_*l G 99 python decorator

嗨我有类似下面的东西.基本上我需要从定义中的实例方法使用的装饰器访问实例方法的类.

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
Run Code Online (Sandbox Code Playgroud)

代码原样给出

AttributeError: 'function' object has no attribute 'im_class'

我发现类似的问题/答案 - Python装饰器让函数忘记它属于一个类Python装饰器中的Get类 - 但这些依赖于一种解决方法,它通过抢夺第一个参数在运行时抓取实例.在我的情况下,我将基于从其类中收集的信息调用该方法,因此我不能等待来电.

谢谢.

Dav*_*rby 63

如果您使用的是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
Run Code Online (Sandbox Code Playgroud)

方法装饰器通过添加"use_class"属性将方法标记为感兴趣的方法 - 函数和方法也是对象,因此您可以将附加元数据附加到它们.

在创建类之后,类装饰器将遍历所有方法,并对已标记的方法执行任何操作.

如果您希望所有方法都受到影响,那么您可以省略方法装饰器并使用类装饰器.

  • 任何试图使用staticmethod或classmethod的人都想阅读这个PEP:http://www.python.org/dev/peps/pep-0232/不确定它是否可行,因为你无法在类上设置属性/静态方法,我认为当它们应用于函数时,它们会吞噬任何自定义函数属性. (3认同)
  • 谢谢,我认为这是要走的路.对于我想要使用这个装饰器的任何类,只需要一行额外的代码.也许我可以使用自定义元类并在__new __...期间执行相同的检查? (2认同)

tyr*_*ion 25

从 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)
Run Code Online (Sandbox Code Playgroud)

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

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

  • 使用带有参数的装饰器会是什么样子?例如 `@class_decorator('test', foo='bar')` (2认同)
  • @luckydonald您可以像普通的[接受参数的装饰器](/sf/ask/415037521/)一样处理它。只需有 `def Decorator(*args, **kwds): class Descriptor: ...; 返回描述符` (2认同)
  • 哇,非常感谢。尽管我已经使用Python 3.6+很长一段时间了,但不知道`__set_name__`。 (2认同)
  • 这种方法有一个缺点:静态检查器根本不理解这一点。Mypy 会认为 `hello` 不是一个方法,而是一个 `class_decorator` 类型的对象。 (2认同)
  • 我认为这是不可靠的,因为装饰器可能与其他装饰器组合堆叠。因此,如果它被另一个装饰器包裹,那么它可能不会被调用。 (2认同)

Mar*_*ser 15

正如其他人所指出的那样,在调用装饰器时尚未创建类.但是,可以使用装饰器参数注释函数对象,然后在元类的__new__方法中重新装饰该函数.你需要直接访问函数的__dict__属性,至少对我来说,func.foo = 1导致了AttributeError.

  • 应该使用`setattr`而不是访问`__dict__` (6认同)

Ant*_*sma 6

问题是,当调用装饰器时,该类还不存在。尝试这个:

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()
Run Code Online (Sandbox Code Playgroud)

该程序将输出:

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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,您将必须找出一种不同的方法来完成您想要的事情。


小智 6

正如马克所说:

  1. 任何称为BEFORE类的装饰器都将被构建,因此装饰器未知。
  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__)
Run Code Online (Sandbox Code Playgroud)

输出结果:

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}
Run Code Online (Sandbox Code Playgroud)

请注意,在此示例中:

  1. 我们可以使用任意参数注释任何函数。
  2. 每个类都有自己的公开方法。
  3. 我们也可以继承暴露的方法。
  4. 暴露功能更新时,方法可能会被覆盖。

希望这可以帮助


Ros*_*ers 5

正如 Ants 所指出的,您无法从类中获取对类的引用。但是,如果您有兴趣区分不同的类(而不是操作实际的类类型对象),则可以为每个类传递一个字符串。您还可以使用类样式装饰器将您喜欢的任何其他参数传递给装饰器。

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()
Run Code Online (Sandbox Code Playgroud)

印刷:

before instantiating Bar()
calling b.foo()
decorating function in  Bar
in foo
Run Code Online (Sandbox Code Playgroud)

另外,请参阅 Bruce Eckel 关于装饰器的页面。