首页 »Python » python网络编程:Python 中的元类编程(3) »正文
python网络编程:Python 中的元类编程(3)
来源: 发布时间:星期四, 2009年1月8日 浏览:56次 评论:0
介绍 去年 ![](/icons/85071dou.gif) 我参加了 EuroPython 2006 会议 ![](/icons/85071dou2.gif) 这个会议非常好 ![](/icons/85071dou.gif) 组织得很完美 ![](/icons/85071dou.gif) 谈话都具有很高 ![](/icons/85071de.gif) 水平 ![](/icons/85071dou.gif) 人们也都特别友好 ![](/icons/85071dou2.gif) 然而 ![](/icons/85071dou.gif) 我在这篇文章归属 ![](/icons/85071de.gif) Python 社区中注意到了 ![](/icons/85071yi.gif) 种令人烦恼 ![](/icons/85071de.gif) 趋势 ![](/icons/85071dou2.gif) 几乎同时 ![](/icons/85071dou.gif) 我 ![](/icons/85071de.gif) 合著者 David Mertz 也在研究 ![](/icons/85071yi.gif) 个类似 ![](/icons/85071de.gif) 有关 ![](/icons/85071yi.gif) 些提交给 Gnosis Utilities ![](/icons/85071de.gif) 补丁 ![](/icons/85071chengxu.gif) ![](/icons/85071de.gif) 问题 ![](/icons/85071dou2.gif) 这种有争议 ![](/icons/85071de.gif) 趋势就是趋向于耍小聪明 ![](/icons/85071dou2.gif) 不幸 ![](/icons/85071de.gif) 是 ![](/icons/85071dou.gif) Python 社区 ![](/icons/85071de.gif) 这种聪明以前只局限于 Zope 和 Twisted ![](/icons/85071dou.gif) 现在已变得无处不在 ![](/icons/85071dou2.gif) 我们在试验项目和学习过程中并不反对这种聪明 ![](/icons/85071dou2.gif) 我们 ![](/icons/85071de.gif) 烦恼是 ![](/icons/85071dou.gif) 在产品框架上必须符合用户 ![](/icons/85071de.gif) 要求 ![](/icons/85071dou2.gif) 在本文中 ![](/icons/85071dou.gif) 我们希望为避免这种聪明做出小小 ![](/icons/85071de.gif) 贡献 ![](/icons/85071dou.gif) 至少在我们比较精通 ![](/icons/85071de.gif) 领域避免元类滥用 ![](/icons/85071dou2.gif) 对于本文 ![](/icons/85071dou.gif) 我们坚持严肃 ![](/icons/85071de.gif) 立场:我们把在不用元类也能解决问题 ![](/icons/85071de.gif) 情况下使用元类都视为元类滥用 ![](/icons/85071dou2.gif) 当然 ![](/icons/85071dou.gif) 作者 ![](/icons/85071de.gif) 过错也很明显:我们 ![](/icons/85071de.gif) 有关 Python 中 ![](/icons/85071de.gif) 元类 ![](/icons/85071de.gif) 前几部分 助长了这种做法 ![](/icons/85071de.gif) 流行 ![](/icons/85071dou2.gif) Nostra culpa ![](/icons/85071dou2.gif) 使用元编程最普通 ![](/icons/85071de.gif) 情况就是创建具有动态生成 ![](/icons/85071de.gif) 属性和思路方法 ![](/icons/85071de.gif) 类 ![](/icons/85071dou2.gif) 跟流行 ![](/icons/85071de.gif) 观点相反 ![](/icons/85071dou.gif) 这是 ![](/icons/85071yi.gif) 个在大多数时候都不需要 而且不想要 自定义元类 ![](/icons/85071de.gif) 工作 ![](/icons/85071dou2.gif) 本文适用于两类读者:普通 ![](/icons/85071chengxu.gif) 员和聪明 ![](/icons/85071de.gif) ![](/icons/85071chengxu.gif) 员 ![](/icons/85071dou2.gif) 前者知道 ![](/icons/85071yi.gif) 些元编程窍门技巧 ![](/icons/85071dou.gif) 但是并没有在大脑中形成具体 ![](/icons/85071de.gif) 概念;后者很聪明 ![](/icons/85071dou.gif) 而且理解得深 ![](/icons/85071yi.gif) 些 ![](/icons/85071dou2.gif) 后者 ![](/icons/85071de.gif) 问题在于变得聪明很容易 ![](/icons/85071dou.gif) 要变得不那么聪明就得花不少时间了 ![](/icons/85071dou2.gif) 例如 ![](/icons/85071dou.gif) 花几个月时间就能理解如何使用元类 ![](/icons/85071dou.gif) 但是要花几年时间才能明白如何不 使用它们 ![](/icons/85071dou2.gif) 有关类 ![](/icons/85071chushi.gif) 化 创建类 ![](/icons/85071de.gif) 过程中 ![](/icons/85071dou.gif) 类 ![](/icons/85071de.gif) 所有属性和思路方法只设置 ![](/icons/85071yi.gif) 次 ![](/icons/85071dou2.gif) 而在 Python 中 ![](/icons/85071dou.gif) 思路方法和属性随时都可以更改 ![](/icons/85071dou.gif) 但是只有不遵守规则 ![](/icons/85071de.gif) ![](/icons/85071chengxu.gif) 员才会这样做 ![](/icons/85071dou2.gif) 在各种条件下 ![](/icons/85071dou.gif) 创建类时 ![](/icons/85071dou.gif) 也许想用比简单地运行静态编码更加动态 ![](/icons/85071de.gif) 思路方法 ![](/icons/85071dou2.gif) 例如 ![](/icons/85071dou.gif) 可能想根据从配置文件读取 ![](/icons/85071de.gif) 参数来设置 ![](/icons/85071yi.gif) 些默认 ![](/icons/85071de.gif) 类属性;或者想根据数据库表中 ![](/icons/85071de.gif) 字段来设置类特性 ![](/icons/85071dou2.gif) 利用强制方式动态自定义类行为最简单 ![](/icons/85071de.gif) 思路方法是:首先创建类 ![](/icons/85071dou.gif) 然后添加思路方法和属性 ![](/icons/85071dou2.gif)
例如 ![](/icons/85071dou.gif) Anand Pillai(我们熟悉 ![](/icons/85071de.gif) ![](/icons/85071yi.gif) 个优秀 ![](/icons/85071chengxu.gif) 员)提出了 ![](/icons/85071yi.gif) 个到 Gnosis Utilities ![](/icons/85071de.gif) 分包 gnosis.xml.object ![](/icons/85071if.gif) y ![](/icons/85071de.gif) 路径 ![](/icons/85071dou.gif) 该分包就是这么做 ![](/icons/85071de.gif) ![](/icons/85071dou2.gif) ![](/icons/85071yi.gif) 个专门用来保存 “xml 节点对象” ![](/icons/85071de.gif) 叫做 gnosis.xml.object ![](/icons/85071if.gif) y._XO_ ![](/icons/85071de.gif) 基类就被许多增强 ![](/icons/85071de.gif) 行为 “装饰” 成如下这样: 清单 1. 基类 ![](/icons/85071de.gif) 动态增强 attr(_XO_, 'orig_tagname', orig_tagname)
attr(_XO_, 'findelem', findelem)
attr(_XO_, 'XPath', XPath)
attr(_XO_, 'change_pcdata', change_pcdata)
attr(_XO_,'addChild',addChild)
您可能会非常合理地想到 ![](/icons/85071dou.gif) 也可以定义 XO 基类 ![](/icons/85071de.gif) 子类来实现同样 ![](/icons/85071de.gif) 增强 ![](/icons/85071dou2.gif) 在感觉上这是对 ![](/icons/85071de.gif) ![](/icons/85071dou.gif) 但 Anand 已经提供了 20 多种可能 ![](/icons/85071de.gif) 增强 ![](/icons/85071dou.gif) 并且 ![](/icons/85071yi.gif) 些特定 ![](/icons/85071de.gif) 用户可能想要其中 ![](/icons/85071de.gif) ![](/icons/85071yi.gif) 些增强 ![](/icons/85071dou.gif) 但不想要 另外 ![](/icons/85071yi.gif) 些增强 ![](/icons/85071dou2.gif) 有太多 ![](/icons/85071de.gif) 替代思路方法可以轻易地为每种增强情形创建子类 ![](/icons/85071dou2.gif) 尽管如此 ![](/icons/85071dou.gif) 上面 ![](/icons/85071de.gif) 代码未必是恰到好处 ![](/icons/85071de.gif) ![](/icons/85071dou2.gif) 应该用 ![](/icons/85071yi.gif) 个附加到 XO、行为是动态决定 ![](/icons/85071de.gif) 自定义元类来完成上述工作 ![](/icons/85071dou2.gif) 但是这又让我们回到了希望避免 ![](/icons/85071de.gif) 聪明过度(和不透明性)上 ![](/icons/85071dou2.gif) 上述问题 ![](/icons/85071de.gif) ![](/icons/85071yi.gif) 种干净、漂亮 ![](/icons/85071de.gif) 解决方案可能需要向 Python 添加类装饰器 ![](/icons/85071dou2.gif) 如果拥有这些装饰器 ![](/icons/85071dou.gif) 编写 ![](/icons/85071de.gif) 代码可能就像这样: 清单 2. 向 Python 添加类装饰器 features = [('XPath',XPath), ('addChild',addChild), ('is_root',is_root)] @enhance(features)
_XO_plus(gnosis.xml.object y._XO_): pass gnosis.xml.object y._XO_ = _XO_plus 然而 ![](/icons/85071dou.gif) 目前没有这种语法 ![](/icons/85071dou2.gif) 当元类变复杂时
表面上看本文除了大惊小怪的外 ![](/icons/85071dou.gif) 似乎毫无意义 ![](/icons/85071dou2.gif) 例如 ![](/icons/85071dou.gif) 为什么不直接把 XO 元类定义为 Enhance ![](/icons/85071dou.gif) 然后就 ![](/icons/85071yi.gif) 切 OK 了呢 ![](/icons/85071dou2.gif) Enhance.__init__ ![](/icons/85071kh.gif) 可以为所讨论 ![](/icons/85071de.gif) 特定用途添加所需 ![](/icons/85071de.gif) 任何功能 ![](/icons/85071dou2.gif) 可能看起来像这样: 清单 3. 将 XO 定义为 Enhance _XO_plus(gnosis.xml.object y._XO_): __meta __ = Enhance features = [('XPath',XPath), ('addChild',addChild)] gnosis.xml.object y._XO_ = _XO_plus
不幸 ![](/icons/85071de.gif) 是 ![](/icons/85071dou.gif) 当考虑到继承时 ![](/icons/85071dou.gif) 问题却没有这么简单 ![](/icons/85071dou2.gif) ![](/icons/85071yi.gif) 旦为基类定义了 ![](/icons/85071yi.gif) 个自定义元类 ![](/icons/85071dou.gif) 所有派生类都将继承此元类 ![](/icons/85071dou.gif) 所以 ![](/icons/85071chushi.gif) 化代码将魔法般隐式地在所有派生类上运行 ![](/icons/85071dou2.gif) 这在特定 ![](/icons/85071de.gif) 情形中可能还不错(例如 ![](/icons/85071dou.gif) 假设必须将所有类都注册到您自己 ![](/icons/85071de.gif) 框架中:使用元类可以确保不会忘记注册派生类) ![](/icons/85071dou.gif) 然而 ![](/icons/85071dou.gif) 在许多情况下则可能不喜欢这种行为 ![](/icons/85071dou.gif) ![](/icons/85071yinwei.gif) : 您相信显式比隐式更好 派生类具有跟基类相同 ![](/icons/85071de.gif) 动态类属性 ![](/icons/85071dou2.gif) 为每个派生类再次设置这些属性是 ![](/icons/85071yi.gif) 种浪费 ![](/icons/85071dou.gif) ![](/icons/85071yinwei.gif) 通过继承它们就会拥有这些属性 ![](/icons/85071dou2.gif) 如果 ![](/icons/85071chushi.gif) 化代码很慢或者需要大量 ![](/icons/85071de.gif) 计算 ![](/icons/85071dou.gif) 那么这 ![](/icons/85071yi.gif) 特性就显得特别重要 ![](/icons/85071dou2.gif) 也许会在元类代码中添加 ![](/icons/85071yi.gif) 个检查 ![](/icons/85071dou.gif) 以查看是否在父类中设置了这些属性 ![](/icons/85071dou.gif) 但是这样会增加负担 ![](/icons/85071dou.gif) 并且不会控制所有 ![](/icons/85071de.gif) 类 自定义元类将会使类有些不可思议和不标准:您肯定不想增加元类冲突、“ __slots__ ” 问题、跟扩展类( Zope )斗争和其他复杂问题 ![](/icons/85071de.gif) 几率 ![](/icons/85071dou2.gif) 元类比很多人认识到 ![](/icons/85071de.gif) 更加脆弱 ![](/icons/85071dou2.gif) 我们甚至在试验代码中用了 4年的后还很少在生产代码中使用它们 您觉得对于类 ![](/icons/85071chushi.gif) 化这类简单 ![](/icons/85071de.gif) 工作使用自定义元类是杀鸡用牛刀 ![](/icons/85071dou.gif) 所以想要使用 ![](/icons/85071yi.gif) 种更为简单 ![](/icons/85071de.gif) 解决方案
换句话说 ![](/icons/85071dou.gif) 只有当想在派生类上运行代码 ![](/icons/85071dou.gif) 又不想让用户注意到时 ![](/icons/85071dou.gif) 才应该使用自定义元类 ![](/icons/85071dou2.gif) 如果不属于这种情形 ![](/icons/85071dou.gif) 那就跳过元类 ![](/icons/85071dou.gif) 使您(和您 ![](/icons/85071de.gif) 用户) ![](/icons/85071de.gif) 生活更加惬意 ![](/icons/85071class.gif) initializer 装饰器 本文以下部分可能会被谴责为聪明过度 ![](/icons/85071dou2.gif) 但是聪明不应该加重用户 ![](/icons/85071de.gif) 负担 ![](/icons/85071dou.gif) 只应该加重我们作者 ![](/icons/85071de.gif) 负担 ![](/icons/85071dou2.gif) 读者可以做 ![](/icons/85071yi.gif) 些和我们假设 ![](/icons/85071de.gif) 理想类装饰器类似 ![](/icons/85071de.gif) 事情 ![](/icons/85071dou.gif) 但是要避免在元类思路方法中出现 ![](/icons/85071de.gif) 继承及元类冲突问题 ![](/icons/85071dou2.gif) 我们后面给出 ![](/icons/85071de.gif) “不可思议 ![](/icons/85071de.gif) ” 装饰器通常情况下只能增强直观 ![](/icons/85071de.gif) (但稍微有些难看 ![](/icons/85071de.gif) )强制思路方法 ![](/icons/85071dou.gif) 并且跟下面 ![](/icons/85071de.gif) 例子在 “精神上相当”: 清单 4. 强制思路方法 def Enhance(cls, **kw): for k, v in kw.iteritems : attr(cls, k, v)
ClassToBeInitialized(object): pass Enhance(ClassToBeInitialized, a=1, b=2) 上面 ![](/icons/85071de.gif) 强制增强器并不是很坏 ![](/icons/85071dou2.gif) 但是也有 ![](/icons/85071yi.gif) 些缺馅:它要求重复输入类名称;可读性不够理想 ![](/icons/85071dou.gif) ![](/icons/85071yinwei.gif) 类定义和类 ![](/icons/85071chushi.gif) 化是分开 ![](/icons/85071de.gif) —— 长 ![](/icons/85071de.gif) 类定义可能会漏掉最后 ![](/icons/85071yi.gif) 行;并且它会认为首先定义 ![](/icons/85071yi.gif) 些内容然后又立即更改是不对 ![](/icons/85071de.gif) ![](/icons/85071class.gif) initializer 装饰器提供了 ![](/icons/85071yi.gif) 个介绍说明性解决方案 ![](/icons/85071dou2.gif) 装饰器将 Enhance(cls,**kw) 转换为 ![](/icons/85071yi.gif) 个能够用于类定义中 ![](/icons/85071de.gif) 思路方法: 清单 5. 基本操作中神奇 ![](/icons/85071de.gif) 装饰器 >>> @ initializer # add magic to Enhance ... def Enhance(cls, **kw): ... for k, v in kw.iteritems : ... attr(cls, k, v) >>> ClassToBeInitialized(object): ... Enhance(a=1, b=2) >>> ClassToBeInitialized.a 1 >>> ClassToBeInitialized.b 2
如果使用过 zope 界面 ![](/icons/85071dou.gif) 也许见过类 ![](/icons/85071chushi.gif) 化器 ![](/icons/85071de.gif) 例子 (zope. ![](/icons/85071int.gif) erface.implements) ![](/icons/85071dou2.gif) 事实上 ![](/icons/85071dou.gif) ![](/icons/85071class.gif) initializer 是使用 ![](/icons/85071yi.gif) 个从 Phillip J. Eby 开创 ![](/icons/85071de.gif) zope. ![](/icons/85071int.gif) erface.advice 复制过来 ![](/icons/85071de.gif) 窍门技巧来实现 ![](/icons/85071de.gif) ![](/icons/85071dou2.gif) 此窍门技巧使用 “ __meta ![](/icons/85071class.gif) __ ” 钩子 ![](/icons/85071dou.gif) 但是它不使用 自定义类 ![](/icons/85071dou2.gif) ClassToBeInitialized 保留了它原始 ![](/icons/85071de.gif) 元类 ![](/icons/85071dou.gif) 即新式类 ![](/icons/85071de.gif) 普通内置元类 type: >>> type(ClassToBeInitialized) <type 'type'> 原则上 ![](/icons/85071dou.gif) 此窍门技巧也适用于老式类 ![](/icons/85071dou.gif) 并且应该容易编写 ![](/icons/85071yi.gif) 个实现来使老式类保持老 ![](/icons/85071de.gif) 样式 ![](/icons/85071dou2.gif) 然而 ![](/icons/85071dou.gif) 由于根据 Guido 所说 ![](/icons/85071de.gif) “老式类在精神上是不受赞成 ![](/icons/85071de.gif) ” ![](/icons/85071dou.gif) 当前 ![](/icons/85071de.gif) 实现将老式类转换为新式类: 清单 6. 升级为新式类 >>> WasOldStyle: ... Enhance(a=1, b=2) >>> WasOldStyle.a, WasOldStyle.b (1, 2) >>> type(WasOldStyle) <type 'type'> ![](/icons/85071class.gif) initializer 装饰器 ![](/icons/85071de.gif) ![](/icons/85071yi.gif) 个动机是要隐藏细节 ![](/icons/85071dou.gif) 使 ![](/icons/85071yi.gif) 般 ![](/icons/85071de.gif) 人们能够用 ![](/icons/85071yi.gif) 种容易 ![](/icons/85071de.gif) 思路方法实现他们自己 ![](/icons/85071de.gif) 类 ![](/icons/85071chushi.gif) 化器 ![](/icons/85071dou.gif) 而不必知道类创建工作 ![](/icons/85071de.gif) 细节和_meta ![](/icons/85071class.gif) _ 钩子 ![](/icons/85071de.gif) 秘密 ![](/icons/85071dou2.gif) 另 ![](/icons/85071yi.gif) 个动机是 ![](/icons/85071dou.gif) 即使对于 Python 奇才来说 ![](/icons/85071dou.gif) 每次编写新 ![](/icons/85071de.gif) 类 ![](/icons/85071chushi.gif) 化器时都得重写管理 _meta ![](/icons/85071class.gif) _ 钩子 ![](/icons/85071de.gif) 代码也是很不方便 ![](/icons/85071de.gif) 最后应该注意 ![](/icons/85071dou.gif) 我们指出 Enhance ![](/icons/85071de.gif) 已装饰版本当作类范围外 ![](/icons/85071de.gif) 未装饰版本来运行已经足够漂亮了 ![](/icons/85071dou.gif) 假设传递给它 ![](/icons/85071yi.gif) 个显式类参数: >>> Enhance(WasOldStyle, a=2) >>> WasOldStyle.a 2 极度不可思议 下面是 ![](/icons/85071class.gif) initializer ![](/icons/85071de.gif) 代码 ![](/icons/85071dou2.gif) 使用装饰器不需要理解该代码:
清单 7. ![](/icons/85071class.gif) initializer 装饰器 import sys def initializer(proc): # basic idea stolen from zope. erface.advice, P.J. Eby def proc(*args, **kw): frame = sys._getframe(1) '__module__' in frame.f_locals and not '__module__' in frame.f_code.co_varnames: # we are in a ![](/icons/85071class.gif) '__meta __' in frame.f_locals: raise SyntaxError("Don't use two initializers orn" "a initializer together with a __meta __ hook") def makecls(name, bases, dic): try: cls = type(name, bases, dic) except TypeError, e: "can't have _disibledevent=Article(['How to use initializers', 'M. Simionato', '2006-07-10']) >>> a.title 'How to use initializers' >>> a.author 'M. Simionato' >>> a.date datetime.date(2006, 7, 10)
相关文章
读者评论
发表评论
|
|