使用Python的PEAK来适配协议的教程

如果您正尝试去处理元类,或者正受困于 Twisted 中的异步编程,或者正在研究由于使用了多分派而使您精疲力尽的面向对象编程,那么您完全错了!PEAK 将所有这些中的一些要素组合到了一个组件编程框架中。PEAK 还存在一些小问题。类似于 Twisted,PEAK 的文档 -- 尽量数量巨大 -- 难以看懂。但是尽管如此,关于 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 知道如何去读和写字节;诸如此类。您可以预期的内置类型的行为集合构成了它们实现的一个 协议。对协议进行系统化的对象被称为 接口(interface)。

对标准的类型而言,将实现的所有行为全部列出并不太困难(尽管不同的 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() 函数或 pprint 模块的工作,不过结果是既与以前串行器(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 interface, but no methods specifically required
class ISeq(Interface): pass
# Class for interface, extremely simple mapping interface
class 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)))
  return "(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 interface with no conversion needed
declareAdapter(NO_ADAPTER_NEEDED, provides=[IMap], forTypes=[dict])
# Define and use func to adapt an InstanceType obj to the ILisp interface
from types import InstanceType
def inst2Lisp(o, p):
  return "(CLASS '(%s) %s)" % (o.__class__.__name__, adapt(o.__dict__,p))
declareAdapter(inst2Lisp, provides=[ILisp], forTypes=[InstanceType])
# Define a class to adapt an ISeq-supporting obj to an IMap-supporting obj
class 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
    return enumerate(self.seq)
# Note that list, tuple implement an ISeq interface w/o conversion needed
declareAdapter(NO_ADAPTER_NEEDED, provides=[ISeq], forTypes=[list, tuple])
# Define a lambda func to adapt str, unicode to ILisp interface
declareAdapter(lambda s,p: "'(%s)" % s,
        provides=[ILisp], forTypes=[str,unicode])
# Define a class to adapt several numeric types to ILisp interface
# Return a string (ILisp-supporting) directly from instance constructor
class NumberAsLisp(object):
  advise(instancesProvide=[ILisp],
      asAdapterForTypes=[long, float, complex, bool] )
  def __new__(klass, val, proto):
    return "(%s %s)" % (val.__class__.__name__.upper(), val)

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

test_lispy.py 对象序列化

from lispy import *
from sys import stdout, stderr
toLisp = lambda o: adapt(o, ILisp)
class 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)+'\n')

运行时,我们得到:

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 类型的数字有显式的适配器,不过 int 一个都没有。这里的技巧在于, int 对象已经拥有一个 .__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() 的假想实现

if isinstance(component, protocol):
  return component
elif hasattr(component,'__conform__'):
  return component.__conform__(protocol)
elif hasattr(protocol,'__adapt__'):
  return protocol.__adapt__(component)
elif default is not None:
  return default
elif factory is not None:
  return factory(component, protocol)
else:
  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 *
class Bar(object):
  pass
class Baz(Bar):
  def __repr__(self):
    return "Represent a "+self.__class__.__name__+" object!"
class Bat(Baz):
  def __conform__(self, prot):
    return "Adapt "+self.__class__.__name__+" to "+repr(prot)+"!"
print adapt(Bar(), ILisp)
print adapt(Baz(), ILisp)
print adapt(Bat(), ILisp)
print adapt(adapt(Bat(), ILisp), ILisp)
$ python2.3 test_lispy2.py
<__main__.Bar object at 0x65250>
Represent a Baz object!
Adapt Bat to WeakSubset(<type 'unicode'>,('__repr__',))!
'(Adapt Bat to WeakSubset(<type 'unicode'>,('__repr__',))!)

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

结束语

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

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

(0)

相关推荐

  • python中实现php的var_dump函数功能

    最近在做python的web开发(原谅我的多变,好东西总想都学着...node.js也是),不过过程中总遇到些问题,不管是web.py还是django,开发起来确实没用php方便,毕竟存在的时间比较短,很多不完善的地方. 比如我在调试php中最常用的函数,var_dump,在python里找不到合适的替代函数.php中var_dump是一个特别有用的函数,它可以输出任何变量的值,不管你是一个对象还是一个数组,或者只是一个数.它总能用友好的方式输出,我调试的时候经常会需要看某位置的变量信息,调用它

  • Python实现获取网站PR及百度权重

    上一次我用requests库写的一个抓取页面中链接的简单代码,延伸一下,我们还可以利用它来获取我们网站的PR以及百度权重.原理差不多.最后我们甚至可以写一个循环批量查询网站的相关信息. 先说说GooglePR,全称PageRank.它是Google官方给出的评定一个网站SEO的评级,这个大家应该不陌生.既然是官方给出的,当然有一个官方的接口去获取它.我们这里就利用官方的接口获取谷歌PR. 复制代码 代码如下: GPR_HASH_SEED ="Mining PageRank is AGAINST

  • Python实现抓取页面上链接的简单爬虫分享

    除了C/C++以外,我也接触过不少流行的语言,PHP.java.javascript.python,其中python可以说是操作起来最方便,缺点最少的语言了. 前几天想写爬虫,后来跟朋友商量了一下,决定过几天再一起写.爬虫里重要的一部分是抓取页面中的链接,我在这里简单的实现一下. 首先我们需要用到一个开源的模块,requests.这不是python自带的模块,需要从网上下载.解压与安装: 复制代码 代码如下: $ curl -OL https://github.com/kennethreitz/

  • 使用Python的PEAK来适配协议的教程

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

  • Python+Socket实现基于UDP协议的局域网广播功能示例

    本文实例讲述了Python+Socket实现基于UDP协议的局域网广播功能.分享给大家供大家参考,具体如下: 服务器端: # udp_gb_server.py '''服务端(UDP协议局域网广播)''' import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) PORT = 1060 network = '<b

  • Python+Socket实现基于TCP协议的客户与服务端中文自动回复聊天功能示例

    本文实例讲述了Python+Socket实现基于TCP协议的客户与服务端中文自动回复聊天功能.分享给大家供大家参考,具体如下: [吐槽] 网上的代码害死人,看着都写的言之凿凿,可运行就是有问题. 有些爱好代码.喜欢收藏代码的朋友,看到别人的代码就粘贴复制过来.可是起码你也试试运行看啊大哥 [正文] 昨日修改运行了UDP协议的C/S聊天程序,可是TCP协议的怎么都不行.各种试,各种坑. 做了下面几个修改后,终于可以了: 1.对发送.接收的信息,分别进行编码和解码 2.客户端的第10行bind改为c

  • Python实现基于TCP UDP协议的IPv4 IPv6模式客户端和服务端功能示例

    本文实例讲述了Python实现基于TCP UDP协议的IPv4 IPv6模式客户端和服务端功能.分享给大家供大家参考,具体如下: 由于目前工作的需要,需要在IPv4和IPv6两种网络模式下TCP和UDP的连接,要做到客户端发包,服务端收包. 前几天写了代码,但是把UDP的客户端和服务端使用TCP模式的代码了.今天在公司使用该工具的时候,发现了问题,忘记了UDP不需要验证.疏忽,疏忽.不过刚刚接触编程,可以原谅. 现在在家,已经把代码改好了.经测试可以使用. 先运行客户端: python Mini

  • python设计tcp数据包协议类的例子

    一. 问题描述 在tcp编程中,最需要解决的就是粘包分包问题.所以,我们需要在每个数据包前面加上数据包的长度用以分割粘连的包. 二. 包结构的设计 包的组成:包长度+数据域 包长度:用4个字节存储数据域长度,数据域长度即为其所占字节数 数据域:由若干个变量组成,如果是定长变量则不用加变量长度 定长变量:我们人为规定,传输中的int为4字节定长变量 变长变量:那就是字符串啦 文字难理解,那我就画个图吧: 上图的第一行是数据包的一个总体结构 第二行是数据域内部的一个结构(数据域的变量数量和位置都是我

  • Python GUI布局尺寸适配方法

    如下所示: #coding=utf-8 #布局自定义尺寸 from tkinter import * class App: def __init__(self,master): frame=Frame(master) frame.pack(fill=BOTH,expand=1) listbox=Listbox(frame) #listbox=Listbox(frame,height=3,selectmode=BROWSE) #curselection() for item in ['red','

  • Python Web编程之WSGI协议简介

    本文实例讲述了Python Web编程之WSGI协议.分享给大家供大家参考,具体如下: WSGI简介 Web框架和Wen服务器之间需要进行通信,如果在设计时它们之间无法相互匹配,那么对框架的选择就会限制对Web服务器的选择,这显然是不合理的.这时候需要设计一套双方都遵守的接口.WSGI是Python Web Server Gateway Interface的简称.WSGI标准在PEP 333中定义并被许多框架实现,它规定了一种在Web服务器之间具有可移植性.在后来的PEP 3333中添加了Pyt

  • python使用socket实现TCP协议长连接框架

    分析多了协议就会发现,很多的应用,特别是游戏类和IM类应用,它们的协议会使用长连接的方式,来保持客户端与服务器的联系,这些长连接,通常是TCP承载的. 如果我们要模拟这个客户端的行为,根据不同应用服务器的实现情况,有些长连接不是必须的,但有些长连接,就必须去实现它.例如最近分析的某应用,虽然它主要使用HTTP协议进行交互,但它在TCP长连接中传输了一些必须的信息,如果不实现长连接,就会有很多信息无法处理. 在python中,很容易实现HTTP协议,当然,也容易实现TCP协议,它的TCP实现,使用

  • Python中with上下文管理协议的作用及用法

    目录 1.简介 2.try… except语句 (1)try…except的标准格式 (2)程序执行流程 (3)异常分类 3.try…finallly语句 4.with…as语句 (1)With语句的基本语法 (2)with语句原理 1.简介 with是从Python2.5引入的一个新的语法,它是一种上下文管理协议,目的在于从流程图中把 try,except 和finally 关键字和资源分配释放相关代码统统去掉,简化try…except…finlally的处理流程. 那我们先理解一下try…e

  • Python网络编程之HTTP协议的python应用

    目录 搭建python自带静态web服务器 1. 静态Web服务器是什么? 2. 如何搭建Python自带的静态Web服务器 3. 访问搭建的静态Web服务器 4. 查看浏览器和搭建的静态Web服务器的通信过程 静态web服务器-返回固定页面数据 1. 开发自己的静态Web服务器 2. 静态Web服务器-返回固定页面数据的示例代码 静态web服务器-返回指定页面数据 1. 静态Web服务器的问题 2. 静态Web服务器-返回指定页面数据的示例代码 静态web服务器-多任务实现 1. 静态Web服

随机推荐