Python 2.7 - 这是对 __metaclass__ 的有效使用吗?

Python 2.7 - Is this a valid use of __metaclass__?

问题如下。有一个 Base class 将被扩展 几个 class 也可以扩展。

所有这些 class 都需要初始化某些 class 变量。根据问题的性质,初始化应该是增量和间接的。 "user"(编写 Base 扩展的程序员)可能想要 "add" 某些 "config" 变量,这些变量可能有也可能没有(布尔)属性 "xdim",并为它们提供默认值。这将存储在 class 变量中的方式取决于实现。用户应该能够说 "add these config vars, with these defaults, and this xdim" 而无需关心这些细节。

考虑到这一点,我定义了辅助方法,例如:

class Base(object):
    @classmethod
    def addConfig(cls, xdim, **cfgvars):
        """Adds default config vars with identical xdim."""
        for k,v in cfgvars.items():
            cls._configDefaults[k] = v
        if xdim:
            cls._configXDims.update(cfgvars.keys())

(还有addConfig等几种方法。)

初始化必须有始有终,所以:

import inspect
class Base(object):
    @classmethod
    def initClassBegin(cls):
        if cls.__name__ == 'Base':
            cls._configDefaults = {}
            cls._configXDims = set()
            ...
        else:
            base = inspect.getmro(cls)[1]
            cls._configDefaults = base._configDefaults.copy()
            cls._configXDims = base._configXDims.copy()
            ...

    @classmethod
    def initClassEnd(cls):
        ...
        if 'methodX' in vars(cls):
            ...

这里有两个烦人的问题。一方面,这些方法中的 none 可以在 class 体内调用,因为 class 还不存在。此外,初始化必须正确地开始和结束(忘记开始只会引发异常;忘记结束会产生不可预知的结果,因为某些扩展的 class 变量可能会透漏)。此外,用户 必须 开始和结束初始化 即使没有任何东西要初始化 (因为 initClassEnd 执行一些基于存在派生的某些方法class).

派生的初始化 class 将如下所示:

class BaseX(Base):
    ...

BaseX.initClassBegin()
BaseX.addConfig(xdim=True, foo=1, bar=2)
BaseX.addConfig(xdim=False, baz=3)
...
BaseX.initClassEnd()

我觉得这种丑陋。所以我在阅读有关 metaclasses 的文章,我意识到它们可以解决此类问题:

class BaseMeta(type):
    def __new__(meta, clsname, clsbases, clsdict):
        cls = type.__new__(meta, clsname, clsbases, clsdict)
        cls.initClassBegin()
        if 'initClass' in clsdict:
            cls.initClass()
        cls.initClassEnd()
        return cls

class Base(object):
    __metaclass__ = BaseMeta
    ...

现在我要求用户提供一个可选的 class 方法 initClass 并在里面调用 addConfig 和其他初始化 class 方法:

class BaseX(Base):
    ...

    @classmethod
    def initClass(cls):
        cls.addConfig(xdim=True, foo=1, bar=2)
        cls.addConfig(xdim=False, baz=3)
        ...

用户甚至不需要知道initClassBegin/End的存在。

这在我写的一些简单的测试用例中工作正常,但我是 Python 的新手(6 个月左右),我已经看到关于 metaclasses 是黑暗艺术的警告避免。他们对我来说似乎并不那么神秘,但我想问问。 这是对 metaclasses 的合理使用吗?它甚至是正确的?

注意:关于正确性的问题最初并不在我的脑海中。发生的事情是,我的第一个实施似乎有效,但它有细微的错误。我自己发现了这个错误。这不是打字错误,而是没有完全理解 metaclasses 是如何工作的结果;这让我想到我可能还遗漏了其他东西,所以我不明智地问了 "Is it even correct?" 我并没有要求任何人测试我的代码。我应该说 "Do you see a problem with this approach?"

顺便说一句,错误是最初我没有定义一个合适的 BaseMeta class,而只是一个函数:

def baseMeta(clsname, clsbases, clsdict):
   cls = type.__new__(type, clsname, clsbases, clsdict)
   ...

问题不会出现在Base的初始化中;那会很好用。但是从 Base 派生的 class 将失败,因为 class 将从 Base 的 class 中获取其元 class,即 type,而不是 [=23] =].

无论如何,我主要关心的是(现在也是)metaclass 解决方案的适当性。

注意:问题被放置在"on hold",显然是因为有些成员不明白我在问什么。在我看来,这已经足够清楚了。 但我会改写我的问题:

  1. 这是对 metaclasses 的合理使用吗?

  2. 我的 BaseMeta 实现是否正确? (不,我不是在问 "Does it work?";它确实是。我在问“是否符合 通常的做法?”)。

xyres 做题没问题。他分别回答了 'yes' 和 'no',并提供了有益的意见和建议。我接受了他的回复(在他发布后几个小时)。

我们现在幸福吗?

通常,metaclasses 用于执行以下操作:

  1. 在 class 创建之前对其进行操作 。通过覆盖 __new__ 方法完成。
  2. 在 class 创建后对其进行操作 。通过覆盖 __init__ 方法完成。
  3. 每次调用 class 来操纵它。通过覆盖 __call__ 方法完成。

当我写manipulate时,我的意思是在class上设置一些属性或方法,或者在它创建时调用一些方法等

在您的问题中,您提到每当创建继承 Base 的 class 时都需要调用 initClassBegin/End。这听起来像是使用 metaclasses.

的完美案例

虽然,有几个地方我想纠正你:

  1. 覆盖 __init__ 而不是 __new__

    __new__ 中,您正在调用 type.__new__(...),其中 returns 是 class。这意味着您实际上是在 class 创建之后 而不是之前操作它。所以,更好的地方是 __init__.

  2. initClassBegin/End 设为私有。

    既然您提到您是 Python 的新手,我想我应该指出这一点。您提到 user/programmer 不需要了解 initClassBegininiClassEnd 方法。那么,为什么不将它们设为私有呢?只需在下划线前加上前缀即可:_initClassBegin_initClassEnd 现在是私有的。

我发现此博客 post 非常有帮助:Python metaclasses by example。作者提到了一些您想使用 metaclasses.

的用例