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 新手,我想我应该指出这一点。您提到用户/程序员不需要了解 initClassBegin
和 iniClassEnd
方法。那么,为什么不将它们设为私有呢?只需在下划线前加上前缀即可:_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_发送简书关注的专题作者最新一篇文章及连接到邮件_20161218