专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »Python » python网络编程:Python 中的元类编程(3) »正文

python网络编程:Python 中的元类编程(3)

来源: 发布时间:星期四, 2009年1月8日 浏览:56次 评论:0
  介绍

  去年我参加了 EuroPython 2006 会议这个会议非常好组织得很完美谈话都具有很高水平人们也都特别友好然而我在这篇文章归属 Python 社区中注意到了种令人烦恼趋势几乎同时合著者 David Mertz 也在研究个类似有关些提交给 Gnosis Utilities 补丁问题这种有争议趋势就是趋向于耍小聪明不幸Python 社区这种聪明以前只局限于 Zope 和 Twisted现在已变得无处不在

  我们在试验项目和学习过程中并不反对这种聪明我们烦恼是在产品框架上必须符合用户要求在本文中我们希望为避免这种聪明做出小小贡献至少在我们比较精通领域避免元类滥用

  对于本文我们坚持严肃立场:我们把在不用元类也能解决问题情况下使用元类都视为元类滥用当然作者过错也很明显:我们 有关 Python 中元类前几部分 助长了这种做法流行Nostra culpa

  使用元编程最普通情况就是创建具有动态生成属性和思路方法跟流行观点相反这是个在大多数时候都不需要 而且不想要 自定义元类工作

  本文适用于两类读者:普通员和聪明前者知道些元编程窍门技巧但是并没有在大脑中形成具体概念;后者很聪明而且理解得深后者问题在于变得聪明很容易要变得不那么聪明就得花不少时间了例如花几个月时间就能理解如何使用元类但是要花几年时间才能明白如何不 使用它们

  有关类

  创建类过程中所有属性和思路方法只设置而在 Python 中思路方法和属性随时都可以更改但是只有不遵守规则员才会这样做

  在各种条件下创建类时也许想用比简单地运行静态编码更加动态思路方法例如可能想根据从配置文件读取参数来设置些默认类属性;或者想根据数据库表中字段来设置类特性利用强制方式动态自定义类行为最简单思路方法是:首先创建类然后添加思路方法和属性

  例如Anand Pillai(我们熟悉个优秀员)提出了个到 Gnosis Utilities 分包 gnosis.xml.objecty 路径该分包就是这么做个专门用来保存 “xml 节点对象” 叫做 gnosis.xml.objecty._XO_ 基类就被许多增强行为 “装饰” 成如下这样:

  清单 1. 基类动态增强

attr(_XO_, 'orig_tagname', orig_tagname)
attr(_XO_, 'findelem', findelem)
attr(_XO_, 'XPath', XPath)
attr(_XO_, 'change_pcdata', change_pcdata)
attr(_XO_,'addChild',addChild)


  您可能会非常合理地想到也可以定义 XO 基类子类来实现同样增强在感觉上这是对但 Anand 已经提供了 20 多种可能增强并且些特定用户可能想要其中些增强但不想要 另外些增强有太多替代思路方法可以轻易地为每种增强情形创建子类尽管如此上面代码未必是恰到好处应该用个附加到 XO、行为是动态决定自定义元类来完成上述工作但是这又让我们回到了希望避免聪明过度(和不透明性)上

  上述问题种干净、漂亮解决方案可能需要向 Python 添加类装饰器如果拥有这些装饰器编写代码可能就像这样:

  清单 2. 向 Python 添加类装饰器

features = [('XPath',XPath), ('addChild',addChild), ('is_root',is_root)]
@enhance(features)
_XO_plus(gnosis.xml.objecty._XO_): pass
gnosis.xml.objecty._XO_ = _XO_plus


  然而目前没有这种语法

  当元类变复杂时

  表面上看本文除了大惊小怪的外似乎毫无意义例如为什么不直接把 XO 元类定义为 Enhance然后就切 OK 了呢 Enhance.__init__ 可以为所讨论特定用途添加所需任何功能可能看起来像这样:

  清单 3. 将 XO 定义为 Enhance

_XO_plus(gnosis.xml.objecty._XO_):
   __meta__ = Enhance
   features = [('XPath',XPath), ('addChild',addChild)]
gnosis.xml.objecty._XO_ = _XO_plus


  不幸当考虑到继承时问题却没有这么简单旦为基类定义了个自定义元类所有派生类都将继承此元类所以化代码将魔法般隐式地在所有派生类上运行这在特定情形中可能还不错(例如假设必须将所有类都注册到您自己框架中:使用元类可以确保不会忘记注册派生类)然而在许多情况下则可能不喜欢这种行为:

  您相信显式比隐式更好

  派生类具有跟基类相同动态类属性为每个派生类再次设置这些属性是种浪费通过继承它们就会拥有这些属性如果化代码很慢或者需要大量计算那么这特性就显得特别重要也许会在元类代码中添加个检查以查看是否在父类中设置了这些属性但是这样会增加负担并且不会控制所有

  自定义元类将会使类有些不可思议和不标准:您肯定不想增加元类冲突、“ __slots__ ” 问题、跟扩展类( Zope )斗争和其他复杂问题几率元类比很多人认识到更加脆弱我们甚至在试验代码中用了 4年的后还很少在生产代码中使用它们

  您觉得对于类化这类简单工作使用自定义元类是杀鸡用牛刀所以想要使用种更为简单解决方案

  换句话说只有当想在派生类上运行代码又不想让用户注意到时才应该使用自定义元类如果不属于这种情形那就跳过元类使您(和您用户)生活更加惬意

  initializer 装饰器

  本文以下部分可能会被谴责为聪明过度但是聪明不应该加重用户负担只应该加重我们作者负担读者可以做些和我们假设理想类装饰器类似事情但是要避免在元类思路方法中出现继承及元类冲突问题我们后面给出 “不可思议” 装饰器通常情况下只能增强直观(但稍微有些难看)强制思路方法并且跟下面例子在 “精神上相当”:

  清单 4. 强制思路方法

def Enhance(cls, **kw):
  for k, v in kw.iteritems:
    attr(cls, k, v)
ClassToBeInitialized(object):
  pass
Enhance(ClassToBeInitialized, a=1, b=2)


  上面强制增强器并不是很坏但是也有些缺馅:它要求重复输入类名称;可读性不够理想类定义和类化是分开 —— 长类定义可能会漏掉最后行;并且它会认为首先定义些内容然后又立即更改是不对

  initializer 装饰器提供了个介绍说明性解决方案装饰器将 Enhance(cls,**kw) 转换为个能够用于类定义中思路方法:

  清单 5. 基本操作中神奇装饰器

>>> @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 界面也许见过类化器例子 (zope.erface.implements)事实上initializer 是使用个从 Phillip J. Eby 开创 zope.erface.advice 复制过来窍门技巧来实现此窍门技巧使用 “ __meta__ ” 钩子但是它不使用 自定义类ClassToBeInitialized 保留了它原始元类即新式类普通内置元类 type:

>>> type(ClassToBeInitialized)
<type 'type'>


  原则上此窍门技巧也适用于老式类并且应该容易编写个实现来使老式类保持老样式然而由于根据 Guido 所说 “老式类在精神上是不受赞成当前实现将老式类转换为新式类:

  清单 6. 升级为新式类

>>> WasOldStyle:
...   Enhance(a=1, b=2)
>>> WasOldStyle.a, WasOldStyle.b
(1, 2)
>>> type(WasOldStyle)
<type 'type'>


  initializer 装饰器个动机是要隐藏细节使人们能够用种容易思路方法实现他们自己化器而不必知道类创建工作细节和_meta_ 钩子秘密个动机是即使对于 Python 奇才来说每次编写新化器时都得重写管理 _meta_ 钩子代码也是很不方便

  最后应该注意我们指出 Enhance 已装饰版本当作类范围外未装饰版本来运行已经足够漂亮了假设传递给它个显式类参数:

>>> Enhance(WasOldStyle, a=2)
>>> WasOldStyle.a
2


  极度不可思议

  下面是 initializer 代码使用装饰器不需要理解该代码:

  清单 7. 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
      '__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)




相关文章

读者评论

发表评论

  • 昵称:
  • 内容: