问题如下。有一个基类将通过 可扩展的几个类。你知道吗
所有这些类都需要初始化某些类变量。根据问题的性质,初始化应该是增量的和间接的。“用户”(编写基本扩展的程序员)可能希望“添加”某些“配置”变量,这些变量可能有(布尔)属性“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
。
不管怎样,我主要关心的是元类解决方案的适当性。
注:该问题被“搁置”,显然是因为一些成员不明白我在问什么。在我看来这已经足够清楚了。 但我要重新回答我的问题:
这是元类的合理使用吗?
我的BaseMeta
实现是否正确?(不,我不是在问“有用吗?”是的。我在问“是否符合
“惯例?”)。
xyres没有问题。他分别回答了“是”和“否”,并提出了有益的意见和建议。我接受了他的回复(在他发布后几个小时)。你知道吗
我们现在快乐吗?你知道吗
通常,元类用于执行以下操作:
__new__
方法完成。你知道吗__init__
方法完成。你知道吗__call__
方法完成。你知道吗当我编写操纵时,我的意思是在类上设置一些属性或方法,或者在创建类时调用一些方法,等等
在您的问题中,您提到每当创建继承
Base
的类时,都需要调用initClassBegin/End
。这听起来像是使用元类的完美案例。你知道吗不过,有几个地方我想纠正你:
重写
__init__
,而不是__new__
。你知道吗在
__new__
内部调用type.__new__(...)
,它返回一个类。这意味着您实际上是在类创建之后而不是之前操作它。所以,最好的方法是__init__
。使
initClassBegin/End
私有化。你知道吗既然你提到你是Python的新手,我想我应该指出这一点。您提到用户/程序员不需要知道
initClassBegin
和iniClassEnd
方法。那么,为什么不把它们保密呢?只需在下划线前面加上前缀,就完成了:_initClassBegin
和_initClassEnd
现在是私有的。我发现这篇博文很有帮助:Python metaclasses by example。作者提到了一些使用元类的用例。你知道吗
相关问题 更多 >
编程相关推荐