Python2.7这是对元类的有效使用吗?

2024-06-17 15:11:23 发布

您现在位置:Python中文网/ 问答频道 /正文

问题如下。有一个基类将通过 可扩展的几个类。你知道吗

所有这些类都需要初始化某些类变量。根据问题的性质,初始化应该是增量的和间接的。“用户”(编写基本扩展的程序员)可能希望“添加”某些“配置”变量,这些变量可能有(布尔)属性“xdim”,也可能没有(布尔)属性“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

不管怎样,我主要关心的是元类解决方案的适当性。

注:该问题被“搁置”,显然是因为一些成员不明白我在问什么。在我看来这已经足够清楚了。 但我要重新回答我的问题:

  1. 这是元类的合理使用吗?

  2. 我的BaseMeta实现是否正确?(不,我不是在问“有用吗?”是的。我在问“是否符合 “惯例?”)。

xyres没有问题。他分别回答了“是”和“否”,并提出了有益的意见和建议。我接受了他的回复(在他发布后几个小时)。你知道吗

我们现在快乐吗?你知道吗


Tags: 方法用户baseifdeftypeclasscls
1条回答
网友
1楼 · 发布于 2024-06-17 15:11:23

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

  1. 在创建类之前操纵类。通过重写__new__方法完成。你知道吗
  2. 在类创建后操纵类。通过重写__init__方法完成。你知道吗
  3. 每次调用类时都要操纵它。通过重写__call__方法完成。你知道吗

当我编写操纵时,我的意思是在类上设置一些属性或方法,或者在创建类时调用一些方法,等等

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


不过,有几个地方我想纠正你:

  1. 重写__init__,而不是__new__。你知道吗

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

  2. 使initClassBegin/End私有化。你知道吗

    既然你提到你是Python的新手,我想我应该指出这一点。您提到用户/程序员不需要知道initClassBegininiClassEnd方法。那么,为什么不把它们保密呢?只需在下划线前面加上前缀,就完成了:_initClassBegin_initClassEnd现在是私有的。

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

相关问题 更多 >