PyCoder’s Weekly 订阅 | 订阅中文邮件列表 | View project on Github
Hi Pythonistas,
本周的新闻并不是很多,不过贴纸的事情倒是有很多要说的,我们这星期会收到新设计的贴纸,所以赶快给我们寄来回邮吧,我们的地址是:
44 Byward Market Square, Suite 210
Ottawa, Ontario Canada
K1P 7A2
戳这里 和我们签订契约成为魔法少女(少年)。 (◕‿‿◕)
– Mahdi and Mike
虽然这个东西在之前的邮件中已经说过了,但是现在还是有必要再说一下, Light Table 现在已经筹集到足够的资金来开发 Python 支持,我们很高兴能够看到像视频中一样的 Flask 支持。
最新一期的 Radio Free Python ( Python 每月 Podcast ),本期采访了来自冰岛 CCP 公司(以制作 Eve Online & DUST 514 知名的游戏公司)的 Kristjan Valur Jonsson ,讨论了关于 Stackless Python 以及其应用在 Eve Online 中的话题。
Python 3.3.0 仍在继续开发,在5月31日发布了 Alpha 4 版本,包含了许多重量级的新特性与漏洞修复,赶快去试一下吧,同时可以在 bugs.python.org 报告发现的漏洞。
当一个 C 程序员开始学习 Python 的时候会出现什么样的情况呢?在这篇讨论里,作者贴了一段很简单的从 C 语言转换到 Python 的代码,从而引出了一系列对这段代码的优化,从这篇讨论中,我们或许能够了解如何用更加 Python 的方式来解决问题,如果你刚开始学习 Python,认真的读一下这篇讨论吧。
这个项目的文档中写道:”Web X-Ray 眼睛为非技术宅提供了一个简单的方式去剖析页面,了解他们是如何运作的”, webxray 提供了一个书签,在你需要的页面点一下书签就可以方便快捷的得到关于这个页面的所有信息了。
django-discover-runner 是一个基于 unittest2 的 Django 单元测试模块,It solves the problem with the django test runner that it runs tests based on the structure of your Django project. django-discover-runner allows you to place your tests outside the structure of a django project and still allow you to run your test suite.
由于配置 Gunicorn 失败.. Tarek Ziadé 决定用 Mongrel2 & Circus 来建立一个 WSGI 栈,自己的进程管理器。这篇文章介绍了如何建立一个网络堆栈,如果你并不想使用传统的 Gunicorn ,可以读一下这篇文章。
在这篇文章里,作者讨论了为什么散点图对于可视化数据来说很不好,并且建议应该绘制密度图而不是散点图,同时作者也提供了一些生成图形的 Python 代码,这些代码对你的数据展示工作或许会有帮助。
这篇文章很好的解释了 Python 中本地线程的奇怪之处,作者比较了新版本与旧版本 Python 的本地线程的不同之处,读一下吧。
Hi Pythonistas,
本周有大量的有趣项目出现,所以本期的项目小节会十分丰富,以及,邮件列表的新设计将在几周内出现~
现在我们提供 RSS 订阅 (需要翻墙, 隐藏秘籍 什么的人家才不会告诉你) 了,同时也有 往期存档 可以查阅。
想和我们签订契约成为魔法少女(少年)么, 戳这里 。 (◕‿‿◕)
以及 这里 是关于本期标题的解释。
想要一个更好的 git 命令行界面么?试试 Legit 吧,这是 Kenneth Reitz 的最新作品,试图将 Github for Mac 为 GUI 用户做的事情移植到命令行界面上。
Sentry,一个由 David Cramer 开发的错误记录工具现在以插件形式在 Heroku 上提供,无需手工搭建及配置,只需启用这个插件就可以了。
即使我们是 Vim 和 Sublime Text 使用者,但是还是要说一下 PyCharm 发布了 2.5 的公开测试版,如果你在寻找一个全能的 Python IDE,可以去试试 PyCharm。
简答: 像老滚那样的游戏是不可能了,但是如果你不介意为了性能写一些 C 代码的话, Minecraft 这样的游戏还是可行的,这篇讨论含金量很高。
这是在本周 Hacker News 上做的关于你最喜欢与最不喜欢的编程语言投票的结果,我觉得说 Python 碾压全场完全不过分,这些数据很值得一番研究。
在 issue 3 中我们介绍了一个用 Python 实现的 Lisp 方言 Clojure ,而这个项目恰好相反,用 Lisp 实现了一个 Python 语言,不过这并不是它的卖点,实际上他允许你通过 Lisp 访问 Python 库,或者在 Python 中访问 Lisp 库,很棒吧,快去试试~
喜欢 Celery ?但是又讨厌它的复杂? RQ (Redis Queue) 是一个简单的任务调度 Python 库,能够在后台调度任务并且处理他们,后端采用 Redis ,能够轻松的融合入你的 Web 项目中。
如果你使用 Python 进行一些科学研究的话, SciPy Central 提供了很多使用 SciPy 处理科学问题以及相关 Python 工具的代码片段,模块,链接等。
在经过许多争议之后, Testosterone 改名为 assertEquals 了, assertEquals 是一个 “史诗级别” 的 Python 测试接口,提供命令行(CLI/Curses)界面,希望这次改变能够使这个项目更有爱 =v=
一篇不错的关于如何制作一个如何玩网页游戏机器人的文章,即时你是初学者也能轻松学习这篇文章所教的内容。
Python 代码对象是用来表示 Python 字节码的 Python 对象。这篇文章是对代码对象很好的介绍,同时也是一个对于学习检查这些代码对象以及了解更多 Python 和它内部的很好的向导。
Simple 是一个使用 Flask 写的 Obtvse 的克隆项目,This saw quite of bit excitement in the entire web development community! Dustin Curtis, made it invite only service; which made the community wanting, this clone was up in less than 24 hrs.
贝叶斯网络在很多地方都能用到,这是一篇很棒的关于 Khan Academy 是如何利用贝叶斯网络的文章,通过贝叶斯网络给用户推荐适合的课程。
这是 A. Jesse Jiryu Davis 发表的一些关于 Python 实时应用程序( Tornado 与 MongoDB )的一些视频和幻灯片,如果你对建造实时应用程序感兴趣的话,这篇文章会对你很有帮助。
这是一篇关于如何使用 Flask 与 Phantom.js 架设你自己的书签服务的文章,这篇文章一步一步的带着你完成整个网站,所以到最后你会在本地得到一个实用的书签服务。
Hi Pythonistas,
正如我们承诺过的,我们已经建立了往期 PyCoder’s Weekly 的存档页,你可以在我们的 主页 的 archives
中找到它。我们正在策划让邮件列表变的更方便一些,如果你有什么建议,请直接回复这封邮件告诉我们。同时不要忘记把 我们 分享给你的朋友~
Enjoy.
– Mahdi and Mike
MIT 对在计算机科学教学中使用 Python 作了一个介绍。
Pyramid 网页框架发布了 1.3 版本,这个版本已经对 Python 3 完美兼容,去他们的网站看看更多的更新内容吧。
Discoball 是一个通过正则表达式匹配染色的工具,最初是用 Ruby 写的,后来只用了两次提交就有了很大的提升,第一步删掉所有 Ruby 代码,然后用 Python 写一遍。在日常工作中给你的命令输出加上颜色真的是很棒,比如高亮栈输出中的行号等等。
这是一个与众不同的 Web 框架, Airy 并不遵循普通的 HTTP 请求方式,取而代之的是使用 WebSockets (通过 Socket.io ),可惜现在并不支持 SQL 查询,希望这点能够改进下。
有没有想过在你的 Django 程序中方便的添加 Twilio 支持?不用在找其他的了, Randall Degges 已经把这个做出来了。
这个东西非常有用,特别是在制作一个邮件表单的时候(比如创建一个邮件列表登录页?)。 Email Pie 提供一个 JSON 的 API 接口去验证邮件地址是否合法,而且还会检查比如邮件格式, MX 记录,以及一些明显的拼写错误。
如果你正要开始一个全新的 Django 1.4 项目,这将会对你很有帮助。这是一个 Django 1.4 的基本模板,所以你基本上只需要运行 django-admin startproject
并且将它指定为项目的模板,你的 Django 项目就会基于这个模板。这个项目是基于 Mozilla 的 Playdoh 的工作。即使它不能完全适合你的需要,这儿也是个好地方让你可以 fork 并创建你自己的项目模板。
译注: Processing
指一门图形开发相关的语言,更多资料参见 enwp:Processing_(programming_language)
这个项目提供了一个创建类似于 Processing 系统的图形开发环境的 Python 包。 pyprocessing 的后端基于 OpenGL 和 Pyglet 开发,同时也提供动画图形渲染。
这是一个关于写一个同时兼容 Python 2/3 程序以及如何达到完整兼容的讨论。如果你打算或者需要维护一个 Python 2 和 3 写的项目,这篇讨论对你会很有帮助。
这是一篇很翔实的关于 Python 内部探究的文章,建议都读一下这篇文章。
想知道 Reddit 的评级算法是如何工作的么,这篇文章很好的解释了它的工作原理,并且还有一个用我们非常喜爱的语言实现的代码样例。
看到有很多人在 Ajax 与 Django 的整合中遇到问题, Bracket 的一群家伙们决定写以下应该如何完成这件事的文章,稍微有些长,但是确实是一篇不错的介绍。
Eli Bendersky 写了这篇关于在 Python 3.3 中,调用是如何工作的文章,如果你希望能够深入了解 Python 的内部实现细节,这篇文章会很有帮助。
这是一篇对于如何在 DotCloud 上搭建 TurboGears 项目的快速指南,如果你是一个 TurboGears 用户这篇文章或许对你很有用。
这也是一篇由 Eli Bendersky 写的文章,对于 XML 分析处理方面,这篇文章或许会给你带来新的看法,并且还包含了许多例子供你思考。
Fabric 是一个对于 Python Hacker 来说非常有用的工具,这里是一个简单的 Bash 自动补全脚本,能够让 Bash 自动补全 Fabric 文件中的命令名字。
原文: http://amix.dk/blog/post/19588#How-Reddit-ranking-algorithms-work
译者: Tiezhen WANG
这篇是 Hacker News 评级算法的工作原理 一文的姊妹篇。这回主要讲的是 Reddit 是如何对话题和回复进行排序的。Reddit 的评级算法本身是非常容易理解和实现的,这里我想做深入一些的探讨。
第一部分主要是对话题排名的讨论,比如 Reddit 是如何对话题进行排名的。接下来是评论排名的讨论,Reddit 对话题和评论使用了不同的评级算法 (这一点跟 Hacker News 不太一样), Reddit 的评论排名算法是很值得玩味的,它由 Randall Munroe (xkcd 的作者) 提出.
Reddit 公开了他们的代码,很方便就能找到。Reddit 是用 Python 实现的,源代码在 这里. 排名算法使用了 Pyrex (一个用来写 Python 的 C 扩展的语言) 来提高性能。这里为了方便说明,我用 Python 写了 他们的 Pyrex 代码.
这个算法被称作热排名 (hot ranking),代码如下:
#Rewritten code from /r2/r2/lib/db/_sorts.pyx
from datetime import datetime, timedelta
from math import log
epoch = datetime(1970, 1, 1)
def epoch_seconds(date):
"""Returns the number of seconds from the epoch to date."""
td = date - epoch
return td.days * 86400 + td.seconds + (float(td.microseconds) / 1000000)
def score(ups, downs):
return ups - downs
def hot(ups, downs, date):
"""The hot formula. Should match the equivalent function in postgres."""
s = score(ups, downs)
order = log(max(abs(s), 1), 10)
sign = 1 if s > 0 else -1 if s < 0 else 0
seconds = epoch_seconds(date) - 1134028003
return round(order + sign * seconds / 45000, 7)
一下是用数学语言对该算法的描述 (我是在 SEOmoz 看到这个描述的,但是不确定这是否是他们原创的):
发表时间和话题排名的影响可以被概括如下:
下图展示了话题得分在好评和差评的数量不变时,随着时间而变化的情况:
Reddit 的热排序算法使用了对数函数来衡量前面的投票与其他投票的差距:
参见下面的图:
去掉对数函数之后(译注:采用线性函数)的效果
Reddit 是为数不多的几个可以投反对票的网站。正如上边代码所述,一个话题的得分被定义成:好评数 - 差评数
下边的图可以帮助我们理解:
这一点对那些同时有大量赞成和反对票的话题(比如说一些有争议的话题)有显著影响。这种话题的排名会比只有赞成票的话题低一些,这也就解释了为什么 kittens 和其他一些没有争议的话题排名如此靠前。
Reddit 评论排名算法是由xkcd 的 Randall Munroe 提出的。他的这篇博客清楚地解释了排名算法:
内容可以归纳如下:
_sorts.pyx 实现了信心排序算法。我已经用纯 Python 重新实现了原来的 Pyrex 代码,缓存优化相关的代码也被省略了。
#Rewritten code from /r2/r2/lib/db/_sorts.pyx
from math import sqrt
def _confidence(ups, downs):
n = ups + downs
if n == 0:
return 0
z = 1.0 #1.0 = 85%, 1.6 = 95%
phat = float(ups) / n
return sqrt(phat+z*z/(2*n)-z*((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)
def confidence(ups, downs):
if ups + downs == 0:
return 0
else:
return _confidence(ups, downs)
信心排序使用了 威尔逊得分区间 数学记法如下:
公式中参数意义如下:
我们把上边的讨论总结如下:
Randall 在他的一篇博客里 有一个很好的例子解释了信心排序是如何给评论作排名的:
如果一个评论有1个好评,没有差评,它的支持率是100%,但是由于数据量过小,系统还是会把它放到底部。 但如果,它有10个好评,1个差评,系统可能会有足够的信息把他放到一个有着40个好评,20个差评的评论之前。因为我们基本确认当它有了40个好评的时候,它收到的差评会少于20个。最好的一点是,一旦这个算法出错了(算法有15%的失效概率),它会很快拿到更多的数据,因为它被排到了前面。(?)
信心排序的有点在于它跟发表时间无关 (与热排名和 Hacker New 的排序不同)。评论是通过信心和数据采样来评级的。比如,投票数越多,得分也就越准确。
正如 Evan Miller 所说,威尔逊得分区间不仅仅用于排名,他举了3个例子:
使用这个算法,只要知道两点:
知道了这个算法的威力和易用性之后,再想到大部分网站仍在使用最朴素的评级方法就会觉得很吃惊。即使是几十亿美元的大公司,诸如亚马逊 Amazon.com 的评级公式也是很简单: 平均得分 = 好评数 / 投票总数。
原文: http://eli.thegreenplace.net/2012/03/15/processing-xml-in-python-with-elementtree/
译者: TheLover_Z
当你需要解析和处理 XML 的时候,Python 表现出了它 “batteries included” 的一面。 标准库 中大量可用的模块和工具足以应对 Python 或者是 XML 的新手。
几个月前在 Python 核心开发者之间发生了一场 有趣的讨论 ,他们讨论了 Python 下可用的 XML 处理工具的优点,还有如何将它们最好的展示给用户看。这篇文章是我本人的拙作,我打算讲讲哪些工具比较好用还有为什么它们好用,当然,这篇文章也可以当作一个如何使用的基础教程来看。
这篇文章所使用的代码基于 Python 2.7,你稍微改动一下就可以在 Python 3.x 上面使用了。
Python 有非常非常多的工具来处理 XML。在这个部分我想对 Python 所提供的包进行一个简单的浏览,并且解释为什么 ElementTree
是你最应该用的那一个。
xml.dom.*
模块 - 是 W3C DOM API 的实现。如果你有处理 DOM API 的需要,那么这个模块适合你。注意:在 xml.dom 包里面有许多模块,注意它们之间的不同。
xml.sax.*
模块 - 是 SAX API 的实现。这个模块牺牲了便捷性来换取速度和内存占用。SAX 是一个基于事件的 API,这就意味着它可以“在空中”(on the fly)处理庞大数量的的文档,不用完全加载进内存(见注释1)。
xml.parser.expat
- 是一个直接的,低级一点的基于 C 的 expat
的语法分析器(见注释2)。 expat
接口基于事件反馈,有点像 SAX 但又不太像,因为它的接口并不是完全规范于 expat
库的。
最后,我们来看看 xml.etree.ElementTree
(以下简称 ET)。它提供了轻量级的 Python 式的 API ,它由一个 C 实现来提供。相对于 DOM 来说,ET 快了很多(见注释3)而且有很多令人愉悦的 API 可以使用。相对于 SAX 来说,ET 也有 ET.iterparse
提供了 “在空中” 的处理方式,没有必要加载整个文档到内存。ET 的性能的平均值和 SAX 差不多,但是 API 的效率更高一点而且使用起来很方便。我一会儿会给你们看演示。
我的建议 是尽可能的使用 ET 来处理 XML ,除非你有什么非常特别的需要。
ElementTree
生来就是为了处理 XML ,它在 Python 标准库中有两种实现。一种是纯 Python 实现例如 xml.etree.ElementTree
,另外一种是速度快一点的 xml.etree.cElementTree
。你要记住: 尽量使用 C 语言实现的那种,因为它速度更快,而且消耗的内存更少。如果你的电脑上没有 _elementtree
(见注释4) 那么你需要这样做:
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
这是一个让 Python 不同的库使用相同 API 的一个比较常用的办法。还是那句话,你的编译环境和别人的很可能不一样,所以这样做可以防止一些莫名其妙的小问题。注意:从 Python 3.3 开始,你没有必要这么做了,因为 ElementTree
模块会自动寻找可用的 C 库来加快速度。所以只需要 import xml.etree.ElementTree
就可以了。但是在 3.3 正式推出之前,你最好还是使用我上面提供的那段代码。
我们来讲点基础的。XML 是一种分级的数据形式,所以最自然的表示方法是将它表示为一棵树。ET 有两个对象来实现这个目的 - ElementTree
将整个 XML 解析为一棵树, Element
将单个结点解析为树。如果是整个文档级别的操作(比如说读,写,找到一些有趣的元素)通常用 ElementTree
。单个 XML 元素和它的子元素通常用 Element
。下面的例子能说明我刚才啰嗦的一大堆。(见注释5)
我们用这个 XML 文件来做例子:
<?xml version="1.0"?>
<doc>
<branch name="testing" hash="1cdf045c">
text,source
</branch>
<branch name="release01" hash="f200013e">
<sub-branch name="subrelease01">
xml,sgml
</sub-branch>
</branch>
<branch name="invalid">
</branch>
</doc>
让我们加载并且解析这个 XML :
>>> import xml.etree.cElementTree as ET
>>> tree = ET.ElementTree(file='doc1.xml')
然后抓根结点元素:
>>> tree.getroot()
<Element 'doc' at 0x11eb780>
和预期一样,root 是一个 Element
元素。我们可以来看看:
>>> root = tree.getroot()
>>> root.tag, root.attrib
('doc', {})
看吧,根元素没有任何状态(见注释6)。就像任何 Element
一样,它可以找到自己的子结点:
>>> for child_of_root in root:
... print child_of_root.tag, child_of_root.attrib
...
branch {'hash': '1cdf045c', 'name': 'testing'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}
我们也可以进入一个指定的子结点:
>>> root[0].tag, root[0].text
('branch', '\n text,source\n ')
从上面的例子我们可以轻而易举的看到,我们可以用一个简单的递归获取 XML 中的任何元素。然而,因为这个操作比较普遍,ET 提供了一些有用的工具来简化操作.
Element
对象有一个 iter
方法可以对子结点进行深度优先遍历。 ElementTree
对象也有 iter
方法来提供便利。
>>> for elem in tree.iter():
... print elem.tag, elem.attrib
...
doc {}
branch {'hash': '1cdf045c', 'name': 'testing'}
branch {'hash': 'f200013e', 'name': 'release01'}
sub-branch {'name': 'subrelease01'}
branch {'name': 'invalid'}
遍历所有的元素,然后检验有没有你想要的。ET 可以让这个过程更便捷。 iter
方法接受一个标签名字,然后只遍历那些有指定标签的元素:
>>> for elem in tree.iter(tag='branch'):
... print elem.tag, elem.attrib
...
branch {'hash': '1cdf045c', 'name': 'testing'}
branch {'hash': 'f200013e', 'name': 'release01'}
branch {'name': 'invalid'}
为了寻找我们感兴趣的元素,一个更加有效的办法是使用 XPath 支持。 Element
有一些关于寻找的方法可以接受 XPath 作为参数。 find
返回第一个匹配的子元素, findall
以列表的形式返回所有匹配的子元素, iterfind
为所有匹配项提供迭代器。这些方法在 ElementTree
里面也有。
给出一个例子:
>>> for elem in tree.iterfind('branch/sub-branch'):
... print elem.tag, elem.attrib
...
sub-branch {'name': 'subrelease01'}
这个例子在 branch
下面找到所有标签为 sub-branch
的元素。然后给出如何找到所有的 branch
元素,用一个指定 name
的状态即可:
>>> for elem in tree.iterfind('branch[@name="release01"]'):
... print elem.tag, elem.attrib
...
branch {'hash': 'f200013e', 'name': 'release01'}
想要深入学习 XPath 的话,请看 这里 。
ET 提供了建立 XML 文档和写入文件的便捷方式。 ElementTree
对象提供了 write
方法。
现在,这儿有两个常用的写 XML 文档的脚本。
修改文档可以使用 Element
对象的方法:
>>> root = tree.getroot()
>>> del root[2]
>>> root[0].set('foo', 'bar')
>>> for subelem in root:
... print subelem.tag, subelem.attrib
...
branch {'foo': 'bar', 'hash': '1cdf045c', 'name': 'testing'}
branch {'hash': 'f200013e', 'name': 'release01'}
我们在这里删除了根元素的第三个子结点,然后为第一个子结点增加新状态。然后这个树可以写回到文件中。
>>> import sys
>>> tree.write(sys.stdout) # ET.dump can also serve this purpose
<doc>
<branch foo="bar" hash="1cdf045c" name="testing">
text,source
</branch>
<branch hash="f200013e" name="release01">
<sub-branch name="subrelease01">
xml,sgml
</sub-branch>
</branch>
</doc>
注意状态的顺序和原文档的顺序不太一样。这是因为 ET 讲状态保存在无序的字典中。语义上来说,XML 并不关心顺序。
建立一个全新的元素也很容易。ET 模块提供了 SubElement
函数来简化过程:
>>> a = ET.Element('elem')
>>> c = ET.SubElement(a, 'child1')
>>> c.text = "some text"
>>> d = ET.SubElement(a, 'child2')
>>> b = ET.Element('elem_b')
>>> root = ET.Element('root')
>>> root.extend((a, b))
>>> tree = ET.ElementTree(root)
>>> tree.write(sys.stdout)
<root><elem><child1>some text</child1><child2 /></elem><elem_b /></root>
就像我在文章一开头提到的那样,XML 文档通常比较大,所以将它们全部读入内存的库可能会有点儿小问题。这也是为什么我建议使用 SAX API 来替代 DOM 。
我们刚讲过如何使用 ET 来将 XML 读入内存并且处理。但它就不会碰到和 DOM 一样的内存问题么?当然会。这也是为什么这个包提供一个特殊的工具,用来处理大型文档,并且解决了内存问题,这个工具叫 iterparse
。
我给大家演示一个 iterparse
如何使用的例子。我用 自动生成 拿到了一个 XML 文档来进行说明。这只是开头的一小部分:
<?xml version="1.0" standalone="yes"?>
<site>
<regions>
<africa>
<item id="item0">
<location>United States</location> <!-- Counting locations -->
<quantity>1</quantity>
<name>duteous nine eighteen </name>
<payment>Creditcard</payment>
<description>
<parlist>
[...]
我已经用注释标出了我要处理的元素,我们用一个简单的脚本来计数有多少 location
元素并且文本内容为“Zimbabwe”。这是用 ET.parse
的一个标准的写法:
tree = ET.parse(sys.argv[2])
count = 0
for elem in tree.iter(tag='location'):
if elem.text == 'Zimbabwe':
count += 1
print count
所有 XML 树中的元素都会被检验。当处理一个大约 100MB 的 XML 文件时,占用的内存大约是 560MB ,耗时 2.9 秒。
注意:我们并不需要在内存中加载整颗树。它检测我们需要的带特定值的 location
元素。其他元素被丢弃。这是 iterparse
的来源:
count = 0
for event, elem in ET.iterparse(sys.argv[2]):
if event == 'end':
if elem.tag == 'location' and elem.text == 'Zimbabwe':
count += 1
elem.clear() # discard the element
print count
这个循环遍历 iterparse
事件,检测“闭合的”(end)事件并且寻找 location
标签和指定的值。在这里 elem.clear()
是关键 - iterparse
仍然建立一棵树,只不过不需要全部加载进内存,这样做可以有效的利用内存空间(见注释7)。
处理同样的文件,这个脚本占用内存只需要仅仅的 7MB ,耗时 2.5 秒。速度的提升归功于生成树的时候只遍历一次。相比较来说, parse
方法首先建立了整个树,然后再次遍历来寻找我们需要的元素(所以慢了一点)。
在 Python 众多处理 XML 的模块中, ElementTree
真是屌爆了。它将轻量,符合 Python 哲学的 API ,出色的性能完美的结合在了一起。所以说如果要处理 XML ,果断地使用它吧!
这篇文章简略地谈了谈 ET 。我希望这篇拙作可以抛砖引玉。
注释1:和 DOM 不一样,DOM 将整个 XML 加载进内存并且允许随机访问任何深度地元素。
注释2: expat 是一个开源的用于处理 XML 的 C 语言库。Python 将它融合进自身。
注释3:Fredrik Lundh,是 ElementTree 的原作者,他提到了一些 基准 。
注释4:当我提到 _elementtree
的时候,我意思是 C 语言的 cElementTree._elementtree
扩展模块。
注释5:确定你手边有 模块手册 然后可以随时查阅我提到的方法和函数。
注释6: 状态 是一个意义太多的术语。Python 对象有状态,XML 元素也有状态。希望我能将它们表达的更清楚一点。
注释7:准确来说,树的根元素仍然存活。在某些情况下根结点非常大,你也可以丢弃它,但那需要多一点点代码。
【这篇文章所描述的 Python 版本是 3.x,更确切地说,是 CPython 3.3 alpha。】
在 Python 中,可调用对象 (callable) 的概念是十分基本的。当我们说什么东西是“可调用的”,马上可以联想到的显而易见的答案便是函数。无论是用户定义的函数 (你所编写的) 还是内置的函数 (经常是在 CPython 解析器内由 C 实现的),他们总是用来被调用的,不是么?
当然,还有方法也可以调用,但他们仅仅是被限制在对象中的特殊函数而已,没什么有趣的地方。还有什么可以被调用呢?你可能知道,也可能不知道,只要一个对象所属的类定义了 __call__
魔术方法,它也是可以被调用的。所以对象可以像函数那样使用。再深入思考一点,类也是可以被调用的。终究,我们是这样创建新的对象的:
class Joe:
... [contents of class]
joe = Joe()
在这里,我们“调用”了 Joe
来创建新的实例。所以说类也可以像函数那样使用!
可以证明,所有这些概念都很漂亮地在 CPython 被实现。在 Python 中,一切皆对象,包括我们在前面的段落中提到的每一个东西 (用户定义和内置函数、方法、对象、类)。所有这些调用都是由一个单一的机制来完成的。这一机制十分优雅,并且一点都不难理解,所以这很值得我们去了解。不过首先我们从头开始。
CPython 经过两个主要的步骤来执行我们的程序:
在这一节中,我会粗略地概括一下第一步中如何处理一个调用。我不会深入这些细节,而且他们也不是我想在这篇文章中关注的真正有趣的部分。如果你想了解更多 Python 代码在编译器中经历的流程,可以阅读 这篇文章 。
简单地来说,Python 编译器将表达式中的所有类似 (参数 …)
的结构都识别为一个调用 [1] 。这个操作的 AST 节点叫 Call
,编译器通过 Python/compile.c
文件中的 compiler_call
函数来生成 Call
对应的代码。在大多数情况下会生成 CALL_FUNCTION
字节码指令。它也有一些变种,例如含有“星号参数”——形如 func(a, b, *args)
,有一个专门的指令 CALL_FUNCTION_VAR
,但这些都不是我们文章所关注的,所以就忽略掉好了,它们仅仅是这个主题的一些小变种而已。
于是 CALL_FUNCTION
就是我们这儿所关注的指令。这是 它做了什么 :
CALL_FUNCTION(argc)
调用一个函数。
argc
的低字节描述了定位参数 (positional parameters) 的数量,高字节则是关键字参数 (keyword parameters) 的数量。在栈中,操作码首先找到关键字参数。对于每个关键字参数,值在键的上面。而定位参数则在关键词参数的下面,其中最右边的参数在最上面。在所有参数下面,是要被调用的函数对象。将所有的函数参数和函数本身出栈,并将返回值压入栈。
CPython 的字节码由 Python/ceval.c
文件的一个巨大的函数 PyEval_EvalFrameEx
来执行。这个函数十分恐怖,不过也仅仅是一个特别的操作码分发器而已。他从指定帧的代码对象中读取指令并执行它们。例如说这里是 CALL_FUNCTION
的处理器 (进行了一些清理,移除了跟踪和计时的宏):
TARGET(CALL_FUNCTION)
{
PyObject **sp;
sp = stack_pointer;
x = call_function(&sp, oparg);
stack_pointer = sp;
PUSH(x);
if (x != NULL)
DISPATCH();
break;
}
并不是很难——事实上它十分容易看懂。 call_function
根本没有真正进行调用 (我们将在之后细究这件事), oparg
是指令的数字参数, stack_pointer
则指向栈顶 [2] 。 call_function
返回的值被压入栈中, DISPATCH
仅仅是调用下一条指令的宏。
call_function
也在 Python/ceval.c
文件。它真正实现了这条指令的功能。它虽然不算很长,但80行也已经长到我不可能把它完全贴在这儿了。我将会从总体上解释这个流程,并贴一些相关的小代码片段取而代之。你完全可以在你最喜欢的编辑器中打开这些代码。
要理解调用过程在 Python 中是如何进行的,最重要的第一步是忽略 call_function
所做的大多数事情。是的,我就是这个意思。这个函数最最主要的代码都是为了对各种情况进行优化。完全移除这些对解析器的正确性毫无影响,影响的仅仅是它的性能。如果我们忽略所有的时间优化, call_function
所做的仅仅是从单参数的 CALL_FUNCTION
指令中解码参数和关键词参数的数量,并且将它们转给 do_call
。我们将在后面重新回到这些优化因为他们很有意思,不过现在先让我们看看核心的流程。
do_call
从栈中将参数加载到 PyObject
对象中 (定位参数存入一个元组,关键词对象存入一个字典),做一些跟综和优化,最后调用 PyObject_Call
。
PyObject_Call
是一个极其重要的函数。它可以在 Python 的 C API 中被扩展。这就是它完整的代码:
PyObject *
PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
{
ternaryfunc call;
if ((call = func->ob_type->tp_call) != NULL) {
PyObject *result;
if (Py_EnterRecursiveCall(" while calling a Python object"))
return NULL;
result = (*call)(func, arg, kw);
Py_LeaveRecursiveCall();
if (result == NULL && !PyErr_Occurred())
PyErr_SetString(
PyExc_SystemError,
"NULL result without error in PyObject_Call");
return result;
}
PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",
func->ob_type->tp_name);
return NULL;
}
抛开深递归保护和错误处理 [3] , PyObject_Call
提取出对象的 tp_call
属性并且调用它 [4] , tp_call
是一个函数指针,因此我们可以这样做。
先让它这样一会儿。忽略所有那些精彩的优化, Python 中的所有调用 都可以浓缩为下面这些内容:
tp_call
将被调用。作为一个 Python 用户,你唯一需要直接与 tp_call
进行的交互是在你希望你的对象可以被调用的时候。当你在 Python 中定义你的类时,你需要实现 __call__
方法来达到这一目的。这个方法被 CPython 直接映射到了 tp_call
上。如果你在 C 扩展中定义你的类,你需要自己手动给类对象的 tp_call
属性赋值。
我们回想起类本身也可以被“调用”以创建新的对象,所以 tp_call
也在这里起到了作用。甚至更加基本地,当你定义一个类时也会产生一次调用——在类的元类中。这是一个有意思的话题,我将会在未来的文章中讨论它。
文章的主要部分在前面那个小节已经讲完了,所以这一部分是选读的。之前说过,我觉得这些内容很有意思,它展示了一些你可能并不认为是对象但事实上却是对象的东西。
我之前提到过,我们对于所有的 CALL_FUNCTION
仅仅需要使用 PyObject_Call
就可以处理。事实上,对一些常见的情况做一些优化是很有意义的,对这些情况来说,前面的方法可能过于麻烦了。 PyObject_Call
是一个非常通用的函数,它需要将所有的参数放入专门的元组和字典对象中 (按顺序对应于定位参数和关键词参数)。 PyObject_Call
需要它的调用者为它从栈中取出所有这些参数,并且存放好。然而在一些常见的情况中,我们可以避免很多这样的开销,这正是 call_function
中优化的所在。
在 call_function
中的第一个特殊情况是:
/* Always dispatch PyCFunction first, because these are
presumed to be the most frequent callable object.
*/
if (PyCFunction_Check(func) && nk == 0) {
这处理了 builtin_function_or_method
类型的对象 (在 C 实现中表现为 PyCFunction 类型)。正如上面的注释所说的,Python 里有很多这样的函数。所有使用 C 实现的函数,无论是 CPython 解析器自带的还是 C 扩展里的,都会进入这一类。例如说:
>>> type(chr)
<class 'builtin_function_or_method'>
>>> type("".split)
<class 'builtin_function_or_method'>
>>> from pickle import dump
>>> type(dump)
<class 'builtin_function_or_method'>
这里的 if
还有一个附加条件——传入函数的关键词参数数量为0。如果这个函数不接受任何参数 (在函数创建时以 METH_NOARGS
标志标明) 或仅仅一个对象参数 (METH_0
标志), call_function
就不需要通过正常的参数打包流程而可以直接调用函数指针。为了搞清楚这是如何实现的,我高度推荐你读一读 文档这个部分 关于 PyCFunction
和 METH_
标志的介绍。
下面,还有一个对 Python 写的类方法的特殊处理:
else {
if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
PyMethod
是一个用于表示 有界方法 (bound methods) 的内部对象。方法的特殊之处在于它还带有一个所在对象的引用。 call_function
提取这个对象并且将他放入栈中作为下一步的准备工作。
这是调用部分的代码剩下的部分 (在这之后在 call_object
中只有一些清理栈的代码):
if (PyFunction_Check(func))
x = fast_function(func, pp_stack, n, na, nk);
else
x = do_call(func, pp_stack, na, nk);
我们已经见过 do_call
了——它实现了调用的最通用形式。然而,这里还有一个优化——如果 func
是一个 PyFunction
对象 (一个在 内部 用于表示使用 Python 代码定义的函数的对象),程序选择了另一条路径—— fast_function
。
为了理解 fast_function
做了什么,最重要的是首先要考虑在执行一个 Python 函数时发生了什么。简单地说,它的代码对象被执行 (也就是 PyEval_EvalCodeEx
本身)。这些代码期望它的参数已经在栈中,因此在大多数情况下,没必要将参数打包到容器中再重新释放出来。稍稍注意一下,就可以将参数留在栈中,这样许多宝贵的 CPU 周期就可以被节省出来。
剩下的一切最终落回到 do_call
上,顺便,包括含有关键词参数的 PyCFunction 对象。一个不寻常的事实是,对于那些既接受关键词参数又接受定位参数的 C 函数,不给它们传递关键词参数要稍稍更高效一些。例如说 [6] :
$ ~/test/python_src/33/python -m timeit -s's="a;b;c;d;e"' 's.split(";")'
1000000 loops, best of 3: 0.3 usec per loop
$ ~/test/python_src/33/python -m timeit -s's="a;b;c;d;e"' 's.split(sep=";")'
1000000 loops, best of 3: 0.469 usec per loop
这是一个巨大的差异,但输入数据很小。对于更大的字符串,这个差异就几乎没有了:
$ ~/test/python_src/33/python -m timeit -s's="a;b;c;d;e"*1000' 's.split(";")'
10000 loops, best of 3: 98.4 usec per loop
$ ~/test/python_src/33/python -m timeit -s's="a;b;c;d;e"*1000' 's.split(sep=";")'
10000 loops, best of 3: 98.7 usec per loop
这篇文章的目的是讨论在 Python 中,可调用对象意味着什么,并且从尽可能最底层的概念——CPython 虚拟机中的实现细节——来接近它。就我个人来说,我觉得这个实现非常优雅,因为它将不同的概念统一到了同一个东西上。在附加部分里我们看到,在 Python 中有些我们常常认为不是对象的东西如函数和方法,实际上也是对象,并且也可以以相同的统一的方法来处理。我保证了,在以后的文章中我将会深入 tp_call
创建新的 Python 对象和类的内容。
[1] | 这是故意的简化—— () 同样可以用作其他用途如类定义 (用以列举基类)、函数定义 (列举参数)、修饰器等等,但它们并不在表达式中。我同样也故意忽略了生成器表达式。 |
[2] | CPython 虚拟机是一个 栈机器 。 |
[3] | 在 C 代码可能结束调用 Python 代码的地方需要使用 Py_EnterRecursiveCall 来让 CPython 保持对递归层级的跟踪,并在递归过深时跳出。注意,用 C 写的函数并不需要遵守这个递归限制。这也是为什么 do_call 的特殊情况 PyCFunction 先于调用 PyObject_Call 。 |
[4] | 这里的“属性”我表示的是一个结构体的字段。如果你对于 Python C 扩展的定义方式完全不熟悉,可以看看 这个页面 。 |
[5] | 当我说 一切 皆对象时,我的意思就是它。你也许会觉得对象是你定义的类的实例。然而,深入到 C 一级,CPython 如你一样创建和耍弄许许多多的对象。类型 (类)、内置对象、函数、模块,所有这些都表现为对象。 |
[6] | 这个例子只能在 Python 3.3 中运行,因为 split 的 sep 这个关键词参数是在这个版本中新加的。在之前版本的 Python 中 split 仅仅接受定位参数。 |
大家好!
最近我了解到了一个云服务叫做 DotCloud . 它看起来不错,于是我想看看它对 Python 的支持情况如何. 我已经看了他们的文档,也看了他们的 Django 相关的文件,大体讲的是说他们支持 WSGI .而我想知道是在这它上面部署一项服务到底好不好用.于是我就开始了,注册了它然后部署了一个非常简单的 TurboGears 项目在它上面.结果看起来还不错.
来让我们继续一起部署一个简单快速的项目吧.
首先呢,你需要一个 DotCloud 账户。注册完后,你需要安装 dotcloud 的 CLI(http://docs.dotcloud.com/firststeps/install/)
现在我们既然已经没什么阻碍了,就开始创建 TurboGears 项目吧.
$ virtualenv --no-site-packages tg2env
$ cd tg2env/
$ source bin/activate
(tg2env)$ easy_install -i http://tg.gy/current tg.devtools
(tg2env)$ paster quickstart -x -n -m example
(tg2env)$ cd example
(tg2env)$ python setup.py develop
上面的命令行是做什么的呢?
需要更多的信息,你只需要运行命令行 paster quickstart --help
或者访问 Quickstarting A TurboGears2 Project 的文档。
我们需要 requirements.txt
这个文件,因为 DotCloud 使用它来安装我们项目的附件.接下来就创建一个 requirements.txt
然后添加下面的代码:
# requirements.txt
-i http://www.turbogears.org/2.1/downloads/current/index
tg.devtools
我们在这朵云上建一个应用程序吧.使用 dotcloud create tgtest
命令行. 你也许很想知道”它是怎么工作的“. 但我们要知道 除非我们把一个在我们的项目中里做 wsgi.py
的文件中输入以下的代码,否则它是不可用的.
# wsgi.py
from paste.deploy import loadapp
application = loadapp('config:/home/dotcloud/current/development.ini')
在我们的项目的地址中,我们需要一个 dotcloud.yml
文件,这样 DotCloud 就知道我们要要用什么了。
# dotcloud.yml
www:
type: python
我们就剩一个东西没有做了。既然这个只是为了测试而建立的程序,我们就把配置文件叫做 development.ini
。 在这个文件中,有一个指令 debug = true
,把他改为 false,像这样 debug = false
。 现在我们就可以发布我们的项目了。
dotcloud push tgtest
接下来就坐着静静的等待你的终端显示出以下的代码吧
18:54:28 [www] Using /home/dotcloud/env/lib/python2.6/site-packages
18:54:28 [www] Finished processing dependencies for example==0.1dev
18:54:32 [www] Build completed successfully. Compiled image size is 17MB
18:54:32 ---> Initializing new services... (This may take a few minutes)
18:54:32 [www.0] Initializing...
18:54:42 [www.0] Service initialized
18:54:44 ---> All services have been initialized. Deploying code...
18:54:44 [www.0] Deploying build revision rsync-1332269603326...
18:54:50 [www.0] Running postinstall script...
18:54:52 [www.0] Launching...
18:54:53 [www.0] Waiting for the service to become responsive...
18:54:54 [www.0] Re-routing traffic to the new build...
18:54:55 [www.0] Successfully deployed build revision rsync-1332269603326
18:54:55 ---> Deploy finished
祝你好运!
部署完毕,你的应用可以通过下面的链接访问了~
此教程为我的数篇文章中的一个重点。主题是魔术方法。
什么是魔术方法?他们是面向对象的Python的一切。他们是可以给你的类增加”magic”的特殊方法。他们总是被双下划线所包围(e.g. __init__
或者 __lt__
)。然而他们的文档却远没有提供应该有的内容。Python中所有的魔术方法均在Python官方文档中有相应描述,但是对于他们的描述比较混乱而且组织比较松散。很难找到有一个例子(也许他们原本打算的很好,在开始语言参考中有描述很详细,然而随之而来的确是枯燥的语法描述等等)。
所以,为了修补我认为Python文档应该修补的瑕疵,我决定给Python中的魔术方法提供一些用平淡的语言和实例驱使的文档。我在开始已经写了数篇博文,现在在这篇文章中对他们进行总结。
我希望你能够喜欢这篇文章。你可以将之当做一个教程,一个补习资料,或者一个参考。本文章的目的仅仅是为Python中的魔术方法提供一个友好的教程。
每个人都知道一个最基本的魔术方法, __init__
。通过此方法我们可以定义一个对象的初始操作。然而,当我调用 x = SomeClass()
的时候, __init__
并不是第一个被调用的方法。实际上,还有一个叫做 __new__
的方法,来构造这个实例。然后给在开始创建时候的初始化函数来传递参数。在对象生命周期的另一端,也有一个 __del__
方法。我们现在来近距离的看一看这三个方法:
__new__(cls, [...)
__new__
是在一个对象实例化的时候所调用的第一个方法。它的第一个参数是这个类,其他的参数是用来直接传递给 __init__
方法。 __new__
方法相当不常用,但是它有自己的特性,特别是当继承一个不可变的类型比如一个tuple或者string。我不希望在 __new__
上有太多细节,因为并不是很有用处,但是在 Python文档 中有详细的阐述。
__init__(self, […)
此方法为类的初始化方法。当构造函数被调用的时候的任何参数都将会传给它。(比如如果我们调用 x = SomeClass(10, 'foo')
),那么 __init__
将会得到两个参数10和foo。 __init__
在Python的类定义中被广泛用到。
__del__(self)
如果 __new__
和 __init__
是对象的构造器的话,那么 __del__
就是析构器。它不实现语句 del x
(以上代码将不会翻译为 x.__del__()
)。它定义的是当一个对象进行垃圾回收时候的行为。当一个对象在删除的时需要更多的清洁工作的时候此方法会很有用,比如套接字对象或者是文件对象。注意,如果解释器退出的时候对象还存存在,就不能保证 __del__
能够被执行,所以 __del__
can’t serve as a replacement for good coding practices ()~~~~~~~
放在一起的话,这里是一个 __init__
和 __del__
实际使用的例子。
from os.path import join
class FileObject:
'''给文件对象进行包装从而确认在删除时文件流关闭'''
def __init__(self, filepath='~', filename='sample.txt'):
#读写模式打开一个文件
self.file = open(join(filepath, filename), 'r+')
def __del__(self):
self.file.close()
del self.file
使用Python的魔术方法的最大优势在于他们提供了一种简单的方法来让对象可以表现的像内置类型一样。那意味着你可以避免丑陋的,违反直觉的,不标准的的操作方法。在一些语言中,有一些操作很常用比如:
if instance.equals(other_instance):
# do something
在Python中你可以这样。但是这会让人迷惑且产生不必要的冗余。相同的操作因为不同的库会使用不同的名字,这样会产生不必要的工作。然而有了魔术方法的力量,我们可以定义一个方法(本例中为 __eq__
),就说明了我们的意思:
if instance == other_instance:
#do something
这只是魔术方法的功能的一小部分。它让你可以定义符号的含义所以我们可以在我们的类中使用。就像内置类型一样。
Python对实现对象的比较,使用魔术方法进行了大的逆转,使他们非常直观而不是笨拙的方法调用。而且还提供了一种方法可以重写Python对对象比较的默认行为(通过引用)。以下是这些方法和他们的作用。
__cmp__(self, other)
__cmp__
是最基本的用于比较的魔术方法。它实际上实现了所有的比较符号(<,==,!=,etc.),但是它的表现并不会总是如你所愿(比如,当一个实例与另一个实例相等是通过一个规则来判断,而一个实例大于另外一个实例是通过另外一个规则来判断)。如果 self < other
的话 __cmp__
应该返回一个负数,当 self == other
的时候会返回0 ,而当 self > other
的时候会返回正数。通常最好的一种方式是去分别定义每一个比较符号而不是一次性将他们都定义。但是 __cmp__
方法是你想要实现所有的比较符号而一个保持清楚明白的一个好的方法。
__eq__(self, other)
定义了等号的行为, ==
。
__ne__(self, other)
定义了不等号的行为, !=
。
__lt__(self, other)
定义了小于号的行为, <
。
__gt__(self, other)
定义了大于等于号的行为, >=
。
举一个例子,创建一个类来表现一个词语。我们也许会想要比较单词的字典序(通过字母表),通过默认的字符串比较的方法就可以实现,但是我们也想要通过一些其他的标准来实现,比如单词长度或者音节数量。在这个例子中,我们来比较长度实现。以下是实现代码:
class Word(str):
'''存储单词的类,定义比较单词的几种方法'''
def __new__(cls, word):
# 注意我们必须要用到__new__方法,因为str是不可变类型
# 所以我们必须在创建的时候将它初始化
if ' ' in word:
print "Value contains spaces. Truncating to first space."
word = word[:word.index(' ')] #单词是第一个空格之前的所有字符
return str.__new__(cls, word)
def __gt__(self, other):
return len(self) > len(other)
def __lt__(self, other):
return len(self) < len(other)
def __ge__(self, other):
return len(self) >= len(other)
def __le__(self, other):
return len(self) <= len(other)
现在,我们创建两个 Words
对象(通过使用 Word('foo')
和 Word('bar')
然后通过长度来比较它们。注意,我们没有定义 __eq__
和 __ne__
方法。这是因为将会产生一些怪异的结果(比如 Word('foo') == Word('bar')
将会返回true)。这对于测试基于长度的比较不是很有意义。所以我们退回去,用 str
内置来进行比较。
现在你知道你不必定义每一个比较的魔术方法从而进行丰富的比较。标准库中很友好的在 functiontols
中提供给我们一个类的装饰器定义了所有的丰富的比较函数。如果你只是定义 __eq__
和另外一个(e.g. __gt__
, __lt__
,etc.)这个特性仅仅在Python 2.7中存在,但是你如果有机会碰到的话,那么将会节省大量的时间和工作量。你可以通过在你定义的类前放置 @total_ordering
来使用。
如同你在通过比较符来比较类的实例的时候来创建很多方法,你也可以定义一些数值符号的特性。系紧你的安全带,来吧,这里有很多内容。为了组织方便,我将会把数值处理的方法来分成五类:一元操作符,普通算数操作符,反射算数操作符(之后会详细说明),增量赋值,和类型转换。
仅仅有一个操作位的一元操作符和函数。比如绝对值,负等。
__pos__(self)
实现正号的特性(比如 +some_object
)
__neg__(self)
实现负号的特性(比如 -some_object
)
__abs__(self)
实现内置 abs()
函数的特性。
__invert__(self)
实现 ~
符号的特性。为了说明这个特性。你可以查看 Wikipedia中的这篇文章
现在我们仅仅覆盖了普通的二进制操作符:+,-,*和类似符号。这些符号大部分来说都浅显易懂。
__add__(self, other)
实现加法。
__sub__(self, other)
实现减法。
__mul__(self, other)
实现乘法。
__floordiv__(self, other)
实现 //
符号实现的整数除法。
__div__(self, other)
实现 /
符号实现的除法。
__truediv__(self, other)
实现真除法。注意只有只用了 from __future__ import division
的时候才会起作用。
__mod__(self, other)
实现取模算法 %
__divmod___(self, other)
实现内置 divmod()
算法
__pow__
实现使用 **
的指数运算
__lshift__(self, other)
实现使用 <<
的按位左移动
__rshift__(self, other)
实现使用 >>
的按位左移动
__and__(self, other)
实现使用 &
的按位与
__or__(self, other)
实现使用 |
的按位或
__xor__(self, other)
实现使用 ^
的按位异或
下面我将会讲解一些反运算的知识。有些概念你可能会认为恐慌或者是陌生。但是实际上非常简单。以下是一个例子:
some_object + other
这是一个普通的加法运算,反运算是相同的,只是把操作数调换了位置:
other + some_object
所以,除了当与其他对象操作的时候自己会成为第二个操作数之外,所有的这些魔术方法都与普通的操作是相同的。大多数情况下,反运算的结果是与普通运算相同的。所以你可以你可以将 __radd__
与 __add__
等价。
__radd__(self, other)
实现反加
__rsub__(self, other)
实现反减
__rmul__(self, other)
实现反乘
__rfloordiv__(self, other)
实现 //
符号的反除
__rdiv__(self, other)
实现 /
符号的反除
__rtruediv__(self, other)
实现反真除,只有当 from __future__ import division
的时候会起作用
__rmod__(self, other)
实现 %
符号的反取模运算
__rdivmod__(self, other)
当 divmod(other, self)
被调用时,实现内置 divmod()
的反运算
__rpow__
实现 **
符号的反运算
__rlshift__(self, other)
实现 <<
符号的反左位移
__rrshift__(self, other)
实现 >>
符号的反右位移
__rand__(self, other)
实现 &
符号的反与运算
__ror__(self, other)
实现 |
符号的反或运算
__xor__(self, other)
实现 ^
符号的反异或运算
Python也有大量的魔术方法可以来定制增量赋值语句。你也许对增量赋值已经很熟悉,它将操作符与赋值来结合起来。如果你仍然不清楚我在说什么的话,这里有一个例子:
x = 5
x += 1 # in other words x = x + 1
__iadd__(self, other)
实现赋值加法
__isub__(self, other)
实现赋值减法
__imul__(self, other)
实现赋值乘法
__ifloordiv__(self, other)
实现 //=
的赋值地板除
__idiv__(self, other)
实现符号 /=
的赋值除
__itruediv__(self, other)
实现赋值真除,只有使用 from __future__ import division
的时候才能使用
__imod_(self, other)
实现符号 %=
的赋值取模
__ipow__
实现符号 **=
的赋值幂运算
__ilshift__(self, other)
实现符号 <<=
的赋值位左移
__irshift__(self, other)
实现符号 >>=
的赋值位右移
__iand__(self, other)
实现符号 &=
的赋值位与
__ior__(self, other)
实现符号 |=
的赋值位或
__ixor__(self, other)
实现符号 |=
的赋值位异或
Python也有很多的魔术方法来实现类似 float()
的内置类型转换特性。
__int__(self)
实现整形的强制转换
__long__(self)
实现长整形的强制转换
__float__(self)
实现浮点型的强制转换
__complex__(self)
实现复数的强制转换
__oct__(self)
实现八进制的强制转换
__hex__(self)
实现二进制的强制转换
__index__(self)
当对象是被应用在切片表达式中时,实现整形强制转换,如果你定义了一个可能在切片时用到的定制的数值型,你应该定义 __index__
(详见PEP357)
__trunc__(self)
当使用 math.trunc(self)
的时候被调用。 __trunc__
应该返回数值被截取成整形(通常为长整形)的值
__coerce__(self, other)
实现混合模式算数。如果类型转换不可能的话,那么 __coerce__
将会返回 None
,否则他将对 self
和 other
返回一个长度为2的tuple,两个为相同的类型。
如果有一个字符串来表示一个类将会非常有用。在Python中,有很多方法可以实现类定义内置的一些函数的返回值。
__str__(self)
定义当 str()
调用的时候的返回值
__repr__(self)
定义 repr()
被调用的时候的返回值。 str()
和 repr()
的主要区别在于 repr()
返回的是机器可读的输出,而 str()
返回的是人类可读的。
__unicode__(self)
定义当 unicode()
调用的时候的返回值。 unicode()
和 str()
很相似,但是返回的是unicode字符串。注意,如a果对你的类调用 str()
然而你只定义了 __unicode__()
,那么将不会工作。你应该定义 __str__()
来确保调用时能返回正确的值。
__hash__(self)
定义当 hash()
调用的时候的返回值,它返回一个整形,用来在字典中进行快速比较
__nonzero__(self)
定义当 bool()
调用的时候的返回值。本方法应该返回True或者False,取决于你想让它返回的值。
许多从其他语言转到Python的人会抱怨它缺乏类的真正封装。(没有办法定义私有变量,然后定义公共的getter和setter)。Python其实可以通过魔术方法来完成封装。我们来看一下:
__getattr__(self, name)
你可以定义当用户试图获取一个不存在的属性时的行为。这适用于对普通拼写错误的获取和重定向,对获取一些不建议的属性时候给出警告(如果你愿意你也可以计算并且给出一个值)或者处理一个 AttributeError
。只有当调用不存在的属性的时候会被返回。然而,这不是一个封装的解决方案。
__setattr__(self, name, value)
与 __getattr__
不同, __setattr__
是一个封装的解决方案。无论属性是否存在,它都允许你定义对对属性的赋值行为,以为这你可以对属性的值进行个性定制。但是你必须对使用 __setattr__
特别小心。之后我们会详细阐述。
__delattr__
与 __setattr__
相同,但是功能是删除一个属性而不是设置他们。注意与 __setattr__
相同,防止无限递归现象发生。(在实现 __delattr__
的时候调用 del self.name
即会发生)
__getattribute__(self, name)
__getattribute__
与它的同伴 __setattr__
和 __delattr__
配合非常好。但是我不建议使用它。只有在新类型类定义中才能使用 __getattribute__
(在最新版本Python中所有的类都是新类型,在老版本中你可以通过继承 object
来制作一个新类。这样你可以定义一个属性值的访问规则。有时也会产生一些帝归现象。(这时候你可以调用基类的 __getattribute__
方法来防止此现象的发生。)它可以消除对 __getattr__
的使用,如果它被明确调用或者一个 AttributeError
被抛出,那么当实现 __getattribute__
之后才能被调用。此方法是否被使用其实最终取决于你的选择。)我不建议使用它因为它的使用几率较小(我们在取得一个值而不是设置一个值的时候有特殊的行为是非常罕见的。)而且它不能避免会出现bug。
在进行属性访问控制定义的时候你可能会很容易的引起一个错误。考虑下面的例子。
def __setattr__(self, name, value):
self.name = value
#每当属性被赋值的时候, ``__setattr__()`` 会被调用,这样就造成了递归调用。
#这意味这会调用 ``self.__setattr__('name', value)`` ,每次方法会调用自己。这样会造成程序崩溃。
def __setattr__(self, name, value):
self.__dict__[name] = value #给类中的属性名分配值
#定制特有属性
Python的魔术方法非常强大,然而随之而来的则是责任。了解正确的方法去使用非常重要。
所以我们对于定制属性访问权限了解了多少呢。它不应该被轻易的使用。实际上,它非常强大。但是它存在的原因是:Python 不会试图将一些不好的东西变得不可能,而是让它们难以实现。自由是至高无上的,所以你可以做任何你想做的。以下是一个特别的属性控制的例子(我们使用 super
因为不是所有的类都有 __dict__
属性):
class AccessCounter:
'''一个包含计数器的控制权限的类每当值被改变时计数器会加一'''
def __init__(self, val):
super(AccessCounter, self).__setattr__('counter', 0)
super(AccessCounter, self).__setattr__('value', val)
def __setattr__(self, name, value):
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
#如果你不想让其他属性被访问的话,那么可以抛出 AttributeError(name) 异常
super(AccessCounter, self).__setattr__(name, value)
def __delattr__(self, name):
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
super(AccessCounter, self).__delattr__(name)]
有很多方法让你的Python类行为可以像内置的序列(dict, tuple,list, string等等)。这是目前为止我最喜欢的魔术方法,因为它给你很搞的控制权限而且让很多函数在你的类实例上工作的很出色。但是在开始之前,需要先讲一些必须条件。
现在我们开始讲如何在Python中创建定制的序列,这个时候该讲一讲协议。协议(Protocols)与其他语言中的接口很相似。它给你很多你必须定义的方法。然而在Python中的协议是很不正式的,不需要明确声明实现。事实上,他们更像一种指南。
我们为什么现在讨论协议?因为如果要定制容器类型的话需要用到这些协议。首先,实现不变容器的话有一个协议:实现不可变容器,你只能定义 __len__
和 __getitem__
(一会会讲更多)。可变容器协议则需要所有不可变容器的所有另外还需要 __setitem__
和 __delitem__
。最终,如果你希望你的对象是可迭代的话,你需要定义 __iter__
会返回一个迭代器。迭代器必须遵循迭代器协议,需要有 __iter__
(返回它本身) 和 next
。
这些是容器使用的魔术方法。
__len__(self)
然会容器长度。对于可变不可变容器都需要有的协议的一部分。
__getitem__(self, key)
定义当一个条目被访问时,使用符号 self[key]
。这也是不可变容器和可变容器都要有的协议的一部分。如果键的类型错误和 KeyError
或者没有合适的值。那么应该抛出适当的 TypeError
异常。
__setitem__(self, key, value)
定义当一个条目被赋值时的行为,使用 self[key] = value
。这也是可变容器和不可变容器协议中都要有的一部分。
__delitem__(self, key)
定义当一个条目被删除时的行为(比如 del self[key]
)。这只是可变容器协议中的一部分。当使用一个无效的键时应该抛出适当的异常。
__iter__(self)
返回一个容器的迭代器。很多情况下会返回迭代器,尤其是当内置的 iter()
方法被调用的时候,或者当使用 for x in container
方式循环的时候。迭代器是他们本身的对象,他们必须定义返回 self
的 __iter__
方法。
__reversed__(self)
实现当 reversed()
被调用时的行为。应该返回列表的反转版本。
__contains__(self, item)
当调用 in
和 not in
来测试成员是否存在时候 __contains__
被定义。你问为什么这个不是序列协议的一部分?那是因为当 __contains__
没有被定义的时候,Python会迭代这个序列并且当找到需要的值时会返回 True
。
__concat__(self, other)
最终,你可以通过 __concat__
来定义当用其他的来连接两个序列时候的行为。当 +
操作符被调用时候会返回一个 self
和 other.__concat__
被调用后的结果产生的新序列。
在我们的例子中,让我们看一看你可能在其他语言中 用到的函数构造语句的实现(比如 Haskell)。
class FunctionalList:
'''一个封装了一些附加魔术方法比如 head, tail, init, last, drop, 和take的列表类。
'''
def __init__(self, values=None):
if values is None:
self.values = []
else:
self.values = values
def __len__(self):
return len(self.values)
def __getitem__(self, key):
#如果键的类型或者值无效,列表值将会抛出错误
return self.values[key]
def __setitem__(self, key, value):
self.values[key] = value
def __delitem__(self, key):
del self.values[key]
def __iter__(self):
return iter(self.values)
def __reversed__(self):
return reversed(self.values)
def append(self, value):
self.values.append(value)
def head(self):
return self.values[0]
def tail(self):
return self.values[1:]
def init(self):
#返回一直到末尾的所有元素
return self.values[:-1]
def last(self):
#返回末尾元素
return self.values[-1]
def drop(self, n):
#返回除前n个外的所有元素
return self.values[n:]
def take(self, n):
#返回前n个元素
return self.values[:n]
你可以通过魔术方法控制控制使用 isinstance()
和 issubclass()
内置方法的反射行为。这些魔术方法是:
__instancecheck__(self, instance)
检查一个实例是不是你定义的类的实例
__subclasscheck__(self, subclass)
检查一个类是不是你定义的类的子类
这些方法的用例似乎很少,这也许是真的。我不会花更多的时间在这些魔术方法上因为他们并不是很重要,但是他们的确反应了Python 中的面向对象编程的一些基本特性:非常容易的去做一些事情,即使并不是很必须。这些魔术方法看起来并不是很有用,但是当你需要的时候你会很高兴有这种特性。
你也许已经知道,在Python中,方法也是一种高等的对象。这意味着他们也可以被传递到方法中就像其他对象一样。这是一个非常惊人的特性。
在Python中,一个特殊的魔术方法可以让类的实例的行为表现的像函数一样,你可以调用他们,将一个函数当做一个参数传到另外一个函数中等等。这是一个非常强大的特性让Python编程更加舒适甜美。
__call__(self, [args...])
允许一个类的实例像函数一样被调用。实质上说,这意味着 x()
与 x.__call__()
是相同的。注意 __call__
参数可变。这意味着你可以定义 __call__
为其他你想要的函数,无论有多少个参数。
__call__
在那些类的实例经常改变状态的时候会非常有效。调用这个实例是一种改变这个对象状态的直接和优雅的做法。用一个实例来表达最好不过了:
class Entity:
'''调用实体来改变实体的位置。'''
def __init__(self, size, x, y):
self.x, self.y = x, y
self.size = size
def __call__(self, x, y):
'''改变实体的位置'''
self.x, self.y = x, y
在Python 2.5中,为了代码利用定义了一个新的关键词 with
语句。会话控制在Python中不罕见(之前是作为库的一部分被实现),直到 PEP343 被添加后。它被成为一级语言结构。你也许之前看到这样的语句:
with open('foo.txt') as bar:
# perform some action with bar
回话控制器通过包装一个 with
语句来设置和清理行为。回话控制器的行为通过两个魔术方法来定义:
__enter__(self)
定义当使用 with
语句的时候会话管理器应该初始块被创建的时候的行为。注意 __enter__
的返回值被 with
语句的目标或者 as
后的名字绑定。
__exit__(self, exception_type, exception_value, traceback)
定义当一个代码块被执行或者终止后会话管理器应该做什么。它可以被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作。如果代码块执行成功, exception_type
, exception_value
, 和 traceback
将会是 None
。否则的话你可以选择处理这个异常或者是直接交给用户处理。如果你想处理这个异常的话,确认 __exit__
在所有结束之后会返回 True
。如果你想让异常被会话管理器处理的话,那么就这样处理。
__enter
和 __exit__
对于明确有定义好的和日常行为的设置和清洁工作的类很有帮助。你也可以使用这些方法来创建一般的可以包装其他对象的会话管理器。以下是一个例子。
class Closer:
'''通过with语句和一个close方法来关闭一个对象的会话管理器'''
def __init__(self, obj):
self.obj = obj
def __enter__(self):
return self.obj # bound to target
def __exit__(self, exception_type, exception_val, trace):
try:
self.obj.close()
except AttributeError: # obj isn't closable
print 'Not closable.'
return True # exception handled successfully
以下是一个使用 Closer
的例子,使用一个FTP链接来证明(一个可关闭的套接字):
>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
... conn.dir()
...
>>> conn.dir()
>>> with Closer(int(5)) as i:
... i += 1
...
Not closable.
>>> i
6
你已经看到了我们的包装器如何静默的处理适当和不适当的使用行为。这是会话管理器和魔术方法的强大功能。
描述器是通过得到,设置,删除的时候被访问的类。当然也可以修改其他的对象。描述器并不是鼓励的,他们注定被一个所有者类所持有。当创建面向对象的数据库或者类,里面含有相互依赖的属性时,描述器将会非常有用。一种典型的使用方法是用不同的单位表示同一个数值,或者表示某个数据的附加属性(比如坐标系上某个点包含了这个点到远点的距离信息)。
为了构建一个描述器,一个类必须有至少 __get__
或者 __set__
其中一个,并且 __delete__
被实现。让我们看看这些魔术方法。
__get__(self, instance, owner)
定义当描述器的值被取得的时候的行为, instance
是拥有者对象的一个实例。 owner
是拥有者类本身。
__set__(self, instance, value)
定义当描述器值被改变时候的行为。 instance
是拥有者类的一个实例 value
是要设置的值。
__delete__(self, instance)
定义当描述器的值被删除的行为。instance
是拥有者对象的实例。
以下是一个描述器的实例:单位转换。
class Meter(object):
'''Descriptor for a meter.'''
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Foot(object):
'''Descriptor for a foot.'''
def __get__(self, instance, owner):
return instance.meter * 3.2808
def __set__(self, instance, value):
instance.meter = float(value) / 3.2808
class Distance(object):
'''Class to represent distance holding two descriptors for feet and
meters.'''
meter = Meter()
foot = Foot()
如果你接触过其他的 Pythoner,你可能已经听说过 Pickle 了, Pickle 是用来序列化 Python 数据结构的模块,在你需要暂时存储一个对象的时候(比如缓存),这个模块非常的有用,不过这同时也是隐患的诞生地。
序列化数据是一个非常重要的功能,所以他不仅仅拥有相关的模块( Pickle
, cPickle
),还有自己的协议以及魔术方法,不过首先,我们先讨论下关于序列化内建数据结构的方法。
让我们深入研究 Pickle,比如说你现在需要临时储存一个字典,你可以把它写入到一个文件里,并且要小心翼翼的确保格式正确,之后再用 exec() 或者处理文件输入来恢复数据,实际上这是很不安全的,如果你使用文本存储了一些重要的数据,任何方式的改变都可能会影响到你的程序,轻则程序崩溃,重则被恶意程序利用,所以,让我们用 Pickle 代替这种方式:
import pickle
data = {'foo': [1, 2, 3],
'bar': ('Hello', 'world!'),
'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # write the pickled data to the file jar
jar.close()
嗯,过了几个小时之后,我们需要用到它了,只需把它 unpickle 了就行了:
import pickle
pkl_file = open('data.pkl', 'rb') # connect to the pickled data
data = pickle.load(pkl_file) # load it into a variable
print data
pkl_file.close()
正如你期望的,数据原封不动的回来了!
同时要给你一句忠告: pickle 并不是很完美, Pickle 文件很容易被不小心或者故意损坏, Pickle 文件比纯文本文件要稍微安全一点,但是还是可以被利用运行恶意程序。 Pickle 不是跨版本兼容的(译注:最近刚好在 《Python Cookbook》上看到相关讨论,书中描述的 Pickle 是跨版本兼容的,此点待验证),所以尽量不要去分发 Pickle 过的文本,因为别人并不一定能够打开。不过在做缓存或者其他需要序列化数据的时候, Pickle 还是很有用处的。
Pickle 并不是只支持内建数据结果,任何遵循 Pickle 协议的类都可以,Pickle 协议为 Python 对象规定了4个可选方法来自定义 Pickle 行为(对于 C 扩展的 cPickle 模块会有一些不同,但是这并不在我们的讨论范围内):
__getinitargs__(self)
如果你希望在逆序列化的同时调用 __init__
,你可以定义 __getinitargs__
方法,这个方法应该返回一系列你想被 __init__
调用的参数,注意这个方法只对老样式的类起作用。
__getnewargs__(self)
对于新式的类,你可以定义任何在重建对象时候传递到 __new__
方法中的参数。这个方法也应该返回一系列的被 __new__
调用的参数。
__getstate__(self)
你可以自定义当对象被序列化时返回的状态,而不是使用 __dict
方法,当逆序列化对象的时候,返回的状态将会被 __setstate__
方法调用。
__setstate__(self, state)
在对象逆序列化的时候,如果 __setstate__
定义过的话,对象的状态将被传给它而不是传给 __dict__
。这个方法是和 __getstate__
配对的,当这两个方法都被定义的时候,你就可以完全控制整个序列化与逆序列化的过程了。
我们以 Slate 为例,这是一段记录一个值以及这个值是何时被写入的程序,但是,这个 Slate 有一点特殊的地方,当前值不会被保存。
import time
class Slate:
'''Class to store a string and a changelog, and forget its value when
pickled.'''
def __init__(self, value):
self.value = value
self.last_change = time.asctime()
self.history = {}
def change(self, new_value):
# Change the value. Commit last value to history
self.history[self.last_change] = self.value
self.value = new_value
self.last_change = time.asctime()
def print_changes(self):
print 'Changelog for Slate object:'
for k, v in self.history.items():
print '%s\t %s' % (k, v)
def __getstate__(self):
# Deliberately do not return self.value or self.last_change.
# We want to have a "blank slate" when we unpickle.
return self.history
def __setstate__(self, state):
# Make self.history = state and last_change and value undefined
self.history = state
self.value, self.last_change = None, None
这份指南的希望为所有人都能带来一些知识,即使你是 Python 大牛或者对于精通于面向对象开发。如果你是一个 Python 初学者,阅读这篇文章之后你已经获得了编写丰富,优雅,灵活的类的知识基础了。如果你是一个有一些经验的 Python 程序员,你可能会发现一些能让你写的代码更简洁的方法。如果你是一个 Python 大牛,可能会帮助你想起来一些你已经遗忘的知识,或者一些你还没听说过的新功能。不管你现在有多少经验,我希望这次对于 Python 特殊方法的旅程能够带给你一些帮助(用双关语真的很不错 XD)(译注: 这里的双关在于标题为 Magic Methods 这里是 神奇的旅程 ,不过由于中英语序的问题,直译略显头重脚轻,所以稍微变化了下意思,丢掉了双关的含义)。
一些魔术方法直接和内建函数相对,在这种情况下,调用他们的方法很简单,但是,如果是另外一种不是特别明显的调用方法,这个附录介绍了很多并不是很明显的魔术方法的调用形式。
魔术方法 | 调用方式 | 解释 |
---|---|---|
__new__(cls [,...]) | instance = MyClass(arg1, arg2) | __new__ 在创建实例的时候被调用 |
__init__(self [,...]) | instance = MyClass(arg1, arg2) | __init__ 在创建实例的时候被调用 |
__cmp__(self, other) | self == other, self > other, 等。 | 在比较的时候调用 |
__pos__(self) | +self | 一元加运算符 |
__neg__(self) | -self | 一元减运算符 |
__invert__(self) | ~self | 取反运算符 |
__index__(self) | x[self] | 对象被作为索引使用的时候 |
__nonzero__(self) | bool(self) | 对象的布尔值 |
__getattr__(self, name) | self.name # name 不存在 | 访问一个不存在的属性时 |
__setattr__(self, name, val) | self.name = val | 对一个属性赋值时 |
__delattr__(self, name) | del self.name | 删除一个属性时 |
__getattribute(self, name) | self.name | 访问任何属性时 |
__getitem__(self, key) | self[key] | 使用索引访问元素时 |
__setitem__(self, key, val) | self[key] = val | 对某个索引值赋值时 |
__delitem__(self, key) | del self[key] | 删除某个索引值时 |
__iter__(self) | for x in self | 迭代时 |
__contains__(self, value) | value in self, value not in self | 使用 in 操作测试关系时 |
__concat__(self, value) | self + other | 连接两个对象时 |
__call__(self [,...]) | self(args) | “调用”对象时 |
__enter__(self) | with self as x: | with 语句环境管理 |
__exit__(self, exc, val, trace) | with self as x: | with 语句环境管理 |
__getstate__(self) | pickle.dump(pkl_file, self) | 序列化 |
__setstate__(self) | data = pickle.load(pkl_file) | 序列化 |
希望这个表格对你对于什么时候应该使用什么方法这个问题有所帮助。
Hi Pythonistas,
本周真是疯狂的一周,本期 PyCoders 充满了各种各样的关于 Python 的新闻。我们尽可能的把在 PyCon 上发生的一切都告诉给你。
我们正在征集一些关于 PyCoders 的推荐语放在我们的首页上,如果你希望在我们的主页上出现你的名字,以及想对我们说一些什么的话,请在 Twitter @pycoders 。我们很期待听到你的声音。
让我们继续吧。
– Mahdi and Mike
这篇在 Django 网站上的文章表明了 Django 转向 Python 3 的计划与进程,Django 将在 1.5 版本中加入对 Python 3 的支持。
Django 1.4 RC2 按照开发计划表如期的发布了,这只是一个候选发布版本,建议你可以去试试用一下他,看能不能帮助找到一些 BUG 。
IronPython 新版添加了 Sqlite3 支持,以及修复了关于 zip 的BUG,以及其他更多的 BUG 修复。
一个基于 webapp2 构建的 Python Web 框架,使用 jinja2 作为模板语言。这个框架致力于构建可高重用与可测性的代码,我们已经迫不及待的想看看这个框架在现实中应用的情景了。
你曾经被数据库的模式,链接,事务性等概念弄得焦头烂额过么?或许你只是需要一个简单的 K-V 数据库存储。试一下 Ben Dowling 开发的 EasyDB 吧,轻松去除烦恼,帮助你集中精神解决程序问题。
我们都不希望接受这个事实,但是,真的是时候去使用 Python 3 了。python-modernize 帮助你转换代码到最新的 Python 版本,基于 2to3 构造,作者是 Armin Ronacher .
这个讨论从比较 C++ 与 Python 的读入速度开始,又讨论了更多的关于在什么时候 Python 比 C++ 快,以及应该如何优化 C++ 与 Python 程序。
在这个讨论中,你会见到 Python 中各种形式的 Goto 语句实现,同样建议可以读一下 这个版本的 Goto 以及在 Hacker News 上的相关讨论
这是一篇很不错的关于在浏览器中添加 Python 支持的文章,我们认为这件事情真的很不错,然后我们就再也听不到 Javascript 开发者的声音了。文章同时也推荐了一些实现这个想法的项目,比如 Python Webkit 。
这篇有趣的文章展示了一个Python之禅 (Zen of Python) 的实例。 值得去试试!
这是一篇关于处理在 python 应用中的 Unicode 错误的文章(内含幻灯片)。 Ned 阐述了一些 Unicode 已经存在的问题,同时还给予了一些解决你的文本问题的提示。
REVSYS 小组为即将到来的 Django 1.4 准备的很酷的作弊条。
(译者:视频 1.2GB ..正在拖,然后上传 Youku )
如果你对 Python 和在科学界使用 Python 感兴趣,你可以来看看这个由 Google 主办的在 2012 PyData 大会上的讨论视频。
Kivy 是一个迅速发展的,为快速开发应用准备的,支持许多创新界面例如多点触控的应用环境。Kivy 现在允许你把应用直接打包成 iOS 应用。Kivy 支持5个平台,所以你可以随意的部署你的应用而不用去更改任何一行代码。
这是 Taavi Burns’s slides 在 PyCon 上关于 python datetimes 的发言的幻灯片。处理时间问题很麻烦,所以这个幻灯值得你去看看。
(译者:等着俺慢慢拖吧... )
这是 Larry Hastings 做的一个关于 Cpython 的内部细节的演讲。演讲开始于一个简单的程序,并贯穿着 Cpython 循序渐进。这次演讲的目的主要通过向你展示你所需要了解并且付诸行动的事情,来让你成为 Python 核心开发者。
原文地址: http://nedbatchelder.com/text/unipain.html
译者: yudun1989
这是我在 Pycon2012 所做的演讲。你可以阅读本页的幻灯片和文字,或者直接在浏览器中打开 演示 ,或者来看现场视频。
同时,点击文章的图片将会进入所在幻灯片的的对应位置,图片中使用了 Symbola 字体,但是如果想要显示一些特殊符号的话,则需先将该字体下载下来。
大家好,我是Ned Batchelder.我已经有十年的Python编程经验,这意味着,很多很多的时候,我与其他程序员一样,犯过很多 Unicode 的编码错误。
如果你和其他 Python 程序员一样,那你肯定也碰到过如下情况:你编写了一段很漂亮的代码,事情看起来很顺。然后某一天一个很奇怪的“方言字符”不知道从哪冒了出来,你的程序中就开始大量涌现 UnicodeErrors 。
你好像知道这种问题应该怎样解决,于是呢,就去在错误出现的地方添加了 encode 和 decode ,但是 UnicodeError 又开始出现在其他的地方。于是你又在另外一个地方添加了 decode 抑或 encode 。在你玩过一段“编码打地鼠”游戏之后,问题似乎被解决。
之后某一天,另一种“方言字符”又在另外一个地方出现了。然后你不得不又去玩这种“打地鼠”直到问题解决掉。
现在你的程序终于可以运行。但是你既烦恼又不适,这个问题花费了太多时间,你知道这样解决“正确”,于是开始憎恨自己。你对 Unicode 的主要了解就是你很讨厌它。
你不想去了解怪异的字符集,你只想要写一个你认为不是很糟糕的程序。
你不必去玩打地鼠游戏. Unicode 会有些麻烦,但是它并不难。了解了相关知识并且加以练习,你也可以方便的优雅的解决相关问题。
接下来我会教给你 five Facts of lie,然后给你一些专业建议来解决 Unicode 问题。下面的内容将会包含 Unicode 基本知识,如何在 Python 2 和 Python 3 中来实现。他们有一定差异,但是你使用的基本策略都是一样的。
我们从 Unicode 基本知识开始。
事实之一:计算机中的一切均为 bytes(字节)。硬盘中的文件为一系列的 byte 组成,网络中传输的只有 byte。所有的信息,在你写的程序中进进出出的,均由 byte 组成。
孤立的 byte 是毫无意义的,所以我们来赋予它们含义。
为了表示各种文字,我们有大约 50 年的时间都在用 ASCII 码。每一个 byte 被赋予 95 种符号的一种,所以,当我给你发送 byte 值为 65 的时候,你知道我想表达一个大写的 A。
ISO Latin 1,或者 8859-1 对 ASCII 的 96 种字符进行了扩展。这也许是你用一个 byte 可以做的最多的事情了。因为 byte 中没有容量可以存储更多的符号了。
在 Windows 中增加了另外 27 种字符,这种叫做 CP1252 编码。
事实之二是,世界上的字符远远比256个要多。一个简单的byte不能够表达世界范围内的字符。在你玩”编码打地鼠”的时候,你多么的希望世界上所有的人都说英语,但是事实并不是这样,人们需要更多的符号来交流。
事实一和二共同造成了计算机设备结构与世界人类需求的一个冲突。
当时为了解决冲突尝试了多种途径。通过一个 byte 来与符号或者字符进行对应的编码,每一种解决途径都没有解决事实二中的实质问题。
当时有很多一个 byte 的编码,都没有能够解决问题。每一个都只能解决人类语言的一部分。但是他们不能解决所有的文字问题。
人们开始创造两个 byte 的字符集,但是仍然像碎片一样,只能够服务于不同地域的一部分人。
当时产生了不同的标准,讽刺的是,他们都不足以满足所有的符号的需求。
Unicode 就是为了解决之前的老的字符集问题。Unicode 分配整形,被成为代码点( UNICODE 的字符被成为代码点( CODE POINTS )用 U 后面加上 XXXX 来表现,其中, X 为16进制的字符)来表示字符。它有 110 万的代码点,其中有十一万被占用,所以它可以有很多很多的空间可供未来的增长使用。
Unicode 的目的是包含一切,它从 ASCII 开始,包含了数以千计的代码,包含这著名的—-雪人??,包含了世界上所有的书写系统,而且一直在被扩充。比如,最新的更新中,就有一大堆没用的词汇。
这里有六个的异国 Unicode 字符。 Unicode 代码点写成 4- , 5- ,或者 6 位的十六进制编码,同时有一个 U 的前缀。每一个字符都有一个用 ASCII 字符规定的名称。
所以说 Unicode 提供了所有我们需要的字符的空间。但是我们仍然需要处理事实一中所碰到的问题:计算机只能看懂 bytes 。我们需要一种用 bytes 来表示 Unicode 的方法这样才可以存储和传播他们。
Unicode 标准定义了多种方法来用 bytes 来表示成代码点,被成为 encoding 。
UTF-8 是最流行的一种对 Unicode 进行传播和存储的编码方式。它用不同的 bytes 来表示每一个代码点。ASCII 字符每个只需要用一个 byte ,与 ASCII 的编码是一样的。所以说 ASCII 是 UTF-8 的一个子集。
这里我们展现了几个怪异字符的 UTF8 的表示方法。 ASCII 字符 H 和 I 只用一个 byte 就可以表示。其他的根据代码点的不同使用了两个或者三个 bytes 。尽管有些并不常用,但是一些代码点使用到四个 bytes。
好,说完了这么多理论知识,我们来讲一讲 Python 2
在 Python2 中,有两种字符串数据类型。一种纯旧式的文字: “str” 对象,存储 bytes 。如果你使用一个 “u” 前缀,那么你会有一个 “unicode” 对象,存储的是 code points 。在一个 unicode 字符串中,你可以使用反斜杠 u(u) 来插入任何的 unicode 代码点。
你可以注意到 “string” 这个词是有问题的。不管是 “str” 还是 “unicode” 都是一种 “string” ,这会吸引叫它们都是 string ,但是为了直接还是将它们明确区分来。
如果想要在 unicode 和 bytes 间转换的话,两者都有一个方法。 Unicode 字符串会有一个 .encode
方法来产生 bytes , bytes 串会有一个 .decode
方法来产生 unicode 。每个方法中都有一个参数来表明你要操作的编码类型。
我们可以定义一个 Unicode 字符串叫做 my_unicode ,然后看这九个字符,我们使用 encode 方法来创建 my_unicode 的 bytes 串。会有 19 个 bytes ,想你所期待的那样。将 bytes 串来 decode 将会得到 utf-8 串。
不幸的是,如果指明的编码名称错误的话,那么 encode 和 decode 会产生错误。现在尝试 encode 我们的几个诡异的字符到 ascii ,会失败。因为 ascii 只能表示 0-127个 字符中的一个。然而我们的 Unicode 字符串早已经超出了范围。
抛出的异常为 UnicodeEncodeError ,它展现了你使用的编码方式, “codec” 即编码、解码器,展现了导致问题的字符的位置。
解码同样会知道出一些问题。现在我们去把一个 UTF-8 字符串解码成 ASCII ,会得到一个 UnicodeDecodeError ,原因一样, ASCII 只接受 127 内的值,我们的 UTF-8字 符串超出了范围。
尽管 UTF-8 不能解码成任何的 bytes 串,我们尝试来 decode 一些垃圾信息。同样也产生了 UnicodeDecodeError 错误。最终, UTF-8 的优势是,有效的 bytes 串,将会帮助我们来创建高鲁棒性的系统:如果数据无效的话,数据不会被接受。
当编码或者解码的时候,你可以指明如果 codec 不能够处理数据的时候,会发生什么情况。 encode 或者 decode 时候的第二个参数指明了规则。默认的值是 “strict” ,意味着像刚才一样,会抛出一个异常。
“replace” 值意味着,失败时将会返回一个标准的替代字符。当编码的时候,替代值是一个问号,所以任何不能被编码的值将会产生一个 ”?”。
一些其他的 handler 非常有用。”xmlcharrefreplace” 将会产生一个完全替代的 HTML/XML 字符,所以 u01B4 将会变成 “ƴ” (因为十六进制的 01B4 是十进制的 436 )。如果你需要将返回的值来输出到 html 文件中的话,将会非常有用。
注意要根据不同的错误原因使用不同的错误处理方式。”replace” 是一个处理不能被解析的数据的自卫型方式,会丢失数据。”xmlcharrefreplace” 会保护所有的原始数据,在 XML 转义符可以使用的时候来输出数据。
你也可以指定在解码时的错误处理方式。”ignore” 会直接将不能解码的 bytes 丢掉。”replace” 将会直接添加 Unicode U+FFFD ,给有问题的 bytes 来直接替换成”替换字符”。注意因为解码器不能解码这些数据。它并不知道到底有多少 Unicode 字符。解码我们的 UTF-8 字符串成为 ASCII 制造出了 16 个”替换字符”。每个 byte 不能被解析都被替换掉了。然而这些 bytes 只想要表示 6 个 Unicode 字符。
Python 2 已经试图在处理 unicode 和 byte 串的时候变得有用些。如果你系那个要把 Unicode 字符串串和 byte 字符串来组合起来的话, Python 2 将会自动的将 byte 串来解码成 unicode 字符串。从而产生一个新的 Unicode 字符串。
比如,我们想要连接 Unicode 串 “hello” 和一个 byte 字符串 “world”。结果是一个 Unicode 的 “hello world”。在我们看来。Python 2 将 “world” 使用 ASCII codec 进行了解码。这次在解码中使用的字符集的值与 sys.getdefaultencoding() 的值相等。
这里这个系统中的字符集为 ASCII, 因为这是唯一合理的一种猜测: ASCII 被如此广泛接受,它是这么多编码的子集,不太会是错误的。
当然,这些隐藏的编码转换不能免疫于解码错误。如果你想要连接 一个 byte 字符串和一个 unicode 字符串,并且 byte 字符串不能被解码成 ASCII 的话,将会抛出一个 UnicodeDecodeError。
这就是那些可恶的 UnicodeError 的圆圈。你的代码中包含了 unicode 和 byte 字符串,只要数据全部是 ASCII 的话,所有的转换都是正确的,一旦一个非 ASCII 字符偷偷进入你的程序,那么默认的解码将会失败,从而造成 UnicodeDecodeError 的错误。
Python 2 的哲学就是 Unicode 字符串和 byte 字符串是可以混合的,它试图去通过自动转换来减轻你的负担。就像在 int 和 float 之间的转换一样, int 到 float 的转换不会失败,byte 字符串到 unicode 字符串会失败。
Python 2 悄悄掩盖了 byte 到 unicode 的转换,让程序在处理 ASCII 的时候更加简单。你付出的代价就是在处理非 ASCII 的时候将会失败。
有很多方法来合并两种字符串(一个 byte 字符串和一个 unicode 字符串),所有的方法都会先将 byte 转换为 unicode,所以处理它们的时候你必须多加小心。
首先我们使用 ASCII 格式字符串,和 unicode 来结合。那么最终的输出将会变成 unicode。返回一个 unicode 字符串。
之后我们将两个交换一下:一个 unicode 格式的字符串和一个 byte 串再一次合并,生成了一个 unicode 字符串,因为 byte 串可以被解码成 ASCII。
简单的去打印出一个 unicode 字符串将会调用隐式的编码:输出总会是 bytes, 所以在 unicode 被打印之前必须被编码成 byte 串。
接下来的事情非常不可理解:我们让一个 byte 串编码成 UTF-8,却得到一个错误说不能被解码成 ASCII!这里的问题是 byte 串不能被编码,要记住编码是你将 Unicode 变成了 byte 串。所以想要执行你的操作的话,Python2 需要的是一个 unicode 字符串,隐式的将你的字符串解码成 ASCII。
最后,我们将 ASCII 字符串编码成 UTF-8。现在我们进行相同的隐式编码操作,因为字符串为 ASCII,编码成功。并且将它编码成了 UTF-8 ,打印出了原始的 byte 字符串,因为 ASCII 是 UTF-8 的一个子集。
最重要的事实之三:byte 和 unicode 都非常重要,你必须将两个都处理好。你不能假设所有的字符串都是 byte,或者所有的字符串都是 unicode,你必须适当地运用它们,必要时转换它们。
我们看到了 Python 2 版本中有关 Unicode 之痛。现在我们看一下 Python 3,在 Python 2 到 Python 3 中最重要的变化就是它们对 Unicode 的处理。
跟 Python 2 类似,Python 3 也有两种类型,一个是 Unicode,一个是 byte 码。但是它们有不同的命名。
现在你从普通文本转换成 “str” 类型后存储的是一个 unicode, “bytes” 类型存储的是 byte 串。你也可以通过一个 b 前缀来制造 byte 串。
所以在 Python 2 中的 “str” 现在叫做 “bytes”,而 Python 2 中的 “unicode” 现在叫做 “str”。这比起Python 2中更容易理解,因为 Unicode 是你总想要存储的内容。而 bytes 字符串只有你在想要处理 byte 的时候得到。
Python 3 中对 Unicode 支持的最大变化就是没有对 byte 字符串的自动解码。如果你想要用一个 byte 字符串和一个 unicode 相连接的话,你会得到一个错误,不管包含的内容是什么。
所有这些在 Python 2 中都有隐式的处理,而在 Python 3 中你将会得到一个错误。
另外如果一个 Unicode 字符串和 byte 字符串中包含的是相同的 ASCII 码,Python 2 中将认为两个是相等的,而在 Python 3 中不会。这样做的结果是 Unicode 中的键不能找到 byte 字符串中的值,反之亦然,然而在 Python 2 中是可行的。
这样彻底了改变了 Python 3 中的 Unicode 痛楚之源。在 Python 2 中,只要你使用 ASCII 数据,那么混合 Unicode 和 byte 将会成功,而在 Python 3 会直接忽略数据而失败。
这样的话,在 Python 2 中所遇到的,你认为你的程序是正确的但是最后发现由于一些特殊字符而失败的错误就会避免。
Python 3 中,你的程序马上就会产生错误,所以即使你处理的是 ASCII 码,那你也必须处理 bytes 和 Unicode 之间的关系。
Python 3 中对于 bytes 和 unicode 的处理非常严格,你被迫去处理这些事情。这曾经引起争议。
这样处理的原因之一是对读取文件的变化,Python 对于读取文件有两种方式,一种是二进制,一种是文本。在 Python 2 中,它只会影响到行尾符号,甚至在 Unix 系统上的时候,基本没有区别。
在 Python 3中。这两种模式将会返回不同的结果。当你用文本模式打开一个文件时不管你是用的 “r” 模式或者默认的模式,读取成功的文件将会自动转码成 unicode ,你会得到 str 对象。
如果你用二进制模式打开一个文件,在参数中输入 “rb” ,那么从文件中读取的数据会是 bytes,对它们没有任何处理。
隐式的对 bytes 到 unicode 的处理使用的是 locale.getpreferedencoding() ,然而它有可能输出你不想要的结果。比如,当你读取 hi_utf8.txt 时,他被解码成语言偏好中所设置的编码方式,如果我们这些例子在 Windows 中创建的话,那么就是 “cp1252” 。像 ISO 8859-1, CP-1252 这些可以得到任意的 byte 值,所以不会抛出 UnicodeDecodeError ,当然也意味着它们会直接将数据解码成 CP-1252,制造出我们并不需要的垃圾信息。
为了文件读取正确的话,你应该指明想要的编码。open 函数现在已经可以通过参数来指明编码。
好,那么如何来减少这些痛苦?好消息是减轻痛苦的规则非常简单,在Python 2和 Python 3中都比较适用。
正如我们在事实一中所看到的,在你的程序中进进出出的只有 bytes, 但是在你的程序中你不必处理所有的 bytes。最好的策略是将输入的 bytes 马上解码成 unicode。你在程序中均使用 unicode ,当在进行输出的时候,尽早将之编码成 bytes 。
制造一个 Unicode 三明治, bytes 在外, Unicode 在内。
要记着,有时候一些库将会帮助你完成类似的事情。一些库可能让你输入 unicode,输出 unicode,它会帮你完成转换的功能。比如 Django 在它的 json 模块中提供 Unicode。
第二条规则是:你需要知道你现在处理的是哪种类型的数据,在你的程序中任何一个位置,你需要知道你处理的是 byte 串还是一个 unicode 串。它不能是一种猜测,而应该被设计好。
另外,如果你有一个 byte 串的话,如果你想对它进行处理。那么你应该知道它是怎样的编码。
在对你的代码进行 debug 的时候,不能仅仅将之打印出来来看它的类型。你应该查看它的 type ,或者查看它 repr 之后的值来查看你的数据到底是什么类型。
我曾经说过,你应该了解你的 byte 字符串的编码类型。好这里要我讲事实四:你不能通过检查它来判断这个字符串编码的类型。你应该通过其他途径来了解。比如很多协议中将会指明编码类型。这里我们给出 HTTP, HTML, XML, Python 源文件中的例子。你也可以通过预先的指定来了解编码。比如数据源码中可能会指明编码。
有一些方式可以来猜测一些 bytes 的编码类型。但是仅仅是猜测。能够确定的唯一方式是通过其他方式。
这里是给出一些怪异的字符的编码猜测。我们用UTF-8 便民店的一些字符,被不同的解码方式解码之后的输出。你可以看见。有时候用不正确的解码方式解码可能会输出正确,但是会输出错误的字符。你的程序不能告诉你这些解析错误了。只有当用户察觉到的时候你才会发现错误。
这是事实四的一个好例子:同样的 bytes 流通过不同的解码器是可以解码的。而 bytes 本身不能指明它自己用的哪种编码方式。
顺便说一下,这些垃圾信息的显示只遵循一个规则,那就是乱码。
不幸的是,bytes 流会根据自己的来源不同而进行不同的编码,有时候我们指明的编码方式可能是错误的。比如你有可能将一个 HTML 从网上抓取下来,HTTP 头中指明编码方式是 8859-1, 然而实际上的编码确是 UTF-8。
在一些情况下编码方式的不匹配可能会产生乱码,而有些时候,则会产生 UnicodeError。
不用说。你应该测试你的 Unicode 支持。为了这样。你首先应该在你的代码中首先去先把 Unicode 来提取出。如果你只会说英语,这可能会有些困难。因为有些 Unicode 数据会比较难以读。幸运的是,大部分时候一些复杂结构的 Unicode 字符串还是比较具有可读性的。
这里是一个例子。ASCII 文本中可以读的文本,和倒置的文本。这些文本的一些有时候是一些青年人会粘贴到社交网络中。
根据你的程序,你有可能在 Unicode 的道路中越挖越深。还有很多很多的细节我这里没有解释清楚。可以被涉及到。我们称之为事实五。因为你不必去对此了解太详细。
复习一下,我们有五个不可忽视的事实:
这是你在编程中保持 Unicode 清洁的三个建议:
如果你遵循以上建议的话,你将会写出对 Unicode 支持很好的代码。不管 Unicode 中有多么不规整的编码你的程序也不会挂掉。
一些其他你可能需要的资源
Joel Spolsky 编写的 The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) 概括了 Unicode 的工作方式和原因。虽然没有 Python 的内容,但是比我解释的详细多了!
如果你需要处理一些语义上的 Unicode 字符问题。那么 unicodedata module 也许会对你有些帮助。
如果你希望找一些 Unicode 来测试的话,网上各种的 编码文本计算器 会对你很有帮助。
原文: http://taaviburns.ca/what_you_need_to_know_about_datetimes/what_you_need_to_know_about_datetimes.pdf
译者: TheLoverZ
译注:这篇文章实在是翻译的相当吃力,因为文中的东西平时几乎不怎么接触(我都是直接用UTC),所以如果有错误请不吝指正,谢谢!
time
, calendar
, 显式和隐式的时间(Naive vs Aware datetimes), pytz
(第10页图)
def _days_before_year(year):
y = year - 1
return y*365 + y//4 - y//100 + y//400
(第15页图)
libc
接口thread
和 os.fork
struct_time
os.environ["TZ"]
以后才有时区支持struct_time
是隐式的,但有一个 is_dst
的标志变量(flag)。time.time()
来得到当前的 POSIX 时间戳。time.gmtime(t)
来得到一个 struct_time
(t == None)
则是当前的时间,或者提供一个 POSIX 时间戳。datetimes
没什么关联,除了⋯calendar.timegm(tuple)
来把一个 UTC 的 struct_time
转化为 POSIX 时间戳。time
模块中但是被拒绝了。dates
, times
, intervals
和 timezones
接口。threading
和 subprocess
。pytz
pytz
来对应时区的改变(包括 DST 的改变)>>> datetime(2011, 11, 6, 5, 30, tzinfo=pytz.UTC)
datetime.datetime(2011, 11, 6, 5, 30, tzinfo=<UTC>)
pytz.timezone().localize()
来得到给定时区的一个显式的时间:>>> helsinki = pytz.timezone('Europe/Helsinki')
>>> helsinki.localize(datetime(2011, 11, 6, 5, 30))
datetime.datetime(2011, 11, 6, 5, 30, tzinfo=<DstTzInfo 'Europe/Helsinki' EET+2:00:00 STD>)
.localize()
设置一下:>>> toronto = pytz.timezone('America/Toronto')
>>> toronto.localize(
... # Is this EDT or EST?
... datetime(2011, 11, 6, 1, 30),
... is_dst=None)
pytz.tzinfo.AmbiguousTimeError: 2011-11-06 01:30:00
>>> toronto = pytz.timezone('America/Toronto')
>>> datetime.now(toronto)
datetime.datetime(2012, 3, 5, 16, 40, 12, 967922, tzinfo=<DstTzInfo 'America/Toronto' EST-1 day, 19:00:00 STD>)
>>> _.date()
datetime.date(2012, 3, 5)
>>> toronto = pytz.timezone('America/Toronto')
>>> datetime(2011, 6, 1, 0, 0, # summer = DST!
... tzinfo=toronto)
datetime.datetime(2011, 6, 1, 0, 0, tzinfo=<DstTzInfo 'America/Toronto' EST-1 day, 19:00:00 STD>)
>>> _.isoformat()
'2011-06-01T00:00:00-05:00'
>>> datetime(2011, 11, 6, 5, 30,
... tzinfo=helsinki)
datetime.datetime(2011, 11, 6, 5, 30, tzinfo=<DstTzInfo 'Europe/Helsinki' HMT+1:40:00 STD>)
.replace()
向一个隐式的时间添加时区:>>> datetime(2011, 11, 6, 5, 30).replace(tzinfo=helsinki)
datetime.datetime(2011, 11, 6, 5, 30, tzinfo=<DstTzInfo 'Europe/Helsinki' HMT+1:40:00 STD>)
(32页图)
CONVERT_TZ(dt,from_tz,to_tz)
,参见 这里 。AT TIME ZONE
。unixepoch
, localtime
, utc
。getUTC*
new Date(posixTimestamp * 1000);
var posixTimestamp = Date.now()/1000;
(new Date(posixTimestamp * 1000)).getTime() / 1000 == posixTimestamp
timegm()
的实现os.environ['TZ'] = 'right/UTC'
time.tzset()
mktime
这时候和 gmtime
正好相反。_EPOCH_DATETIME = datetime(1970, 1, 1)
_SECOND = timedelta(seconds=1)
def timegm(tuple):
return (datetime(*tuple[:6]) - _EPOCH_DATETIME) // _SECOND
Hi Pythonistas,
本期 PyCoders 依然由 SWIX 特别赞助。
嘛,PyCon 现在正在举行中,如果你在会场的话,告诉某两个人 PyCoders 的话,我们说不定会为你打开那个跳舞机器人哟,如果你真的想这么做,就来加入我们吧~本周依然有很多的好东西,比如作为 Python 中的 Rake 的项目 Shovel,以及更多的新鲜玩意儿~
同时不要忘记把 我们 推荐给你的朋友~
P.S. 如果你真的很想知道那个跳舞机器人是啥,偷偷地告诉你:就是那个今年 PyCon 开场的会跳舞的机器人哟。(译者注:本届 PyCon 大会的开场视频可以在 这里 看到)
– Mahdi and Mike
Yay~
Mezzanine 是一个 Django 为框架的 CMS 开源系统,最近发布了 1.0 版本,Mezzanine 是一个非常成熟的 CMS 系统,你可以看到有 这么多 网站都选择了 Mezzanine ,并且你可以整合 Cartridge 使 Mezzanine 变成一个电子商务平台。 Mezzanine 官方网站
本周是 PyCon 周,来这里看看你有没有错过什么 PyCon 上的好东西吧 (如果你都看过了,无视这个好了……)
PyCon 实时讨论是基于 Disqus 搭建的一个 web 应用,你可以在这里问问题,向大会反馈意见以及找到志同道合的朋友。找到一个喜欢的话题进去讨论吧,或者建立一个你感兴趣的话题。
我相信你们应该都在 Stack Overflow 上看过这篇文章了,SO (Stack Overflow,下同) 上曾经也对 Python 的 修饰器 , yield 进行过很好的解释。我们认为这篇文章对于 Python 中元类的解释很有价值,同时在 Reddit 上的讨论也很不错,去看看吧。
底层的 HTTP 处理工具包,支持 HTTP 1.1 协议,提供和 Tornado 类似的 API 调用,但是实际上是用 twisted 写的,将两个项目融合在了一起,他们说性能和易用性都有极大的提升。
一个 Siri 服务器 (不是代理) 的纯 python 实现。不幸的是,只有 iPhone 4S 可以跟苹果服务器进行通讯。这个项目尝试用 Google 语音识别 API 来重造这个 Siri 服务器。
安装这个插件之后,按下 command + shift + r
键会自动切换到 Google Chrome 中并且自动刷新当前页,如果你在按之前没有保存文件,这个插件会替你保存然后再刷新。
一个关于 Django 的新密码的哈希能力的简要概述,和使用开源或私人文档 (源代码) 的关于使用在 1.4 上的默认哈希算法差异的一些有趣的评论。
Django 设置是很困难的一件事,每个人都似乎有不同的方式做到这一点,这些方法也都有自己的优缺点。这篇文章中,作者列出了3种不同的方式为你的不同的环境做环境特定设置,他还列出了大量的能让你作出判断的材料。这很值得一试,即使你已经做了设置,它总是能够让你在解决相同问题上发现不同观点。
这是一个能够令 Python 开发者和 Python 新手扩充知识的很棒的书单。这个书单相当完整,我强烈推荐你看看这个书单。这本书单唯一的遗漏是一本侧重于最佳实践,优化,管理代码和一些不同的编程范式教学的叫做 Python 高级编程 的书。
这段代码漂亮整洁的展示了 pip 并行化下载的可能性。其中任务调度是基于 gevent 的。这个想法令我们眼前一亮,我们希望 pip 官方能够实现这一特性,这样就可以在有着繁多继承关系的时候更快的部署系统。
Doug Hellmann 在这篇文章中介绍的各种方法和算法可以用来解决数据库内搜索相同发音单词的要求。文章中包含一些示例代码,所以,通过这篇文章,你能够搞定问题。
在这份 newsletter 中,你也许不会发现太多幽默的文章,虽然这篇文章在几年前就已经发表了,但是真的很有意思,所以我们认为你值得看一看。
Aaron Shwartz 是 Reddit 的创始人和 web.py 的作者。他有一些关于如何实现推广 Python 3 的有趣想法。他的主要观点是 Python 3 的过渡方式应该同 Python 2.x 系列保持一致,令 Python 3 支持文件所以你可以只用 from __future__ import python3
。我们不认为这些想法能够改变什么,但是绝对值得你一读。
目录译者: wangtz & Yueh & Zeray Rice
感谢 bcxx 的校对工作。
原文地址: http://www.levigross.com/post/18880148948/a-review-of-djangos-new-password-authentication
译者: Dukedyb
Django 刚刚发布的 1.4 版本中包含了一些安全方面的重要提升。其中一个是使用 PBKDF2 密码加密算法代替了 SHA1 。另外一个特性是你可以添加自己的密码加密方法。
Django 会使用你提供的第一个密码加密方法(在你的 setting.py
文件里要至少有一个方法)
PASSWORD_HASHERS = (
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.BCryptPasswordHasher',
'django.contrib.auth.hashers.SHA1PasswordHasher', # Insecure Hashes
'django.contrib.auth.hashers.MD5PasswordHasher', # Insecure Hashes
'django.contrib.auth.hashers.CryptPasswordHasher', # Insecure Hashes
)
我们先从一睹如何创建自己的密码加密方法开始。
首先,你必须从 BasePasswordHarsher
这里继承,它就在 django.contrib.auth.hashers
里。这个 Class 看起来就像下面这样:
class BasePasswordHasher(object):
"""
Abstract base class for password hashers
When creating your own hasher, you need to override algorithm,
verify(), encode() and safe_summary().
PasswordHasher objects are immutable.
"""
algorithm = None
library = None
def _load_library(self):
if self.library is not None:
if isinstance(self.library, (tuple, list)):
name, mod_path = self.library
else:
name = mod_path = self.library
try:
module = importlib.import_module(mod_path)
except ImportError:
raise ValueError("Couldn't load %s password algorithm "
"library" % name)
return module
raise ValueError("Hasher '%s' doesn't specify a library attribute" %
self.__class__)
def salt(self):
"""
Generates a cryptographically secure nonce salt in ascii
"""
return get_random_string()
def verify(self, password, encoded):
"""
Checks if the given password is correct
"""
raise NotImplementedError()
def encode(self, password, salt):
"""
Creates an encoded database value
The result is normally formatted as "algorithm$salt$hash" and
must be fewer than 128 characters.
"""
raise NotImplementedError()
def safe_summary(self, encoded):
"""
Returns a summary of safe values
The result is a dictionary and will be used where the password field
must be displayed to construct a safe representation of the password.
"""
raise NotImplementedError()
正如你看到那样,你必须创建下面的方法 verify()
, encode()
以及 safe_summary()
。
Django 是使用 PBKDF 2算法与10,000次的迭代使得它不那么容易被暴力破解法轻易攻破。密码用下面的格式储存:
algorithm$number of iterations$salt$password hash”
在这个例子中,Django 的 PBKDF2 使用 Base64 来加密。
class PBKDF2PasswordHasher(BasePasswordHasher):
"""
Secure password hashing using the PBKDF2 algorithm (recommended)
Configured to use PBKDF2 + HMAC + SHA256 with 10000 iterations.
The result is a 64 byte binary string. Iterations may be changed
safely but you must rename the algorithm if you change SHA256.
"""
algorithm = "pbkdf2_sha256"
iterations = 10000
digest = hashlib.sha256
def encode(self, password, salt, iterations=None):
assert password
assert salt and '$' not in salt
if not iterations:
iterations = self.iterations
hash = pbkdf2(password, salt, iterations, digest=self.digest)
hash = hash.encode('base64').strip()
return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
def verify(self, password, encoded):
algorithm, iterations, salt, hash = encoded.split('$', 3)
assert algorithm == self.algorithm
encoded_2 = self.encode(password, salt, int(iterations))
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
algorithm, iterations, salt, hash = encoded.split('$', 3)
assert algorithm == self.algorithm
return SortedDict([
(_('algorithm'), algorithm),
(_('iterations'), iterations),
(_('salt'), mask_hash(salt)),
(_('hash'), mask_hash(hash)),
])
你也许担心你需要安装一些东西,但 Django 已经包含了一个算法 utils.crypto
。
def pbkdf2(password, salt, iterations, dklen=0, digest=None):
"""
Implements PBKDF2 as defined in RFC 2898, section 5.2
HMAC+SHA256 is used as the default pseudo random function.
Right now 10,000 iterations is the recommended default which takes
100ms on a 2.2Ghz Core 2 Duo. This is probably the bare minimum
for security given 1000 iterations was recommended in 2001. This
code is very well optimized for CPython and is only four times
slower than openssl's implementation.
"""
assert iterations > 0
if not digest:
digest = hashlib.sha256
hlen = digest().digest_size
if not dklen:
dklen = hlen
if dklen > (2 ** 32 - 1) * hlen:
raise OverflowError('dklen too big')
l = -(-dklen // hlen)
r = dklen - (l - 1) * hlen
hex_format_string = "%%0%ix" % (hlen * 2)
def F(i):
def U():
u = salt + struct.pack('>I', i)
for j in xrange(int(iterations)):
u = _fast_hmac(password, u, digest).digest()
yield _bin_to_long(u)
return _long_to_bin(reduce(operator.xor, U()), hex_format_string)
T = [F(x) for x in range(1, l + 1)]
return ''.join(T[:-1]) + T[-1][:r]
同时也提供了 bcrypt
,但是你需要先安装 py-bcryptor
。
我发现了一些很有趣的东西: Django doc 中的引文与 Django Source Code 所写的内容是相互矛盾的..
Django Doc
本来Django使用PBKDF2算法和SHA256哈希算法,这是NIST推荐的一种把密码加密延长的方法。这对于大多数的用户足够了: 它非常安全,需要超级多的计算时间才能破解。
Django Source Code
目前,2.2Ghz Core 2 Duo计算10,000次的迭代仅需要100毫秒,因此默认推荐使用此方法。在2001的推荐中这大概是耗时最少的算法了.这些代码为CPytoon做了很好的优化,仅仅比openssl慢4倍.
呃。。非常安全 VS 非常省时,我想知道我应该选哪个。
原文地址: http://www.sparklewise.com/django-settings-for-production-and-development-best-practices/
译者: TheLoverZ
当你刚开始使用 Django 的时候,你会很诧异竟然没有一个标准的配置。然而,作为一个初学者,确实有一些简单好用的办法。
问题在哪儿? Django 把所有的项目设置都存在一个叫做 settings.py 的文件里。在你不需要为了适应某些特定环境而手动改变配置的时候(比如说生产开发环境),Django 的做法看起来还算不错。
开发使用的配置文件应该和生产的不一样。为什么?
这里我列出了在不同环境之间可能会有变化的东西:
DEBUG
或者 TEMPLATE_DEBUG
)我们接下来要用三种方法来配置你的配置文件。
这个方法有一个 settings.py
文件,一些公共设置和一个特殊环境使用的 local_settings
文件。
我们来看看:
### settings.py file
### settings that are not environment dependent
try:
from local_settings import *
except ImportError:
pass
### local_settings.py
### environment-specific settings
### example with a development environment
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django',
'USER': 'django',
'PASSWORD': '1234',
'HOST': '',
'PORT': '',
}
local_settings.py
应该在源码管理的范围以外,你需要一个独立的文件来应对生产和开发环境。local_settings
的公共设置。但是这种方法在大多数简单的情况下都有用。在 Ches Martin 这儿有个例子。这个方法依赖于一个指向正确的 Python 模块的环境变量。它有直接明了的优点,所以你可以直接命名你的特殊设置文件(在这个例子中 production.py
是生产环境的配置文件):
[myapp]$ls settings
__init__.py
defaults.py
dev.py
staging.py
production.py
我们引用设置文件的时候会发生什么?设置文件在这个情况下是一个包(Python 的包是一个内含 __init__.py
的文件夹),所以当编译器加载包的时候会执行 __init__.py
文件。
默认情况下,我们来让它在开发环境下可以工作。
### __init.py__
from dev import *
### default.py__
## sensible choices for default settings
### dev.py
from defaults import *
DEBUG = True
### other development-specific stuff
### production.py
from defaults import *
DEBUG = False
### other production-specific stuff
怎么在生产环境和部署环境(Staging Environment)使用这个配置呢?
技巧就在于设置模块可以被覆盖。所以在启动服务器之前我们需要覆盖环境变量。比如说如果我们用 Apache 作为 Django 服务器的话,把你的 Apache 配置修改成:
SetEnv DJANGO_SETTINGS_MODULE myapp.settings.production
### settings.py
import os
ENVIRONMENT_SETTING_FILE = '/etc/django.myproject.settings'
### this will load all environment file settings in here
execfile(ENVIRONMENT_SETTING_FILE)
### all common settings
### ...
显而易见的,这种办法的一个问题就在于我们不能修改公共配置文件 settings.py
里面定义好的变量。
但是另一个方面,这个方法简化了配置文件的管理。分别创造一个开发所需的,一个部署所需的,以及生产所需的,做好保密措施,然后你就没事儿了。
还有很多办法来管理你的配置,所以你可以多折腾折腾。看看下面列出的资料会很有好处。如果你有高见,来留个言吧!
原文地址: http://www.informit.com/articles/article.aspx?p=1849069
译者: Zeray Rice & Yueh
Wesley J. Chun, 《Python 核心编程》的作者 ( Informit | 豆瓣 ),为大家推荐了一系列的他觉得很不错的 Pyhton 书目,不管你是初学者还是 Python 开发大牛,都应该来看一下他推荐的书。
在我十五年的 Python 生涯中,有无数人问过我类似“你能向我推荐一些 Python 相关的好书么?”这样的问题,不过作为一个写书的人,当然会说“这还用问,当然是我的书啊!”。
关键问题是,实际上这个问题是没有真正的 正确 答案的,一本好书,是要根据读者的学习方法与水平来判断的,也就是说,对于不同的人,“好书”的标准也是不同的。更深一层说,我的主要目标是为了社区能够更好地发展。如果我的书能够对一些人有帮助,我会很高兴,但是如果对你没有很大的帮助,我很乐意给你推荐一本更适合你的书。
在我们开始之前,我还要说说另外一个经常被问到的问题,“选择 Python 2 还是 Python 3?”。尽管 Python 3 已经发布三年多了,并且已经有许多库都对它提供了支持,而且今年夏天就要发布 Python 3.3 了,但是世界上的大多数 Python 程序依然选择 Python 2,我建议你,如果你不需要转换软件到 Python 3 或者刚刚准备学习 Python ,你应该选择 3.x,不过如果你有 2.x 的代码的话,你还是继续用 2.x 吧,因为他们只有表面上的不同而已(虽然不是向后兼容的)。只学习一个你就能轻松的在两个版本之间转换了。现在我们来读一下这篇文章吧。
在这篇文章里,我将会给三个不同阶段的人提供三个不同的阅读列表,第一个列表是给有其他语言的编程经验的人的:
作者:Mark Pilgrim 出版社:Apress 出版年:2004,2009
《深入 Python》是 Python 中最流行的书之一,第一版在 2004 年出版,2009 年为 Python 3 出了第二版。对于程序员来说,如果你想通过代码来学习 Python ,那么这本书是最好的选择,更因为作者就是我的同事!不过如果你想在开始写程序之前多了解一些其他的,你还有其他的选择。
作者:Wesley Chun 出版社:Prentice Hall 出版年:2006
《Python 核心编程》差不多是一本和《深入 Python》站在一个完全相反的角度的书。比起“快速深入”,我更觉得这本书是对 Python 语言的深度剖析,这本书的主要目标是尽可能快的教会你使用 Python ,同时又达到尽可能的全面。这本书中包含了大量的代码样例,这样你可以在阅读中边试边学,所以在读这本书之前你并不需要太多的准备,更好的是,在每章的结束部分都会有练习去助你理解所学习的内容。此外,还有一些很有用的表格作为参考资料提供给读者。2009 年的时候,为了让这本书能够不落伍,我在里面添加了一些关于 Python 2.6 (当然也有部分关于 2.7 的)以及 3.x 的内容,新内容你会在第五版或新的印刷版中看到,以及,所有读者都可以在 corepython.com 下载到这部分新加的内容和勘误表。
作者:Vern Ceder 出版社:Manning 出版年:2010
( Safari Book Online | 豆瓣 )
《Python 快速入门》是一本和《深入 Python》很像的书,但是在某些细节上的东西要比《深入 Python》强,就像是《深入 Python》这本书的弟弟一样,就在一两年前刚刚更新了 Python 3 的内容。
作者:Magnus Lie Hetland 出版社:Apress 出版年:2008
《Python 基础教程》这本书在细节问题上依然要比同是 Apress 出版的《深入 Python》强,这本书的可读性很强,然而,像《Python 快速入门》一样,他并没有像《Python 核心编程》那样的深入,它就像一杯泡的恰好的茶一样。这本书同时也提供一个配套学习的网站。
我们下面要介绍的这一份清单重点将放在那些没有编程经验的人身上(包括孩子们),正如标题所说的那样。
下面这四本书经常被用来教小朋友编程,因为他们更喜欢用 Python 来写游戏,难道还会有比这个更好玩的事情么?
( Safari Book Online | 豆瓣 )
作者:Warren Sande and Carter Sande 出版社:Manning 出版年:2009
这本畅销的儿童书是由一位工程师和他的儿子共同完成的,所以有很多儿童视角的提示框,有爱的提示框和儿童视角使得这本书很适合全家一起看~
作者:Al Sweigart 出版年:2010
《用 Python 编写你自己的电脑游戏(第二版)》是又一本很适合初学者的书,通过制作游戏来学习编程,这本书从读者没有任何经验的角度出发,详细介绍了关于编程与编写游戏中的各种概念。
作者:Allen B. Downey, Jeff Elkner and Chris Meyers 出版社:Green Tea Press 出版年:2002
这是一系列书中的一本,最初只有 C++ 版本的,后来又出现了 JAVA 版,然后就有了 Python 版。这一系列书适合年龄稍大些的孩子阅读,比如高中生或者非科学/工程系的大学生们阅读。你不仅能从这本书中学习到如何写程序,同时也能学习到一些关于计算机科学的概念。
注:因为本书没有对应的中文译名,所以未翻译书名。
作者:Michael Dawson 出版社:Course Technology 出版年:2010
( Safari Book Online | 豆瓣 )
在这份列表中的最后一本书是一本刚刚面世的新书。这本书通过编写游戏来教授编程的方法,这本书现在很受欢迎,因为这本书的思路非常独特,现在这本书的最新修订版已经加入了关于 Python 3 的相关内容。
在最后一个列表中我将会介绍一些 Python 参考书,这些书基本只需要在你的书架躺着,当有需要的时候去查阅就行了,然后再放回去。
作者:David Beazley 出版社:Addison-Wesley 出版年:2009
这个列表中的第一本书就是经典的“PER”(Python Essential Reference),这本书是第一本 Python 参考书(至少在他第一版出版的时候是),回想 Python 版本还是 1.5 的时候,当时 Python 程序员只有标准库手册能够参考,打印出来几乎有一英寸后(还是双面打印),开发者们迫切希望能够有一本带回家的参考书。《Python 参考手册》的出现极大的缓解了这样的需求,因为这是一本轻便的,可移动的 Python 参考书。这本书的维护者是一名爵士乐音乐家和疯狂的(计算机)科学家,David Beazley.
作者:Alex Martelli 出版社:O’Reilly 出版年:2006
( Safari Book Online | 豆瓣 )
数年后,第二本参考书出版了,这本书是作为 O’Reilly 的技术手册系列出版的,作者是大牛 Alex Martelli,同时他也是我的同事。这本书以及 PER 均是由 Python 界的知名人物编写的,并且这两本书都很像,我建议你先去翻翻这两本书然后选一本风格你更喜欢的。
作者:Alex Martelli, Anna Ravenscroft, David Ascher 出版社:O’Reilly 出版年:2005
( Safari Book Online | 豆瓣 )
最后两本书并不是像前两本那样很有参考书的样子,但他们依然是一本很不错的参考书,因为他们包含了很多不仅仅是参考书的内容。这本书主要由 Python Cookbook 网站构成,里面有很多去解决某样问题的 Python 代码,你可以在这里找到所有的内容: http://code.activestate.com/recipes/langs/python ,但是书中选取的都是精华片段,还包括很多编者们的评语,最近这本书将要推出新版。
当你学习完Python之后你需要做些什么呢?也许你已经读完了我推荐给你的书,又或者你已经对 Python 有了深入的学习,开始并且能写出一些基本的工具或者应用。然而,如果你想做的更好,你就必须更加深入的学习关于其他的特定主题的书籍,比如游戏编程,数据库,图形与多媒体,图形用户界面,科学的编程,网络,等等。
一定会有一些关于更高级主题(比如我们上面讨论的主题或者其他的)的教程,不要误会我的意思。如果你想要学习一些我们上面提到的技术,你必须购买一本涵盖你所感兴趣的主题的书。但是如果你只是希望提高你的基本功,你就不必这样做了。如果你是这种情况,那么你需要这本书:
作者:Wesley Chun 出版社:Prentice Hall 出版年:2012
如果你已经读过 Python 核心编程,你会发现这一本书的一部分内容已经在 Python 核心编程里出现,因为这本书就是由 Python 核心编程的第二部分改编而来的。在 Python 核心编程这本书里,我认为我已经完成了足够出色的能够教会读者使用 Python 的编著工作,但是没有足够细致的去介绍如何去开发。所以,这本 Python 核心应用编程更像是 Python 核心编程的一个延展材料。下面我开始对这本涵盖中高级水平内容的书进行介绍:
我很高兴的向大家介绍,一些原书的章节已经被我提取并重新整理,同时添加了 Python 3 与 2.x 系列的范例让读者可以同时学习 2.x 与 3.x 。在剩余的章节里,我添加了大量新的材料以便你学习哪怕你对于使用 Django 一窍不通,另外,还有对GAE,CSV,JSON和XML文本处理的介绍;目的是提供全面的介绍这些领域的应用开发,就像这本书的书名一样。我希望你能像我一样为这本书兴奋。
现在,你已经知道了解决不同问题可能需要用到的书,我们希望你能够通过这篇文章找到你所需要的。虽然我希望我的书能最符合你的要求,但我更高兴你能找到合适你的那一本书,并且通过 Python 开发很棒的应用。如果你是一个 Python 新手,欢迎你加入 Python 大家庭!
原文地址: http://www.informit.com/articles/article.aspx?p=1848528
译者: Upsuper
当你编写代码搜索数据库时,你不能总是依赖于相信所有的数据项都有正确的拼写。DreamHost 的开发者以及《Python 标准库编程范例》( The Python Standard Library by Example ) 的作者 Doug Hellmann 在这篇文章中回顾了一些根据目标的发音,而不是准确的拼写,进行数据库搜索的方法。
在数据库中搜索人名是一项独特的挑战。对于不同来源和不同年代的数据,你不但不能指望其中名字的拼写是正确的,甚至相同的名字如果多次出现时,它们的拼写都不一定一样。而储存的数据和搜索项之间也有可能因为个人喜好、文化差异、 同音词 、拼写错误、文盲或仅仅因为在某些时期根本没有标准拼法而出现差异。这些问题在历史学家、谱系学家和其他研究者的手写的文本记录中尤为常见。
一个常用的解决这样的字符串搜索问题的方法是寻找与搜索目标相近的值。但是,使用传统的 模糊匹配算法 计算两个任意字符串之间的相似度,代价是很大的,同时它也不适合用于搜索大规模数据集。一个更好的解决方案是为数据库里的每一项预先计算一个哈希值,有一些专门的哈希算法正是为此设计的。这些语音算法 (Phonetic algorithm) 让你可以基于发音,而不是精确的拼写,来比较两个单词或者名字。
一个这样的算法叫做 Soundex ,它由 Margaret K. Odell、Robert C. Russell 于1900年代早期开发出来。由于 Soundex 算法被美国人口普查所使用,并且它正是专门为编码姓名所设计的,因此在谱系相关的环境中十分常见。计算 Soundex 的哈希值,首先保留名字的首字母,之后将后面的辅音字母根据一张简单的对照表转换为数字。舍去元音和连续的数字,并将结果保留到4个字符。
Fuzzy 库包含了一个 Python 程序可以使用的 Soundex 实现:
#!/usr/bin/env python
import fuzzy
names = ['Catherine', 'Katherine', 'Katarina',
'Johnathan', 'Jonathan', 'John',
'Teresa', 'Theresa',
'Smith', 'Smyth',
'Jessica',
'Joshua',
]
soundex = fuzzy.Soundex(4)
for n in names:
print '%-10s' % n, soundex(n)
这个程序 show_soundex.py
的输出显示出有一部分有相近读音的名字被编码为了相同的哈希值,但结果并不理想:
$ python show_soundex.py
Catherine C365
Katherine K365
Katarina K365
Johnathan J535
Jonathan J535
John J500
Teresa T620
Theresa T620
Smith S530
Smyth S530
Jessica J200
Joshua J200
在这个例子中, Theresa 和 Teresa 产生了相同的 Soundex 值,但 Catherine 和 Katherine 虽然听起来一样,却因为有不同的首字母而输出了不同的结果。而最后两个名字 Jessica 和 Joshua 他们一点关系都没有,但却得到了相同的结果,仅仅因为字母 J、S 和 C 都映射到了数字2上,并且算法删除了重复项。这类错误正体现出了 Soundex 的主要缺陷。
在 Soundex 之后又发展出一些使用不同编码方案的算法,有的基于 Soundex 并改进了对照表,有的从头开始构建了自己的规则。所有这些算法都用不同的方法来处理 音位 以提高精确度。例如70年代,由Robert L. Taft 公布的 纽约州模式识别与智能系统 (New York State Identification and Intelligence System, NYSIIS) 算法。NYSIIS 最初被用于现在被称作纽约州刑事司法事物司 (New York State Division of Criminal Justice Services) 的部门,用以帮助他们识别他们数据库中的人。由于特别关注了对欧洲和西班牙姓氏中出现的音元的处理,它产生的结果好于 Soundex。
#!/usr/bin/env python
import fuzzy
names = ['Catherine', 'Katherine', 'Katarina',
'Johnathan', 'Jonathan', 'John',
'Teresa', 'Theresa',
'Smith', 'Smyth',
'Jessica',
'Joshua',
]
for n in names:
print '%-10s' % n, fuzzy.nysiis(n)
在我们的样例数据中, show_nysiis.py
的输出结果要好于 Soundex:
$ python show_nysiis.py
Catherine CATARAN
Katherine CATARAN
Katarina CATARAN
Johnathan JANATAN
Jonathan JANATAN
John JAN
Teresa TARAS
Theresa TARAS
Smith SNATH
Smyth SNATH
Jessica JASAC
Joshua JAS
在这里, Catherine 、 Katherine 和 Katariha 被映射到了相同的哈希值上。而由于 NYSIIS 使用了更多字母, Jessica 和 Joshua 的错误匹配也被消除了。
由 Lawrence Philips 在1990年发布的 Metaphone 算法是对早期系统如 Soundex 和 NYSIIS 的另一个改进。这种算法比其他的算法要远远复杂得多,因为它包含了许多特殊的规则用于处理拼写不一致和检查辅音与一些元音的组合。一个叫做 Double Metaphone 的升级版算法走得更远,它进一步添加了一些用于处理其他语言的拼写和发音的规则。
#!/usr/bin/env python
import fuzzy
names = ['Catherine', 'Katherine', 'Katarina',
'Johnathan', 'Jonathan', 'John',
'Teresa', 'Theresa',
'Smith', 'Smyth',
'Jessica',
'Joshua',
]
dmetaphone = fuzzy.DMetaphone(4)
for n in names:
print '%-10s' % n, dmetaphone(n)
除了有更大的编码规则集,Double Metaphone 还为每个输入的单词产生两个可选的哈希值,这让调用者可以实现两级精度的搜索。在我们样例程序的结果中, Catherine 和 Katherine 的主哈希值是相同的,它们的次哈希值和 Katarina 的主哈希值是相同的。这样就发现了 Soundex 无法发现的匹配,同时又降低了结果的权重,不像 NYSIIS 那样完全没有差别。
$ python show_dmetaphone.py
Catherine ['K0RN', 'KTRN']
Katherine ['K0RN', 'KTRN']
Katarina ['KTRN', None]
Johnathan ['JN0N', 'ANTN']
Jonathan ['JN0N', 'ANTN']
John ['JN', 'AN']
Teresa ['TRS', None]
Theresa ['0RS', 'TRS']
Smith ['SM0', 'XMT']
Smyth ['SM0', 'XMT']
Jessica ['JSK', 'ASK']
Joshua ['JX', 'AX']
在你的程序中使用语音检索是非常简单的,但是你也许需要给数据库服务器添加扩展或者给你的程序捆绑第三方库。MySQL、PostgreSQL、SQLite 和 Microsoft SQL Server 都支持使用一个可以直接在查询中调用的字符串函数来计算 Soundex。PostgreSQL 同时也包含了用于计算原始的 Metaphone 和 Double Metaphone 的函数。
对于主流的语言,如 Python、PHP、Ruby、Perl、C/C++ 和 Java,每种算法也都有独立的实现。这些库可以被用于那些没有提供内建的语音算法支持的数据库,如 MongoDB。举例来说,下面的脚本加载一系列的名字到数据库,同时为每个名字预计算他们的哈希值使得将来的搜索更容易:
#!/usr/bin/env python
import argparse
import fuzzy
from pymongo import Connection
parser = argparse.ArgumentParser(description='Load names into the database')
parser.add_argument('name', nargs='+')
args = parser.parse_args()
c = Connection()
db = c.phonetic_search
dmetaphone = fuzzy.DMetaphone()
soundex = fuzzy.Soundex(4)
for n in args.name:
# Compute the hashes. Save soundex
# and nysiis as lists to be consistent
# with dmetaphone return type.
values = {'_id':n,
'name':n,
'soundex':[soundex(n)],
'nysiis':[fuzzy.nysiis(n)],
'dmetaphone':dmetaphone(n),
}
print 'Loading %s: %s, %s, %s' % \
(n, values['soundex'][0], values['nysiis'][0],
values['dmetaphone'])
db.people.update({'_id':n}, values,
True, # insert if not found
False,
)
在命令行执行 mongodb_load.py
来保存名字,并且稍后将他们取出来:
$ python mongodb_load.py Jonathan Johnathan Joshua Jessica
Loading Jonathan: J535, JANATAN, ['JN0N', 'ANTN']
Loading Johnathan: J535, JANATAN, ['JN0N', 'ANTN']
Loading Joshua: J200, JAS, ['JX', 'AX']
Loading Jessica: J200, JASAC, ['JSK', 'ASK']
$ python mongodb_load.py Catherine Katherine Katarina
Loading Catherine: C365, CATARAN, ['K0RN', 'KTRN']
Loading Katherine: K365, CATARAN, ['K0RN', 'KTRN']
Loading Katarina: K365, CATARAN, ['KTRN', None]
搜索程序 mongodb_search.py
让用户可以选择一种哈希函数,然后构建一个 MongoDB 查询来找到所有哈希值与输入的名字匹配的项。
#!/usr/bin/env python
import argparse
import fuzzy
from pymongo import Connection
ENCODERS = {
'soundex':fuzzy.Soundex(4),
'nysiis':fuzzy.nysiis,
'dmetaphone':fuzzy.DMetaphone(),
}
parser = argparse.ArgumentParser(description='Search for a name in the database')
parser.add_argument('algorithm', choices=('soundex', 'nysiis', 'dmetaphone'))
parser.add_argument('name')
args = parser.parse_args()
c = Connection()
db = c.phonetic_search
encoded_name = ENCODERS[args.algorithm](args.name)
query = {args.algorithm:encoded_name}
for person in db.people.find(query):
print person['name']
在这样例中,结果集里额外返回的值正是我们所需要的,因为它们是正确的匹配项。另一方面我们也看到,用 Soundex 搜索 Joshua 再次返回了不相关的值 Jessica :
$ python mongodb_search.py soundex Katherine
Katherine
Katarina
$ python mongodb_search.py nysiis Katherine
Catherine
Katherine
Katarina
$ python mongodb_search.py soundex Joshua
Joshua
Jessica
$ python mongodb_search.py nysiis Joshua
Joshua
虽然 Soundex 产生的结果比其他的算法差很多,但由于它内建于许多数据库服务器,它仍然被广泛地应用。同时它的简单也让它比 NYSIIS 或 Double Metaphone 更快。在它的结果可以被接受的情况下,它的速度就成为了选择它的决定性因素。
我希望这篇文章给你展示了语音算法可以给你程序增添的搜索特性的力量,以及如何简单地实现他们。你的数据和你想要进行的搜索决定了哪一种算法才是你的正确选择。如果从数据上看,很难决定使用哪一个,或许你可以给用户提供一个选项让他们来选择一个恰当的算法。虽然给用户提供选择会让你需要做更多的工作来建立索引,但这为实验和改善搜索带来了极大的灵活性。很多研究者、历史学家和谱系学家对于这些算法的名字都很熟悉,即使不清楚他们的实现。所以给他们相应的选项应该不会吓跑这些用户。
原文地址: http://www.aaronsw.com/weblog/python3
译者: yudun1989
作为一个普通的 Python 开发者,不免会碰到 Python 2 到 3 转换不成功的问题。我经常会得到一些请求说要让我写的库能够在 Python 3 中运行,但是对于如何升级这个问题,在我尝试了各种各样解决冲突的建议之后,并没有发现有多少个可行的方案。
确实,当你看到新的 3.x 匆匆发布,却没有多少人来使用,难免会去猜测 Python 是否会在这次艰难的转型中倒下。那么我们应该如何来帮助它越过这道坎?
我似乎看到很多关于 Python 3 彻底的,未来的,崭新的版本特性的讨论,但我们却忽视了升级的方法。在 Python 2 时代,我们有非常清楚的方法来应对版本更新。
from __future__ import new_feature
语句,你就可以明确声明你想要使用的新特性来使用新功能。这种方法可能确实蛮有效,但是我却不清楚为什么在向 Python 3 转换的过程中,却没有什么效果。它可能只是想表达:
from __future__ import python3
将这个加在文件的首部将会声明这是一个 Python 3 文件,编译器将会正确的解析它 (我意识到这样做可能会为 Python 2 和 3 的编译器带来许多繁重的工作,但是坦诚地说,维护一个统一的代码库永远都是件好事)。
如果我想让我基于 Python 2 的程序去使用一些属于 Python 3 的模块,我只需要确认这些模块在头部都有调用 import 。如果我想给我的模块发布一个可以在 Python 3 上运行的新版本,我只需要声明这个程序只在 Python 2.x 或更高版本中运行(使用新的 import
语句)。如果项目比较大,我甚至可以一次性地将版本升级到 3,留下旧代码,直到一些人来修正这些琐碎。最重要的,我可以升级到 Python 3 而不必等到所有的依赖都满足、直到有一个完整的方案时才来解决。
用户只有知道在不改变既有代码的情况下他们才会升级到 2.x 版本。开发者知道大家最终会升级到 2.x 所以他们放弃对之前版本的支持。但是既然 2.x 的代码在 3 下也兼容,那么他们就会开始编写和发布可以和未来兼容的代码,最终大部分的代码都将会在 Python 3 下工作,用户也会升级到 3 (2.x 将会对一些残余的代码给以警告)。最后我们可以丢掉对 2.x 的支持,然后让所有的代码都能得到更好的兼容。
这不是一个激进的点子,这是 Python 升级既有的方式。除非我们再次这样做,否则我不知道我们如何才能渡过这个艰难的阶段。
Hi Pythonistas,
本周的 PyCoder’s Weekly 由 SWIX 盛情赞助。
让我们开始吧,本周的关注热点有:使用 Python 进行 iOS 开发, Python 3 的一些新的改进等等。
我们保证下周的时候你们就能在直接在网站上看到之前所有的周刊了,我们刚刚结束了存档页面的设计。同时,像往常一样,欢迎给我们提建议,直接回复这封邮件就行了。
把我们 分享 给你的朋友吧!
Enjoy.
– Mahdi and Mik
任务队列管理库 Celery 在本周发布了新版本,去试试新功能吧!
Python 2.7 运行时终于摘掉了 Beta 的帽子,从实验室毕业了,同时还支持多个流行库(比如 PIL、numpy、lxml 等等),是时候去折腾下GAE了!
关于 Python 与 Haskell 代码可读性的讨论非常激烈。最近有人在 Github 上发了一篇文章,分别用 Haskell 与 Python 实现了一个基于 Trie 树的自动补全程序,这个讨论值得一看。
Guido 批准在 Python 3 中提供 Unicode 标识符的功能,详细信息请在 PEP 414 中查看。
这是一个非常有爱 iOS应用,用来检测你的 Python 知识,这个应用是为那些想去学习 Python 的人所设计的。
首先普及一下知识, Travis CI 是一个基于云的持续集成项目,就像 Jenkins 那样的。Travis 是最近才出现的新项目,现在他已经能够完美支持 Python 项目了。最近我们试用了下,偶尔会出现一些小问题但是基本上还是不错的。
这是 iOS 平台上的一个非常简洁的 Python IDE,不管你想用它来干啥,这个东西真的是酷!毙!了!Python for iOS 拥有完整的解释器,代码编辑器以及文档浏览器。快用你的 iPad 试试这个东西吧~
这是一篇很棒的文章,我们喜欢 Django,但是我们经常不理解为什么很多人觉得 Django 对于小项目来说太笨重了,Software Maniacs 解决了这个问题,让 Django 也能灵活适用于一些小项目,读读看吧,或许会改变你对 Django 的看法。
对于 Python 的包分发方式一直有很多的讨论,在这篇文章里 Ian 简单勾勒出了未来的应用开发与部署方案。
这个东西真的很酷, httpie 是一个用 Python 写的类似 cURL 的程序,实际上他只是一个对于 python-request 的包装。基本你能用 cURL 做的事情它都可以做,而且实现方式比 cURL 更加优美,还有各种代码高亮,以及更多的牛逼功能,赶快把它加到你的工具库里面吧。
想知道自动补全功能是怎么做出来的么,读一下这篇文章你就知道了,这篇文章利用 Python 实现了一个类似 Google 搜索建议的自动补全程序,利用 Python 代码很清晰的展示出了 Trie 树是如何工作的。读完这篇文章后或许你可以看看 Trie 树的维基百科条目。
要参加今年位于米帝的 PyCon 会议么? 今年的 PyCon US 提供了一个手机版的指南,提供 iOS Android 两种版本,你可以利用这个应用查询今年会议的时刻表,查看会场地图,向大会反馈意见等等。
这是一个很有意思的项目,作者认为静态虚拟机对于动态语言来说是非常不合适的,所以他开发了一个用纯 Python 编写的 Clojure 实现以让人们相信虚拟机可以拥有更好的性能,正如 pypy 那样。这个项目的发展方向很令人期待。
@fhaard 写的这一系列文章对 Python 的装饰器做了一个非常系统的解析,如果你想学习或复习关于 Python 装饰器的东西,可以看看这篇文章。
这是一篇关于如何使用 Pyhton 的 AST 模块对 Python 代码进行静态分析和修改的文章。它同时也介绍了关于 CPython 的编译过程以及修改 AST 树的工作过程。在这篇文章的最后,还有一个很有价值的 PyCon 演讲视频,来看看吧!
原文地址: http://softwaremaniacs.org/blog/2011/01/07/django-micro-framework/en/
翻译: yudun1989
大家都知道Django不适合仅仅想要显示一些HTML而不需要其他特殊组件的小型项目。如果你的项目仅仅是几个函数能搞定的话,去启动一个Django Project实际上是不值得的。
对于我来说这个其实很显然,但是最近鉴于从同事那里听来的一些说法,发现不是很多人能够意识到这一点。因为每个人并不总是能想起这个问题,所以结果你懂的。。我将会来演示如何用最少代价的来创建一个这样的Django项目好让大家更能理解这一点。
之后生成的代码会是这样
DEBUG = True
ROOT_URLCONF = 'urls'
TEMPLATE_DIRS = ['.']
from django.conf.urls.defaults import *
import views
urlpatterns = patterns('',
(r'^$', views.index),
(r'^test/(\d+)/$', views.test),
)
from django.shortcuts import render
def index(request):
return render(request, 'index.html', {})
def test(request, id):
return render(request, 'test.html', {'id': id})
这是一个已经带有全部功能的项目,你可以通过以下指令来运行它
django-admin.py --settings=settings runserver
你得到的是一个带有请求-应答通道和模板的Django系统。尽管我没有检查,但是其他一些库应该也可以工作。你丢失的只是ORM和其他所有依赖他的APP系统。但这毕竟可以用来应对一些小型工作了。
我想要深入一下应用的想法直到看起来有点荒谬,我决定将所有的代码变成一个可以运行的模块,并且成功了。
from django.conf import settings
if not settings.configured:
settings.configure(
DEBUG = True,
ROOT_URLCONF = 'web',
TEMPLATE_DIRS = ['.'],
)
from django.conf.urls.defaults import patterns
urlpatterns = patterns('',
(r'^$', 'web.index'),
(r'^test/(\d+)/$', 'web.test'),
)
#### Handlers
from django.shortcuts import render
def index(request):
return render(request, 'index.html', {})
def test(request, id):
return render(request, 'test.html', {'id': id})
#### Running
if __name__ == '__main__':
from django.core.management import execute_from_command_line
execute_from_command_line()
你可以这样来运行它
python web.py runserver
然而,将这些代码来放在一起并不是很好。因为文件中的可读部分已经被分开到不同的段中。这种暗示意味着它们最终应该分开到不同的文件中去。另外,我不敢凭记忆将这些代码写出来因为有众多的琐碎: 要避免初始化不同的配置文件,要记住很多import等等。
但是它仍然非常有趣。
当我在工程初始化中挣扎时,我发现ROOT_URLCONF的设置是在请求处理中被调用的。这会将Django管道与一个定义了 urlpattens变量的模块绑定,我认为如果Django能够通过一个简单的list来动态的进行初始化配置的话将会更加灵活:
settings.configure(
DEBUG = True,
urlconf = [
(r'^$', index),
],
)
也许你在匆忙中搭建一个环境时候也许会用到。比如在测试的时候。
原文地址: http://blog.ianbicking.org/2012/02/29/python-application-package/
翻译: CMGS
我经常琢磨着怎样统一的部署Python的Web应用(也是准备Python 夏日峰会 的一部分)。现在我有了想法。
我在一年前写了 这篇 文章,并且在最近修订了一些 建议 ,同时我也考虑了一些更加基础的东西:一种简单的方法部署服务端应用和打包代码。Web应用仅仅只是这种部署方案的一种用况而已。
现在,让我们称这种部署方案为“Python application package”。它拥有以下几点特征:
还有很多东西你可以从这4点中引申出来,并且在复杂的应用中你也能用到他们。你可能有些WSGI应用,也可能不是WSGI的应用去监听Web Sockets,或者来自Celery队列的一些东西,或者接受传入邮件等。几乎所有的情况下,我认为最基本的应用生命周期应该是这样:当应用部署完毕通过命令启动他们,有时候还会检测环境是不是对的,有时候也可以备份它的数据,或者干脆卸载它。
还有一些的是,所有的环境必须设置或者内嵌到应用之中。比如$TMPDIR应该指向一个地方放置应用的临时文件。或者,所有的应用有着自己的目录放置自己的日志文件(也许通过另外一个环境变量指定)。
为了更加具体,这里有我想象中的一个小型应用的描述;大概YAML应该是更好的文件格式:
platform: python, wsgi
require:
os: posix
python: <3
rpm: m2crypto
deb: python-m2crypto
pip: requirements.txt
python:
paths: vendor/
wsgi:
app: myapp.wsgiapp:application
我想平台意味着一系列的技术。系统并不是真正需要特定的python;当需要创建类似于 Silver Lining 的时候,我发现比较容易添加PHP支持(编译型语言可能不能这样容易移植,比如Go,这样会更容易扩展)。因此Python也是应用使用的特性中的一种。你能想象到很多其他模块化的功能,但是也会很容易导致非生产环境下的混乱。
应用对环境有一定的要求,比如说Python的版本和OS类型。应用可能还需要其他的库,可能这货还不好移植(M2Crypto就是一个例子)。现代化的包管理器能工作得很靠谱,所以依赖系统包管理器是我首先会尝试并且相信是最好的解决方法(我会把requirements.txt作为后备计划,但不会把它当做主要解决依赖的方案)。
我同时也认为应用程序主要依靠直接绑定在他们内部的依赖会是更可靠的选择(比如用vendor目录)。这种方案就是工具可能会参差不齐(译者表示这丫的是想表达版本混乱吧),但是我相信通过统一包格式会解决这个问题。这里有个 例子 告诉你肿么设置一个虚拟环境来管理依赖库(你这个时候不需要virtualenv才能去用这些库),这样做的话你能从你的版本控制中检查结果。这样做会有些复杂,不过还算能工作(好吧,几乎能工作-bin/files都需要修正,玩死你丫的)。但至少,这算一个好的开始吧。
在环境这一方面来说我们需要一个很好的库。 pywebapp 有一些基础的特性,虽然这货还没完成。我想要一个库像这样的:
from apppackage import AppPackage
app = AppPackage('/var/apps/app1.2012.02.11')
# Maybe a little Debian support directly:
subprocess.call(['apt-get', 'install'] +
app.config['require']['deb'])
# Or fall back of virtualenv/pip
app.create_virtualenv('/var/app/venvs/app1.2012.02.11')
app.install_pip_requirements()
wsgi_app = app.load_object(app.config['wsgi']['app'])
你想象下通过这个建立起一个服务,或者设置一个持续集成服务器(app.run_command(app.config[‘unit_test’])),等等。
如果设计得当,我认为这种应用在部署上格式也可以用在本地开发上。当checkout代码之后能直接在“开发环境”中运行,就像在其他环境中运行一样。
使用zip文件或者tar文件作为包格式已经过时了,或者说至少不能带来更多有利的一面。我看到用这些存档格式唯一的理由是这货容易迁移;但是我们得着眼于未来,我们有很多方法来移动目录,不需要迎合旧的习惯。如果用一个脚本创建一个tar,FTP到另一台机器,然后解压搞定 - 这种格式不应该知道实际上你是怎么传递文件的。But let’s not worry about copying WARs 。(译者按,最后一句,自己看wiki吧,这货就一坑啊,明显嘲讽Java的War格式)
原文地址: http://blueprintforge.com/blog/2012/02/27/static-modification-of-python-with-python-the-ast-module/
翻译: Upsuper
修改代码在有时会变的十分有用,比如在进行测试和分析的时候。在这篇文章中,我们将看到如何使用 ast
模块对 Python 代码进行修改,同时还将看到一些使用了这个技术的工具。
在开始之前,我们应该先看看 CPython 的编译过程,这个过程在 PEP 339 中有详细的描述。
当然,在读这篇文章的时候,你并不需要对这个步骤有很深入的理解,不过这可以帮助你对整个过程有一个大体的了解。
首先,编译器会根据源代码生成一棵语法分析树 (Parse Tree),随后,再根据语法分析树建立抽象语法树 (AST, Abstract Syntax Tree)。从 AST 中可以生成出控制流图 (CFG, Control Flow Graph),最后再将控制流图编译为代码对象 (Code Object)。
图中标蓝的部分就是 AST 这一步,也就是我们今天所关注的部分。Python 从 2.6 开始就提供了现在这样的 ast
模块,它提供了一种访问和修改 AST 的简单方式。
通过这个,我们可以从 AST 中生成代码对象,也可以出于某些原因,根据修改过的 AST 重新生成源代码。
先来写一点简单的代码,我们写一个叫做 add
的函数,然后观察它所生成的 AST。
>>> import ast
>>> expr = """
... def add(arg1, arg2):
... return arg1 + arg2
... """
>>> expr_ast = ast.parse(expr)
>>> expr_ast
<_ast.Module object at 0x10a7a09d0>
现在我们已经生成了一个 ast.Module
对象,我们来看看它的内容:
>>> ast.dump(expr_ast)
"Module(
body=[
FunctionDef(
name='add', args=arguments(
args=[
Name(id='arg1', ctx=Param()),
Name(id='arg2', ctx=Param())
],
vararg=None,
kwarg=None,
defaults=[]),
body=[
Return(
value=BinOp(
left=Name(id='arg1', ctx=Load()),
op=Add(),
right=Name(id='arg2', ctx=Load())))
],
decorator_list=[])
])"
正如我们所见, Module
是父节点,它的 body
中包含了一个函数定义的元素,这个函数定义包含了函数名、参数列表和函数体。函数体又包含了一个单独的 Return
节点,节点中含有一个 Add
运算。
我们如何修改这棵树以改变代码的作用呢?为了说明这个问题,我们来做点也许你永远也不会在你自己代码中做的疯狂的事情吧。我们将遍历这棵树,并且将 Add
运算修改为 Mult
运算。看,我说过这很疯狂吧!
我们要先建立一个 NodeTransformer
变换器的子类,并且定义 visit_BinOp
方法。每当这个变换器访问到一个二元运算符节点时,就会调用这个方法。
class CrazyTransformer(ast.NodeTransformer):
def visit_BinOp(self, node):
print node.__dict__
node.op = ast.Mult()
print node.__dict__
return node
现在我们已经定义好了我们这个奇怪的变换器,让我们看看将它应用于我们开始时写的那些代码会怎么样:
>>> transformer = CrazyTransformer()
>>> transformer.visit(expr_ast)
{
'op': <_ast.Add object at 0x10a8321d0>,
'right': <_ast.Name object at 0x10a839390>,
'lineno': 3, 'col_offset': 8,
'left': <_ast.Name object at 0x10a839350>}
{
'op': <_ast.Mult object at 0x10a839510>,
'right': <_ast.Name object at 0x10a839390>,
'lineno': 3, 'col_offset': 8,
'left': <_ast.Name object at 0x10a839350>}
你可以从输出的结果对比发现, Add
节点已经被替换成了一个 Mult
。我们有许多方法没有提到,比如访问子节点,不过这个例子已经足以刻画出它的基本原理。
我们在最初的代码后面加上一个调用,比如:
print add(4, 5)
让我们看看这些代码是如何运行的:
>>> unmodified = ast.parse(expr)
>>> exec compile(unmodified, '<string>', 'exec')
9
>>> transformer = CrazyTransformer()
>>> modified = transformer.visit(unmodified)
>>> exec compile(modified, '<string>', 'exec')
20
我们可以看到,未修改的和修改后的 AST 所编译出的代码,一个输出了9,一个输出了20。
最后,我们可以用 unparse
模块将修改后的代码转换回对应的源代码, unparse
模块可以在 这里 找到。
>>> unparse.Unparser(modified, sys.stdout)
def add(arg1, arg2):
return (arg1 * arg2)
print add(4, 5)
正如我们所看到的, *
运算符取代了 +
。在这个反解析工具对于理解你的 AST 变换器如何修改代码很有帮助。
显然,我们上面的例子在实际应用中几乎没有意义。然而静态分析和修改代码却是十分有用的。
比如你可以为测试程序而注入一些代码。你可以看看 这篇 PyCon 演讲 ( origin ) 以理解如何使用一个节点转换器注入指令代码来测试程序。
除此之外, Pythonscope 项目 也使用了 AST 访问器 (visitor) 来处理源代码并根据函数签名生成测试。
还有像 pylint 这样的项目使用 AST 步移法 (walking method) 来分析源代码。在 pylint 中,Logilab 还建立了一个模块专门用于:
“提供一个通用的 Python 源代码基本表示方式以为如 pychecker、pyreverse 或 pylint 等项目的开发提供方便。”
你可以在 这里 看到更多关于这个项目的信息。
Matthew J Desmarais 的 这篇 PyCon 演讲 ( origin ) 以及 Eli Bendersky 的 这篇博客 对于本文的帮助是无可估量的。
翻译: TheLover_Z
原文地址: http://blaag.haard.se/Python-Closures-and-Decorators–Pt–1/
回想起来,当初我做出了错误的选择,把 Python 的课程削减到了4个小时以至于把装饰器的部分搞砸了,我答应大家我稍后会对闭包和装饰器做一个更好的解说 —— 我是这么打算的。
函数也是对象。实际上,在 Python 中函数是一级对象——也就是说,他们可以像其他对象一样使用而没有什么特别的限制。这给了我们一些有趣的选择,我会由浅到深解释这个问题。
关于函数就是对象的一个最常见的例子就是 C 中的函数指针;将函数传递到其他的将要使用它的函数。为了说明这一点,我们来看看一个重复函数的实现 —— 也就是,一个函数接受另外一个函数以及一个数字当作参数,并且重复调用指定函数指定次数:
>>> #A very simple function
>>> def greeter():
… print("Hello")
…
>>> #An implementation of a repeat function
>>> def repeat(fn, times):
… for i in range(times):
… fn()
…
>>> repeat(greeter, 3)
Hello
Hello
Hello
>>>
这种模式在很多情况下都有用 —— 比如向一个排序算法传递比较函数,向一个语法分析器传递一个装饰器函数,通常情况下这些做法可以使一个函数的行为 更专一化 ,或者向已经抽象了工作流的函数传递一个待办的特定部分(比如, sort()
知道怎么排序, compare()
知道怎么比较元素)。
函数也可以在其他函数的内部声明,这给了我们另一个很重要的工具。在一般情况下,这可以用来隐藏实用函数的实现细节:
>>> def print_integers(values):
… def is_integer(value):
… try:
… return value == int(value)
… except:
… return False
… for v in values:
… if is_integer(v):
… print(v)
…
>>> print_integers([1,2,3,"4", "parrot", 3.14])
1
2
3
这可能是有用的,但它本身并不算是个强大的工具。相比函数可以当作参数被传递而言,我们可以将它们包装(wrap)在另外的函数中,从而向已经构建好的函数增加新的行为。一个简单的例子是向一个函数增加跟踪输出:
>>> def print_call(fn):
… def fn_wrap(*args, **args): #take any arguments
… print ("Calling %s" % (fn.func_name))
… return fn(*args, **kwargs) #pass any arguments to fn()
… return fn_wrap
…
>>> greeter = print_call(greeter) #wrap greeter
>>> repeat(greeter, 3)
Calling fn_wrap
Hello
Calling fn_wrap
Hello
Calling fn_wrap
Hello
>>>
>>> greeter.func_name
'fn_wrap'
正如你看到的那样,我们可以使用带日志的函数来替换掉现有函数相应的部分,然后调用原来的函数。在例子的最后两行,函数的名字已经反映出了它已经被改变,这个改变可能是我们想要的,也可能不是。如果我们想包装一个函数同时保持它原来的名字,我们可以增加一行 print_call
函数,代码如下:
>>> def print_call(fn):
… def fn_wrap(*args, **kwargs): #take any arguments
… print("Calling %s" % (fn.func_name))
… return fn(*args, **kwargs) #pass any arguments to fn()
… fn_wrap.func_name = fn.func_name #Copy the original name
… return fn_wrap
因为这是一个很长的话题,我明天会来更新第二部分,我们会讲讲闭包,偏函数(partial),还有(终于到它了)装饰器。
至此,如果这些你之前全部没有接触过,可以先用 print_call
函数作为基础,来创建一个能够在正常调用函数之前先打印出这个函数名字的一个修饰器。
原文地址: http://blaag.haard.se/Python-Closures-and-Decorators–Pt–2/
在第一部分中,我们学习了以函数作为参数调用其他的函数,还有嵌套函数,最终我们把一个函数包装在另外的函数中。我们先把第一部分的答案给出:
>>> def print_call(fn):
… def fn_wrap(*args, **kwargs):
… print("Calling %s with arguments: \n\targs: %s\n\tkwargs:%s" %fn.__name__, args, kwargs))
… retval = fn(*args, **kwargs)
… print("%s returning '%s'" % (fn.func_name, retval))
… return retval
… fn_wrap.func_name = fn.func_name
… return fn_wrap
…
>>> def greeter(greeting, what='world'):
… return "%s %s!" % (greeting, what)
…
>>> greeter = print_call(greeter)
>>> greeter("Hi")
Calling greeter with arguments:
args: ('Hi',)
kwargs:{}
greeter returning 'Hi world!'
'Hi world!'
>>> greeter("Hi", what="Python")
Calling greeter with arguments:
args: ('Hi',)
kwargs:{'what': 'Python'}
greeter returning 'Hi Python!'
'Hi Python!'
>>>
这稍微有那么点儿用了,但它可以变的更好!你可能听说过或者没有听说过*闭包*,你可能听说过成千上万种闭包定义中的某一种或者某几种 —— 我不会那么挑剔,我只是说闭包就是一个捕捉了(或者关闭)非本地变量(自由变量)的代码块(比如一个函数)。如果你不清楚我在说什么,你可能需要进修一下 CS 的相关课程,但是不要担心 —— 我会给你演示例子。闭包的概念很简单:一个可以引用在函数闭合范围内变量的函数。
比如说,看一下这个代码:
>>> a = 0
>>> def get_a():
… return a
…
>>> get_a()
0
>>> a = 3
>>> get_a()
3
正如你看到的那样, get_a
函数可以取得 a
的值,并且可以读取更新后的值。然而这里有一个限制 —— 被捕获的变量(captured variable,下同)不能被写入。
>>> def set_a(val):
… a = val
…
>>> set_a(4)
>>> a
3
为什么会这样?由于闭包不能写入任何被捕获的变量, a = val
这个语句实际上写入了本地变量 a
从而隐藏了模块级别的 a
,这正是我们想写入的内容。为了解决这个限制(也许这并不是一个好主意),我们可以用一个容器类型:
>>> class A(object): pass
…
>>> a = A()
>>> a.value = 1
>>> def set_a(val):
… a.value = val
…
>>> a.value
1
>>> set_a(5)
>>> a.value
5
因此,我们已经知道了函数从它的闭合范围内捕捉变量,我们最终可以接触到有趣的东西了,我们先实现一个偏函数(partial,下同)。一个偏函数是一个你已经填充了部分或者全部参数的函数的实例;比如说你有一个存储了用户名和密码的会话,和一个查询后端的函数,这个函数有不同的参数但是*总是*需要身份验证。与其说每次都手动传递身份验证信息,我们可以用偏函数来预填充那些信息。
>>> #Our 'backend' function
… def get_stuff(user, pw, stuff_id):
… """Here we would presumably fetch data using the
… credentials and id"""
… print("get_stuff called with user: %s, pw: %s, stuff_id: %s" % (user, pw, stuff_id))
>>> def partial(fn, *args, **kwargs):
… def fn_part(*fn_args, **fn_kwargs):
… kwargs.update(fn_kwargs)
… return fn(*args + fn_args, **kwargs)
… return fn_part
…
>>> my_stuff = partial(get_stuff, 'myuser', 'mypwd')
>>> my_stuff(3)
get_stuff called with user: myuser, pw: mypwd, stuff_id: 3
>>> my_stuff(67)
get_stuff called with user: myuser, pw: mypwd, stuff_id: 67
偏函数可以用在许多地方来消除代码的重复。当然,你没有必要自己手动实现它,只需要 from functools import partial
就可以了。
最后,我们来看看函数装饰器(未来可能有类装饰器)。函数装饰器接收一个函数作为参数然后返回一个新的函数。听起来很熟悉吧?我们已经实现过一个 print_call
装饰器了。
>>> @print_call
… def will_be_logged(arg):
… return arg*5
…
>>> will_be_logged("!")
Calling will_be_logged with arguments:
args: ('!',)
kwargs:{}
will_be_logged returning '!!!!!'
'!!!!!'
使用@符号标记是一个很方便的方法。
>>> def will_be_logged(arg):
… return arg*5
…
>>> will_be_logged = print_call(will_be_logged)
但是如果我们想要确定装饰器的参数呢?在这种情况下,作为装饰器的函数会接收参数,并且返回一个包装(wrap)了装饰器函数的函数。
>>> def require(role):
… def wrapper(fn):
… def new_fn(*args, **kwargs):
… if not role in kwargs.get('roles', []):
… print("%s not in %s" % (role, kwargs.get('roles', [])))
… raise Exception("Unauthorized")
… return fn(*args, **kwargs)
… return new_fn
… return wrapper
…
>>> @require('admin')
… def get_users(**kwargs):
… return ('Alice', 'Bob')
…
>>> get_users()
admin not in []
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in new_fn
Exception: Unauthorized
>>> get_users(roles=['user', 'editor'])
admin not in ['user', 'editor']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in new_fn
Exception: Unauthorized
>>> get_users(roles=['user', 'admin'])
('Alice', 'Bob')
就是这样。你现在会写装饰器了,也许你会用这些知识去写面向方面(aspect-oriented)的编程。加入 @cache
, @trace
, @throttle
都是微不足道的(在你添加 @cache
之前,一定要检查 functools
,如果你用的是 Python 3 的话!)