2

假设我有一堂课

class Foo:
    def __init__(self):
        pass
    def meth1(self, *args):
        do_stuff()
    def meth2(self, **kwargs):
        more_stuff()
    def callme(self):
        print "I'm called!"

我想巧妙地修改它,以便callme()在执行之后调用它meth1meth2以及其他所有内容。我怎么做?我从来没有使用过类装饰器或元类,所以我不确定哪种方法是正确的,以及如何正确使用它们。

对于元类,我整理了以下内容:

from types import FunctionType
def addcall_meta(name, bases, dct):
    newdct = dict(dct)
    for k, v in dct.items():
        if isinstance(v, FunctionType) and k not in ('__metaclass__', 'callme'):
            # the above can include more filters such as not startswith, etc.
            print k, 'is a function!'
            def newmethod(self, *args, **kwargs):
                v(self, *args, **kwargs)
                dct['callme'](self)
            newdct[k] = newmethod
    return type(name, bases, newdct)

使用类装饰器:

def addcall_deco(cls):
    dct = {}
    for k, v in cls.__dict__.items():
        if isinstance(v, FunctionType) and k != 'callme':
            def newv(self, *args, **kwargs):
                v(self, *args, **kwargs)
                self.callme()
            cls.k = newv
    return cls

两者似乎都适用于我的超级简单示例:

In [75]: Foo()
I'm called!
Out[75]: <__main__.Foo instance at 0x21ed680>

是否有明显的理由使用一个而不是另一个(例如特定的极端情况等)?我也认为我可以以某种方式使用__new__,但从常识的角度来看,我想修改类,而不是它的所有实例。

4

2 回答 2

2

旧版本的 Python 不支持类装饰器,因此如果考虑向后兼容性,则需要使用元类或将类装饰器转换为包装类的函数调用。

于 2012-09-01T16:46:16.493 回答
1

实际上,您在这里使用元类作为类装饰器 - 并不真正需要元类允许的更灵活的东西。

因此,两种实现都是相同的,您应该使用更简单的方法——即类装饰器。至于“向后兼容性” - 这不是您在新项目中应该担心的问题。

当然,由于“长期支持”版本,很多服务器都在运行一些非常旧的 Linux,并且具有 2.4 或 2.5 的 Python 版本 - 但如果您的项目确实在这么旧的服务器中使用,那么用户始终可以像普通用户一样安装更新到足以支持 Python 的 Python - 无需使用系统 Python。

附带说明:请注意您的包装器,如果有的话,您将丢弃包装的方法返回值。这样做的方法是:

 def newv(self, *args, **kwargs):
      result = v(self, *args, **kwargs)
      self.callme()
      return result

最后,如果您认为在您处理的任何项目中都非常需要这种方法,请查看“面向方面的编程” - 有几个项目允许在 Python 中使用面向方面的方法,而无需您的工具链或工作方式发生了许多变化。

于 2012-09-07T22:29:19.763 回答