7


8

Pythonでクラス定義に適用されるデコレーター

関数に適用されたデコレーターと比較して、クラスに適用されたデコレーターを理解することは容易ではありません。

@foo
class Bar(object):
    def __init__(self, x):
        self.x = x
    def spam(self):
        statements

クラスへのデコレータの使用例は何ですか? どうやって使うのですか?

2 Answer


23


これは、はるかに簡単な方法で、カスタムメタクラスの古典的な優れた用途の大部分を置き換えます。

このように考えてください:本体の実行が完了するまでクラスオブジェクトは存在しないため、クラス本体に直接あるものはクラスオブジェクトを参照できません(クラスオブジェクトを作成するのはメタクラスの仕事です-通常は `type ’s、カスタムメタクラスのないすべてのクラス)。

しかし、クラスデコレータのコードは、クラスオブジェクトが作成された後(実際、クラスオブジェクトを引数として!)実行されるため、そのクラスオブジェクトを完全に適切に参照できます(通常はそうする必要があります)。

たとえば、

def enum(cls):
  names = getattr(cls, 'names', None)
  if names is None:
    raise TypeError('%r must have a class field `names` to be an `enum`!',
                    cls.__name__)
  for i, n in enumerate(names):
    setattr(cls, n, i)
  return cls

@enum
class Color(object):
  names = 'red green blue'.split()

これで、 0、` 1`などではなく、 Color.red、` Color.green`、&cを参照できます。 (もちろん、「enum」を作成するためにさらに多くの機能を追加しますが、ここでは、カスタムメタクラスを必要とせずに、このような機能をクラスデコレータに追加する簡単な方法を示します!-)


6


私が考えることができる1つのユースケースは、1つの関数デコレータでクラスのすべてのメソッドをラップしたい場合です。 次のデコレータがあるとします:

def logit(f):
    def res(*args, **kwargs):
        print "Calling %s" % f.__name__
        return f(*args, **kwargs)
    return res

そして次のクラス:

>>> class Pointless:
    def foo(self): print 'foo'
    def bar(self): print 'bar'
    def baz(self): print 'baz'

>>> p = Pointless()
>>> p.foo(); p.bar(); p.baz()
foo
bar
baz

すべてのメソッドを装飾できます:

>>> class Pointless:
    @logit
    def foo(self): print 'foo'
    @logit
    def bar(self): print 'bar'
    @logit
    def baz(self): print 'baz'

>>> p = Pointless()
>>> p.foo(); p.bar(); p.baz()
Calling foo
foo
Calling bar
bar
Calling baz
baz

しかし、それはラメです! 代わりにこれを行うことができます。

>>> def logall(cls):
for a in dir(cls):
    if callable(getattr(cls, a)):
        setattr(cls, a, logit(getattr(cls, a)))
return cls

>>> @logall
class Pointless:
    def foo(self): print 'foo'
    def bar(self): print 'bar'
    def baz(self): print 'baz'

>>> p = Pointless()
>>> p.foo(); p.bar(); p.baz()
Calling foo
foo
Calling bar
bar
Calling baz
baz

更新: `logall`のより一般的なバージョン:

>>> def wrapall(method):
    def dec(cls):
        for a in dir(cls):
            if callable(getattr(cls, a)):
                setattr(cls, a, method(getattr(cls, a)))
        return cls
    return dec

>>> @wrapall(logit)
class Pointless:
        def foo(self): print 'foo'
        def bar(self): print 'bar'
        def baz(self): print 'baz'

>>> p = Pointless()
>>> p.foo(); p.bar(); p.baz()
Calling foo
foo
Calling bar
bar
Calling baz
baz
>>>

完全な開示:これを行う必要はなかったため、この例を作成しました。