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

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

首页 »Python » python平台怎么使用:在 Python 中对协议使用 PEAK »正文

python平台怎么使用:在 Python 中对协议使用 PEAK

来源: 发布时间:星期四, 2009年1月8日 浏览:19次 评论:0
  PEAK是个用于快速开发和重用应用组件 Python 框架虽然 Python 本身就已经是门非常高层次语言但 PEAK 提供了更高层次抽象这些抽象主要来自于对元类(meta)及其他高级Python 技术巧妙运用在很多方面PEAK 对 Python 贡献相当于 J2EE 对 Java 贡献PEAK 中形式化部分体现在协议显式规范标准中具体说是在可以单独获得 PyProtocols 包中

  如果您正尝试去处理元类或者正受困于 Twisted 中异步编程或者正在研究由于使用了多分派而使您精疲力尽面向对象编程那么您完全错了!PEAK 将所有这些中些要素组合到了个组件编程框架中PEAK 还存在些小问题类似于 TwistedPEAK 文档 -- 尽量数量巨大 -- 难以看懂但是尽管如此有关 Python 领袖 Phillip J. Eby 领导项目还是有些东西非常值得关注;而且我觉得有机会进行极具生产价值并且层次特别高应用开发

  PEAK 包由许多区别用途子包组成些重要子包是 peak.api、 peak.binding、 peak.config、 peak.naming 和 peak.storage 那些名字大部分是自我解释性子包 peak.binding 用于组件间灵活连接; peak.config 让您可以存储“很少改变(lazily immutable)”数据这些数据和声明性应用(declarative application )编程有关; peak.naming 让您可以为(网络)资源创建全局惟标识符; peak.storage 顾名思义让您可以管理数据库和持久内容

  不过对本文来说我们将关注是 peak.api 特别是 PyProtocols 包它可以单独获得并为其他 PEAK 子包提供个基础设施在 peak.api.protocols 中包括了 PyProtocols 包个版本不过现在我所感兴趣是研究个独立 protocols 包在以后部分我将返回来讨论 PEAK 其他部分话题

  什么是协议?

  抽象地说协议只是对象同意遵循组行为强类型(Strongly-typed)编程语言 -- 包括 Python -- 都有个基本类型集合每个基本类型都有组得到保证行为:Integer 知道如何去求它们自己乘积;list 知道如何去遍历它们内容;dictionary 知道如何根据个关键字找到相应值;file 知道如何去读和写字节;诸如此类您可以预期内置类型行为集合构成了它们实现个 协议对协议进行系统化对象被称为 接口(erface)

  对标准类型而言将实现所有行为全部列出并不太困难(尽管区别 Python 版本的间会稍有区别;或者区别编程语言的间当然会有差别)但是在边界 -- 对于属于自定义类对象来说 -- 难以声明最终是什么构成了“类-dictionary”或“类-file”行为大部分情况下只实现了比如内置 dict 类型思路方法个子集 -- 甚至是相当小子集 -- 自定义对象就足够“类-dictionary”而可以满足当前要求不过能显式地整理出个对象要用到、模块、类或者框架中需要能够做哪些事情将是很吸引人那就是 PyProtocols 包所做到(部分)

  在具有静态类型声明编程语言中为了在新上下文中使用数据您通常需要将其自个类型 强制类型转换(cast)或者 转换(convert)到另个类型在其他语言中转换根据上下文需要隐式地进行这些被称为 强迫同型(coercions)Python 中既有强制类型转换也有强迫同型通常使用更多是前者(“显式优于隐式”)您可以将向个浮点数加到个整型数结果得到个更为通用浮点数;但是如果您希望将串 "3.14" 转换为个数字那么您需要使用显式构造 float("3.14")

  PyProtocols 具有个称为“适配(adaptation)”功能类似于“部分类型(partial typing)”这非正统计算机科学概念适配还可能被认为是“加速强制同型”如果个接口定义了所需要组能力 (也就是对象思路方法)那么要去做“所需要切”对象就要求适配 -- 通过 protocols.adapt 实现 -- 以提供所需要能力显然如果您有个显式转换可以将类型 X 对象转换为类型 Y 对象(在这里 Y 实现了某个 IY 接口)那么那个要能够让 X 适配协议 IY 不过PyProtocols 中适配可以做比这多得多事情例如甚至如果您从来没有显式地编写过从类型 X 到类型 Y 转换 adapt 通常可以推演出条让 X 提供 IY 所要求能力途径(也就是说找到从 X 到接口 IZ 从 IZ 到 IW 然后再从 IW 到 IY 中间转换)

  声明接口和适配器

  在 PyProtocols 中有很多区别思路方法可以创建接口和适配器PyProtocols 文档非常详细地介绍了这些技术 -- 很多不会在本文中涉及接下来我们将进入些细节不过我觉得在这里给出实际 PyProtocols 代码个最简化例子是个有用思路方法

  例如我决定创建个 Python 对象类-Lisp 序列化其描述并不是准确 Lisp 语法我也并不在意这种格式确切优点和缺点在这里想法只是创建个功能使的可以执行类似 repr 或 ppr 模块工作不过结果是既和以前串行器(serializers)有明显区别又要能更容易地扩展/定制出于举例介绍说明做出了个非常不像 Lisp 选择:映射(mappings)是个比列表(list)更为基础数据结构(Python 元组(tuple)或列表被作为以连续整数为键映射来处理)下面是代码:

  lispy.py PyProtocol 定义

from protocols import *
from cStringIO import StringIO
# Like unicode, & even support objects that don't explicitly support ILisp
ILisp = protocolForType(unicode, ['__repr__'], implicit=True)
# Class for erface, but no methods specically required
ISeq(Interface): pass
# Class for erface, extremely simple mapping erface
IMap(Interface):
  def items:
    "A requirement for a map is to have an .items method"
# Define function to create an Lisp like representation of a mapping
def map2Lisp(map_, prot):
  out = StringIO
  for k,v in map_.items:
    out.write("(%s %s) " % (adapt(k,prot), adapt(v,prot)))
   "(MAP %s)" % out.getvalue
# Use this func to convert an IMap-supporting obj to ILisp-supporting obj
declareAdapter(map2Lisp, provides=[ILisp], forProtocols=[IMap])
# Note that a dict implements an IMap erface with no conversion needed
declareAdapter(NO_ADAPTER_NEEDED, provides=[IMap], forTypes=[dict])
# Define and use func to adapt an InstanceType obj to the ILisp erface
from types import InstanceType
def inst2Lisp(o, p):
   "(CLASS '(%s) %s)" % (o.____.__name__, adapt(o.__dict__,p))
declareAdapter(inst2Lisp, provides=[ILisp], forTypes=[InstanceType])
# Define a to adapt an ISeq-supporting obj to an IMap-supporting obj
SeqAsMap(object):
  advise(instancesProvide=[IMap],
      asAdapterForProtocols=[ISeq] )
  def __init__(self, seq, prot):
    self.seq = seq
    self.prot = prot
  def items(self):  # Implement the IMap required .items method
     enumerate(self.seq)
# Note that list, tuple implement an ISeq erface w/o conversion needed
declareAdapter(NO_ADAPTER_NEEDED, provides=[ISeq], forTypes=[list, tuple])
# Define a lambda func to adapt str, unicode to ILisp erface
declareAdapter(lambda s,p: "'(%s)" % s,
        provides=[ILisp], forTypes=[str,unicode])
# Define a to adapt several numeric types to ILisp erface
# Return a (ILisp-supporting) directly from instance constructor
NumberAsLisp(object):
  advise(instancesProvide=[ILisp],
      asAdapterForTypes=[long, float, complex, bool] )
  def ____(klass, val, proto):
     "(%s %s)" % (val.____.__name__.upper, val)


  在上面代码中我已经用些区别思路方法声明了许多适配器些情况中代码将个接口转换到另个接口;在其他情况中类型本身直接适配到另个接口我希望您能注意到有关代码些方面:(1)没有创建任何从 list 或 tuple 到 ILisp 接口适配器;(2)没有为 数字类型显式声明适配器;(3)就此而言没有声明直接由 dict 到 ILisp 适配器下面是代码将如何适配( adapt )各种 Python 对象:

  test_lispy.py 对象序列化

from lispy import *
from sys import stdout, stderr
toLisp = lambda o: adapt(o, ILisp)
Foo:
  def __init__(self):
    self.a, self.b, self.c = 'a','b','c'
tests = [
 "foo bar",
 {17:2, 33:4, 'biz':'baz'},
 ["bar", ('f','o','o')],
 1.23,
 (1L, 2, 3, 4+4j),
 Foo,
 True,
]
for test in tests:
  stdout.write(toLisp(test)+'
')


  运行时我们得到:

  test_lispy.py 序列化结果

$ python2.3 test_lispy.py
'(foo bar)
(MAP (17 2) ('(biz) '(baz)) (33 4) )
(MAP (0 '(bar)) (1 (MAP (0 '(f)) (1 '(o)) (2 '(o)) )) )
(FLOAT 1.23)
(MAP (0 (LONG 1)) (1 2) (2 3) (3 (COMPLEX (4+4j))) )
(CLASS '(Foo) (MAP ('(a) '(a)) ('(c) '(c)) ('(b) '(b)) ))
(BOOL True)


  对我们输出进行些解释将会有所帮助行比较简单我们定义了个直接从串到 ILisp 适配器对 adapt("foo bar", ILisp) 只是返回了 lambda 结果行只是有点复杂没有直接从 dict 到 ILisp 适配器;但我们不必使用任何适配器就可以让 dict 去适配 IMap (我们声明了足够多)而且我们有从 IMap 到 ILisp 适配器类似对于后面列表和元组我们可以使 ILisp 适配 ISeq 使 ISeq 适配 IMap 并使 IMap 适配 ILisp PyProtocols 会指出要采取适配路径所有这些不可思议过程都在幕后完成个旧风格例子所经历过程和串或者支持 IMap 对象相同我们有个直接到 ILisp 适配

  不过在我们 dict 和 tuple 对象中用到所有整数是如何处理呢? long 、 complex、 float 和 bool 类型数字有显式适配器不过 个都没有这里窍门技巧在于 对象已经拥有个 .__repr__ 思路方法;通过将隐式支持声明为 ILisp 接口部分我们可以巧妙地使用对象已有 .__repr__ 思路方法作为对 ILisp 接口支持实际上作为个内置类型整数用不加任何修饰阿拉伯数字表示而不使用大写类型器(比如 LONG )

  适配协议

  让我们来更明确地看下 protocol.adapt 都做了什么事情在我们例子中我们使用“声明 API(declaration API)”来隐式地为适配设置了组“工厂(factories)”这个 API 有几个层次声明 API “基本层次(primitives)”是: declareAdaptorForType 、 declareAdaptorForObject 和 declareAdaptorForProtocol 前面例子中没有用到这些而是用到了些高层次 API如 declareImplementation 、 declareAdaptor 、 adviceObject 和 protocolForType 种情况下我们看到在个类体中有“奇妙” advise 声明 advise 支持用于配置那些建议和角色大量关键字参数您还可以建议 (advise) 个模块对象

  您不需要使用声明 API 来创建知道如何使对象适配( adapt )自己可适配对象或者接口让我们来看 adapt 标记然后解释它随后过程对 adapt 类似这样:

  adapt 标记

adapt(component, protocol, [, default [, factory]])

  这就表示您希望让对象 component 去适配接口 protocol 如果指定了 default 它可以返回为个包装对象(wrapper object)或者对 component 修改如果 factory 被指定为个关键字参数那么会使用个转换工厂来生成包装或者修改不过让我们先退回来看下 adapt 尝试完整动作次序(简化代码):

  adapt 假想实现

isinstance(component, protocol):
   component
el hasattr(component,'__conform__'):
   component.__conform__(protocol)
el hasattr(protocol,'__adapt__'):
   protocol.__adapt__(component)
el default is not None:
   default
el factory is not None:
   factory(component, protocol)
:
  NotImplementedError


  对 adapt 应该保持些特性(不过这是对建议而不是库般强制要求)对 adapt 应该是等幂也就是说对于个对象 x 和个协议 P 我们希望: adapt(x,P)adapt(adapt(x,P),P) 高级地这样做类似于从 .__iter__ 思路方法返回自身( self )迭代器(iterator)类您基本上不会希望去重新适配到您已经适配到相同类型以产生波动结果

  还值得注意适配可能是有损耗为了让个对象去顺应个接口可能不方便或者不可能保持重新化这个对象所需要所有信息也就是说通常情况下对对象 x 及协议 P1 和 P2 而言: adapt(x,P1)!=adapt(adapt(adapt(x,P1),P2),P1)

  在结束的前让我们来看另个利用了 adapt 低层次行为测试脚本:

  test_lispy2.py 对象序列化

from lispy import *
Bar(object):
  pass
Baz(Bar):
  def __repr__(self):
     "Represent a "+self.____.__name__+" object!"
Bat(Baz):
  def __conform__(self, prot):
     "Adapt "+self.____.__name__+" to "+repr(prot)+"!"
pr adapt(Bar, ILisp)
pr adapt(Baz, ILisp)
pr adapt(Bat, ILisp)
pr adapt(adapt(Bat, ILisp), ILisp)
$ python2.3 test_lispy2.py
<____.Bar object at 0x65250>
Represent a Baz object!
Adapt Bat to WeakSub(<type 'unicode'>,('__repr__',))!
'(Adapt Bat to WeakSub(<type 'unicode'>,('__repr__',))!)




  结果证明 lispy.py 设计不能满足等幂目标改进这设计可能是个不错练习不过像 ILisp 这样描述肯定会损耗原始对象中信息(这是没关系)

  结束语

  感觉上PyProtocols 和本专栏提及其他“外来”话题有些共同的处首先声明 API 是声明性(相对于解释性)声明性编程并不给出执行个动作所需要步骤和开关而是声明处理特定内容由库或编译器来具体指出如何执行名称“declare*”和“advice*”正在来自于这观点

  不过我也发现 PyProtocols 编程有些类似于使用多分派进行编程具体说就是使用我在另期文章提到 gnosis.magic.multimethods 模块和 PyProtocols 确定适配路径形成对照我自己模块执行了个相对简单推演确定要分派相关祖先类不过两个库都倾向于在编程中鼓励使用类似模块化思想 -- 由大量或类来执行“可插入”任务不需要受死板类层级结构所困在我看来这种风格有其优越的处



0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: