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

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参数对函数对象进行注释,然后在元类的__new__方法中重新修饰函数。您需要直接访问函数的__dict__属性,至少对于我来说,func.foo = 1导致了attributeerRor。


正如马克所说:

  • 在类生成之前调用任何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,您可以使用object.__set_name__以非常简单的方式完成这一任务。该文档指出,__set_name__是"在创建所属类所有者时调用的"。下面是一个例子:

    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

    如果您想了解更多关于如何创建类的信息,特别是在调用__set_name__时,您可以参考有关"创建类对象"的文档。


    下面是一个简单的例子:

    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将使用register类方法注册类)来实际包装该方法。

    您可以重用这个模式,这次使用一个元类,以便在导入时包装该方法。

    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/最新版本/

    它似乎具有修饰方法的能力,并且在这样做的同时允许您访问类和方法。注:调用setattr(ob, wrapped.__name__, decorated)并不是使用金星人的典型方式,在某种程度上破坏了这个目的。

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

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