在类声明内部调用的函数是如何工作的?

3

以下是代码:

>>> class Foo:
...     zope.interface.implements(IFoo)
...
...     def __init__(self, x=None):
...         self.x = x
...
...     def bar(self, q, r=None):
...         return q, r, self.x
...
...     def __repr__(self):
...         return "Foo(%s)" % self.x

显然,某种方式上zope.interface.implements的调用会改变类Foo的属性和行为。
这是如何发生的?我该如何在我的代码中使用这种方法?
示例代码是zope.interface模块的一部分。

2
值得注意的是,从我在源代码中所看到的 - 这种方法已经被弃用了,现在更推荐使用@implementer装饰器。 - Gareth Latty
2个回答

6

详细的“发生了什么”

zope.interface.implements()函数检查帧栈并更改正在构建的类的locals()命名空间(一个Python dict)。Python中class语句中的所有内容都在该命名空间中执行,结果形成类主体。

该函数向类命名空间添加了一个额外的值__implements_advice_data__,其中包含一些数据(您传递给函数的接口以及classImplements可调用项,稍后将使用该项)。

然后,它要么添加要么链接到要处理的类的元类,通过在命名空间中添加(或更改预先存在的)__metaclass__键来实现。这确保在将来每次创建类的实例时,现在安装的元类将首先被调用。

事实上,这个元类(类顾问)有点狡猾;它在第一次创建实例之后就会自动删除。它只是调用__implements_advice_data__指定的回调函数以及您传递给原始implements()函数的接口,在从类中删除__metaclass__键或将其替换为原始的__metaclass__(它调用以创建第一个类实例的元类)之后。回调函数清理自己,从类中删除__implements_advice_data__属性。

简短的版本

总之,zope.interface.implements()所做的所有工作都是:

  • 将传递的接口与回调一起添加到类的特殊属性(__implements_advice_data__)中。
  • 使用特殊元类确保在第一次创建实例时调用回调。

最终,这相当于像这样定义您的接口:

class Foo:
    def __init__(self, x=None):
        self.x = x

    def bar(self, q, r=None):
        return q, r, self.x

    def __repr__(self):
        return "Foo(%s)" % self.x

zope.interface.classImplements(Foo, IFoo)

除了最后一次调用被推迟到您首次创建 Foo 实例之前,没有其他不同。

但为什么要这样做呢?

zope.interface 首次开发时,Python还没有类装饰器。

zope.interface.classImplements()需要单独调用作为函数,类已被创建后,而在类体内的 zope.interface.implements() 调用提供了关于类实现的接口的更好的文档。您可以将其放置在类声明的顶部,每个人都可以在查看类时看到这个重要的信息。将 classImplements() 调用放置在类声明之后,不太明显和清晰,对于长的类定义,它很容易被完全忽略。

PEP 3129 最终增加了类装饰器到语言中,并在 Python 2.6 和 3.0中添加了它们; zope.interface 是在 Python 2.3时期首次开发的(如果我没记错)。

现在我们有了类装饰器, zope.interface.implements() 已被弃用,您可以使用 zope.interface.implementer 类装饰器代替:

@zope.interface.implementer(IFoo)
class Foo:
    def __init__(self, x=None):
        self.x = x

    def bar(self, q, r=None):
        return q, r, self.x

    def __repr__(self):
        return "Foo(%s)" % self.x

2

Read the source, luke:

http://svn.zope.org/zope.interface/trunk/src/zope/interface/declarations.py?rev=124816&view=markup

def _implements(name, interfaces, classImplements):
    frame = sys._getframe(2)
    locals = frame.f_locals

    # Try to make sure we were called from a class def. In 2.2.0 we can't
    # check for __module__ since it doesn't seem to be added to the locals
    # until later on.
    if (locals is frame.f_globals) or (
        ('__module__' not in locals) and sys.version_info[:3] > (2, 2, 0)):
        raise TypeError(name+" can be used only from a class definition.")

    if '__implements_advice_data__' in locals:
        raise TypeError(name+" can be used only once in a class definition.")

    locals['__implements_advice_data__'] = interfaces, classImplements
    addClassAdvisor(_implements_advice, depth=3)

这只是zope.interface所演奏的整个舞蹈中的一小部分。 - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接