web-dev-qa-db-ja.com

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デコレータは関数がクラスに属していることを忘れさせます および Get class in Pythonデコレータ -しかしこれらは最初のパラメーターを取得することにより、実行時にインスタンスを取得する回避策に依存します。私の場合は、クラスから収集した情報に基づいてメソッドを呼び出すため、呼び出しが来るのを待つことはできません。

ありがとうございました。

96
Carl G

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」属性を追加することにより、メソッドを対象のメソッドとしてマークします。関数とメソッドもオブジェクトなので、追加のメタデータをそれらに追加できます。

クラスが作成された後、クラスデコレータはすべてのメソッドを通過し、マークされたメソッドで必要なことをすべて実行します。

すべてのメソッドに影響を与えたい場合は、メソッドデコレータを省略して、クラスデコレータのみを使用できます。

60
Dave Kirby

他の人が指摘したように、デコレータが呼び出された時点ではクラスは作成されていません。 ただし、関数オブジェクトにデコレータパラメータで注釈を付けてから、メタクラスの__new__メソッドで関数を再装飾することができます。少なくとも私にとっては、__dict__がAttributeErrorになったため、関数のfunc.foo = 1属性に直接アクセスする必要があります。

15
Mark Visser

マークが示唆するように:

  1. デコレータは、クラスが構築される前に呼び出されるため、デコレータには認識されません。
  2. tagこれらのメソッドを使用して、必要な後処理を後で行うことができます。
  3. 後処理には2つのオプションがあります。クラス定義の最後、またはアプリケーションが実行される前の場所です。基本クラスを使用する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}

この例では次のことに注意してください。

  1. 任意のパラメーターで任意の関数に注釈を付けることができます。
  2. 各クラスには、独自の公開メソッドがあります。
  3. 公開されたメソッドも継承できます。
  4. 公開機能が更新されると、メソッドがオーバーライドされる可能性があります。

お役に立てれば

6

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

また、 デコレータに関するBruce Eckelのページを参照してください。

4
Ross Rogers

python 3.6から object.__set_name__ これを非常に簡単な方法で実現します。ドキュメントには、__set_name__は、「所有クラスownerが作成されたときに呼び出されます」。以下に例を示します。

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__が呼び出されます。 「クラスオブジェクトの作成」に関するドキュメント を参照できます。

4
tyrion

以下に簡単な例を示します。

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

flask-classy が行うことは、メソッドに保存する一時キャッシュを作成し、別のものを使用することです(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.__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

3
charlax

問題は、デコレータが呼び出されたときにクラスがまだ存在しないことです。これを試して:

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

あなたが見るように、あなたはあなたがしたいことをする別の方法を見つけ出す必要があります。

3
Ants Aasma

これは古い質問ですが、金星に出会いました。 http://venusian.readthedocs.org/en/latest/

メソッドをデコレートし、クラスとメソッドの両方にアクセスできるようにします。 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 __== '__main__':
    t = Foo()
    t.bar()
1
eric.frederich

関数は、デコレータコードの実行時に、定義ポイントのメソッドであるかどうかを知りません。クラス/インスタンス識別子を介してアクセスされた場合のみ、クラス/インスタンスを認識できます。この制限を克服するには、記述子オブジェクトで修飾して、アクセス/呼び出し時間まで実際の修飾コードを遅らせることができます。

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

デコレータが返す装飾されたメソッドでメソッドが呼び出されているオブジェクトのクラスにアクセスできます。そのようです:

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'>
0
Will McCutchen