在Python中应用于类定义的装饰器

7 投票
2 回答
906 浏览
提问于 2025-04-16 05:00

跟给函数加装饰器相比,给类加装饰器就不太好理解了。

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

那给类加装饰器有什么用呢?怎么用呢?

2 个回答

6

我能想到的一个用例是,如果你想用一个函数装饰器来包装一个类的所有方法。比如你有以下这个装饰器:

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

需要说明的是:我其实从来没有做过这个,只是随便举个例子。

23

这段话的意思是,用一种更简单的方法替代了大多数传统上使用自定义元类的好方法。

可以这样理解:在类的主体中,任何东西都不能直接引用类对象,因为类对象在类的主体运行完之前是不存在的(创建类对象的工作是元类的职责,通常是 type,对于没有自定义元类的所有类来说都是这样)。

但是,类装饰器中的代码是在类对象创建之后运行的(实际上,它的参数就是这个类对象!),所以它可以很方便地引用这个类对象(而且通常需要这样做)。

举个例子,考虑以下代码:

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()

这样你就可以引用 Color.redColor.green 等,而不是 01 等数字。(当然,通常你会添加更多功能来实现“一个 enum”,但这里我只是展示了如何简单地在类装饰器中添加这样的功能,而不需要自定义元类!)

撰写回答