こんにちは、次のようなものがあります。基本的に、定義内のインスタンスメソッドで使用されるデコレータからインスタンスメソッドのクラスにアクセスする必要があります。
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デコレータ -しかしこれらは最初のパラメーターを取得することにより、実行時にインスタンスを取得する回避策に依存します。私の場合は、クラスから収集した情報に基づいてメソッドを呼び出すため、呼び出しが来るのを待つことはできません。
ありがとうございました。
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__
がAttributeErrorになったため、関数のfunc.foo = 1
属性に直接アクセスする必要があります。
マークが示唆するように:
このコードは、自動後処理を使用してこれがどのように機能するかを示しています。
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}
この例では次のことに注意してください。
お役に立てれば
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 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__
が呼び出されます。 「クラスオブジェクトの作成」に関するドキュメント を参照できます。
以下に簡単な例を示します。
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
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
問題は、デコレータが呼び出されたときにクラスがまだ存在しないことです。これを試して:
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
あなたが見るように、あなたはあなたがしたいことをする別の方法を見つけ出す必要があります。
これは古い質問ですが、金星に出会いました。 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()
関数は、デコレータコードの実行時に、定義ポイントのメソッドであるかどうかを知りません。クラス/インスタンス識別子を介してアクセスされた場合のみ、クラス/インスタンスを認識できます。この制限を克服するには、記述子オブジェクトで修飾して、アクセス/呼び出し時間まで実際の修飾コードを遅らせることができます。
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'}
デコレータが返す装飾されたメソッドでメソッドが呼び出されているオブジェクトのクラスにアクセスできます。そのようです:
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'>