Python 2.7 - 这是对 __metaclass__ 的有效使用吗? [关闭]

Posted

技术标签:

【中文标题】Python 2.7 - 这是对 __metaclass__ 的有效使用吗? [关闭]【英文标题】:Python 2.7 - Is this a valid use of __metaclass__? [closed] 【发布时间】:2016-06-10 18:43:07 【问题描述】:

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

所有这些类都需要初始化某些类变量。根据问题的性质,初始化应该是增量的和间接的。 “用户”(编写基本扩展的程序员)可能想要“添加”某些“配置”变量,这些变量可能有也可能没有(布尔)属性“xdim”,并为它们提供默认值。这将存储在类变量中的方式取决于实现。用户应该能够说“添加这些配置变量,使用这些默认值和这个 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):
            ...

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

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

class BaseX(Base):
    ...

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

我觉得这种丑。所以我在阅读元类,我意识到它们可以解决这种问题:

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

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

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 个月左右),我已经看到有关元类是要避免的黑暗艺术的警告。他们对我来说似乎并不那么神秘,但我想我会问。 这是对元类的合理使用吗?它甚至是正确的?

注意:关于正确性的问题最初不在我的脑海中。发生的事情是我的第一个实现似乎工作,但它是微妙的错误。我自己发现了错误。这不是拼写错误,而是由于没有完全理解元类是如何工作的;这让我想到我可能还缺少其他东西,所以我不明智地问:“它甚至正确吗?”我没有要求任何人测试我的代码。我应该说“你觉得这种方法有问题吗?”

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

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

Base 的初始化不会出现该问题;那会很好用。但是从 Base 派生的类将失败,因为该类将从 Base 类中获取其元类,即type,而不是BaseMeta

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

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

    这是对元类的合理使用吗?

    我的BaseMeta 实现是否正确正确? (不,我不是在问“它有效吗?”;它确实有效。我在问“它是否符合 通常的做法?”)。

xyres 没有问题。他分别回答“是”和“否”,并提供了有用的 cmets 和建议。我接受了他的回复(在他发布几小时后)。

我们现在快乐吗?

【问题讨论】:

如果这是您认为可以改进的工作代码,请考虑Code Review。 不是。我从工作代码中提取的一些行。其他我刚刚输入的;可能有错误。我的问题更多是关于这是一个适当的解决方案,以及它是否正确,除了琐碎的错误。 如果你不知道它是否正确,测试它。一旦它工作了,如果你想知道它是否合适,把它带到代码审查(使用你的实际代码,而不仅仅是 sn-ps) 正如我所说,我的(简单)测试有效。我担心的是关于不要弄乱元类的警告。无论如何,忘记正确性。我将编辑原始帖子以解释我什至问的原因。 好吧,显然你想知道,所以据此你不需要。 【参考方案1】:

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

    在创建类之前对其进行操作。通过覆盖 __new__ 方法完成。 在创建类之后对其进行操作。通过覆盖 __init__ 方法完成。 每次调用一个类来操作它。通过覆盖 __call__ 方法完成。

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

在您的问题中,您提到每当创建继承Base 的类时,您都需要调用initClassBegin/End。这听起来像是使用元类的完美案例。


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

    覆盖__init__ 而不是__new__

    __new__ 内部,您正在调用type.__new__(...),它返回一个类。这意味着您实际上是在在创建类之后而不是在之前操作它。所以,最好的地方是__init__

    initClassBegin/End设为私有。

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

我发现这篇博文很有帮助:Python metaclasses by example。作者提到了一些您希望使用元类的用例。

【讨论】:

这很有帮助!另外,我不需要initClassBegin 中的inspect.getmro 行; BaseMeta.__init__ 获取类的基础,所以我可以通过 clsbases[0]

以上是关于Python 2.7 - 这是对 __metaclass__ 的有效使用吗? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

Python 2.7_pandas连接MySQL数据处理_20161229

Python 2.7_Second_try_爬取阳光电影网_获取电影下载地址并写入文件 20161207

Python 2.7_多进程获取简书专题数据

Python 2.7_发送简书关注的专题作者最新一篇文章及连接到邮件_20161218

python定义接口继承类invalid syntax解决办法

__future__ 模块在 Python 2.7 中如何工作? [复制]