用python编写“生日悖论解决”的解决方法

相关随笔:
MapReduce与HDFS简介
什么是Hadoop?
Google为自己的业务需要提出了编程模型MapReduce和分布式文件系统Google File System,并发布了相关论文(可在Google Research的网站上获得:
、 )。 Doug Cutting和Mike Cafarella在开发搜索引擎Nutch时对这两篇论文做了自己的实现,即同名的MapReduce和HDFS,合起来就是Hadoop。
MapReduce的Data flow如下图,原始数据经过mapper处理,再进行partition和sort,到达reducer,输出最后结果。
图片来自Hadoop: The Definitive Guide
Hadoop Streaming原理
Hadoop本身是用Java开发的,程序也需要用Java编写,但是通过Hadoop Streaming,我们可以使用任意语言来编写程序,让Hadoop运行。
Hadoop Streaming的相关源代码可以在 查看。简单来说,就是通过将用其他语言编写的mapper和reducer通过参数传给一个事先写好的Java程序(Hadoop自带的*-streaming.jar),这个Java程序会负责创建MR作业,另开一个进程来运行mapper,将得到的输入通过stdin传给它,再将mapper处理后输出到stdout的数据交给Hadoop,partition和sort之后,再另开进程运行reducer,同样地通过stdin/stdout得到最终结果。因此,我们只需要在其他语言编写的程序里,通过stdin接收数据,再将处理过的数据输出到stdout,Hadoop streaming就能通过这个Java的wrapper帮我们解决中间繁琐的步骤,运行分布式程序。
图片来自Hadoop: The Definitive Guide
原理上只要是能够处理stdio的语言都能用来写mapper和reducer,也可以指定mapper或reducer为Linux下的程序(如awk、grep、cat)或者按照一定格式写好的java class。因此,mapper和reducer也不必是同一类的程序。
Hadoop Streaming的优缺点
可以使用自己喜欢的语言来编写MapReduce程序(换句话说,不必写Java XD)
不需要像写Java的MR程序那样import一大堆库,在代码里做一大堆配置,很多东西都抽象到了stdio上,代码量显著减少
因为没有库的依赖,调试方便,并且可以脱离Hadoop先在本地用管道模拟调试
只能通过命令行参数来控制MapReduce框架,不像Java的程序那样可以在代码里使用API,控制力比较弱,有些东西鞭长莫及
因为中间隔着一层处理,效率会比较慢
所以Hadoop Streaming比较适合做一些简单的任务,比如用python写只有一两百行的脚本。如果项目比较复杂,或者需要进行比较细致的优化,使用Streaming就容易出现一些束手束脚的地方。
用python编写简单的Hadoop Streaming程序
这里提供两个例子:
使用python编写Hadoop Streaming程序有几点需要注意:
在能使用iterator的情况下,尽量使用iterator,避免将stdin的输入大量储存在内存里,否则会严重降低性能
streaming不会帮你分割key和value传进来,传进来的只是一个个字符串而已,需要你自己在代码里手动调用split()
从stdin得到的每一行数据末尾似乎会有\n,保险起见一般都需要使用rstrip()来去掉
在想获得K-V list而不是一个个处理key-value pair时,可以使用groupby配合itemgetter将key相同的k-v pair组成一个个group,得到类似Java编写的reduce可以直接获取一个Text类型的key和一个iterable作为value的效果。注意itemgetter的效率比lambda表达式要高,所以如果需求不是很复杂的话,尽量用itemgetter比较好。
我在编写Hadoop Streaming程序时的基本模版是
#!/usr/bin/env python
# -*- coding: utf-8 -*-
Some description here...
import sys
from operator import itemgetter
from itertools import groupby
def read_input(file):
"""Read input and split."""
for line in file:
yield line.rstrip().split('\t')
def main():
data = read_input(sys.stdin)
for key, kviter in groupby(data, itemgetter(0)):
# some code here..
if __name__ == "__main__":
如果对输入输出格式有不同于默认的控制,主要会在read_input()里调整。
本地调试用于Hadoop Streaming的python程序的基本模式是:
$ cat &input path& | python &path to mapper script& | sort -t $'\t' -k1,1 | python &path to reducer script& & &output path&
或者如果不想用多余的cat,也可以用&定向
$ python &path to mapper script& & &input path& | sort -t $'\t' -k1,1 | python &path to reducer script& & &output path&
这里有几点需要注意:
Hadoop默认按照tab来分割key和value,以第一个分割出的部分为key,按key进行排序,因此这里使用
sort -t $'\t' -k1,1
来模拟。如果你有其他需求,在交给Hadoop Streaming执行时可以通过命令行参数调,本地调试也可以进行相应的调整,主要是调整sort的参数。因此为了能够熟练进行本地调试,建议先掌握sort命令的用法。
如果你在python脚本里加上了shebang,并且为它们添加了执行权限,也可以用类似于
./mapper.py
python mapper.py
阅读(...) 评论()IBM Bluemix
点击按钮,开始云上的开发!
developerWorks 社区
从编写过程式脚本转换到面向对象的编程通常是非常困难的。本文探索如何重用来自 PHP、Bash 或 Python 脚本的程序,转换到 Python 中的面向对象的编程。本文还将简略地谈到函数式编程的适当使用。
, 软件工程师, Giftcs
Noah Gift 是 O'Reilly 出版的“Python For Unix and Linux”一书的合著者。他是一名作家、演说家、顾问和社区负责人,并为 IBM developerWorks、Red Hat Magazine、O'Reilly 和 MacTech 众多出版商撰稿。他的咨询公司的网站是 ,他的个人网站是 。Noah 目前还是 www.pyatl.org 网站的组织者,该网站是佐治亚州亚特兰大的 Python 用户组。他拥有加州洛杉矶的 CIS 的硕士学位,加州 Poly San Luis Obispo 的营养科学学士学位,他还是通过 Apple 和 LPI 认证的系统管理员,他曾经在许多公司工作过,如加利福尼亚理工学院、Disney Feature Animation、Sony Imageworks 和 Turner Studios。在空闲的时候,他喜欢和妻子 Leah,以及他们的儿子 Liam 一起度过,弹奏钢琴以及进行宗教活动。
引言Python 在近年来的受欢迎程度剧增,部分原因在于该语言非常灵活,同时功能非常强大。Python 可用于系统管理、Web 开发、GUI 编程、科学计算等等。本文的主要目标是向习惯于使用 Bash、PHP 或其它某种语言编写脚本过程代码的人介绍面向对象的 Python 开发,并帮助他们转换到面向对象的 Python 开发。Python 的这种日益流行性意味着,对于目前使用其他编程语言的开发人员,除了使用他们最喜欢的语言之外,他们还可以采用 Python 来完成某些项目。
过程式编程当然有其用武之地,并且可能是解决某个问题的高度有效的方法。在非常基本的层次上,过程式编程可定义为指令的列表,Bash 和 PHP 通常就是以这样的方式编写的。然而由于 Python 的流行,对于作为 Web 开发人员或系统管理员的 PHP 和 Bash 脚本编写人员,他们正陷入必须同时学习面向对象的编程和 Python 的境地。
面向对象这个概念很难一次性地掌握,因此本文采用过程式 Bash 和 PHP 脚本,并首先将它们转换为过程式 Python。作为最后一步,它们将转换为面向对象的 Python 这个终结目标。本文在结束时将简略讨论一下面向对象的 Python 的一些优点,然后在最后讨论一些可能更适合采用过程或函数式编程的一些不利场景。到本文结束时,Bash 或 PHP 程序员应该能够毫无畏惧地一头扎进面向对象的 Python 项目。
采用 PHP 和 Bash 编写磁盘监视函数
虽然 PHP 主要是为了在浏览器中运行,但是也可以通过 exec 函数执行系统调用。采用 PHP 编写的第一个示例将捕获 Shell 命令“df –h”的输出,将输出放在一个数组中,然后根据一个正则表达式检查输出的每一行。如果该行与正则表达式匹配,则打印该行。如果您希望从主目录运行此示例,只需将此脚本命名为 index.php,并将其放在 Apache/mod_php 服务器的对外服务目录中。
PHP 磁盘监视示例&html&
//Analyzes disk usage
//Takes regex pattern and message
function disk_space( $pattern="/2[0-9]%/", $message="CAPACITY WARNING:" )
exec(escapeshellcmd("df -h"),$output_lines,$return_value);
foreach ($output_lines as $output) {
if (preg_match( $pattern, $output ))
echo "&b&$message&/b& $output &br /&";
disk_space()
&/html&如果您在浏览器中运行此网页,将会获得以下结果:
CAPACITY WARNING: /dev/sda1 3.8G 694M 2.9G 20% /查看该代码,可以看到正则表达式模式被设置为匹配某个包含 20-29% 的行。可以容易地修改此模式以适应其他标志,例如 90-99%,因为 20% 是非常低的磁盘容量。
下面让我们看一下如何在 Bash 函数中完成同样的事情。在 Bash 中,该问题要容易解决得多,因为您实际上是在处理系统调用。在此示例中,您甚至不需要使用数组或正则表达式库,因为使用到 grep 的管道容易多了。不过,在 Bash 中设置函数的缺省参数始终有点麻烦。
Bash 磁盘监视示例#!/usr/bin/env bash
#function flags disk usage takes pattern and message optionally
function disk_space ()
#checks for pattern parameter
if [ "$1" != "" ]; then
pattern=$1
pattern="2[0-9]%"
#checks for message parameter
if [ "$2" != "" ]; then
message=$2
message="CAPACITY WARNING:"
#looks at output for pattern to flag
output_lines=`df -h | grep $pattern`
if [ "$output_lines" != "" ]; then
echo $message $output_lines
#example of optional parameters usage
#disk_space 9[0-9]% ALERT:
disk_space
当您运行此脚本时,将会获得同样的输出,因此可以跳过输出的显示。您能够从该脚本的 PHP 版本和 Bash 版本中找到的相关性在于,此过程式代码事实上像一组指令一样运行。似乎计算机就像是一个小孩,而您告诉该小孩如何做某件事情,例如第一次系鞋带。在您开始在 Python 中考虑“面向对象范式”之前,让我们首先看一下如何采用 Python 来创建这同一个脚本的过程式版本。
Python 磁盘监视示例from subprocess import Popen, PIPE
def disk_space(pattern="2[0-9]%", message="CAPACITY WARNING:"):
#takes shell command output
ps = Popen("df -h", shell=True,stdout=PIPE, stderr=PIPE)
output_lines = ps.stdout.readlines()
for line in output_lines():
line = line.strip()
if re.search(pattern,line):
print "%s %s" % (message,line)
disk_space()
浏览一下我们的代码的过程式 Python 版本,发现它与 Bash 和 PHP 版本非常相似。对于 Python,子过程模块处理对 Shell 命令的系统调用,并将输出发在一个列表(在 Bash 和 PHP 中称为数组)中。与 PHP 版本非常相似,然后我对命令的标准输出行列表中的项进行了迭代遍历。我寻找构成所寻找模式的正则表达式,然后使用注入的特殊消息来打印该磁盘报告行。这是如何解决自顶向下的脚本问题的经典示例,但是在下一个部分中,您将完全改变这种方法,并从对象的角度考虑问题。
从过程到面向对象的 Python
过程式编程通常是初学的开发人员的最自然编程风格,并且对于许多问题来说也是高度有效的。另一方面,对于创建抽象从而创建可重用的代码来说,面向对象的编程可能是非常有用的方法。然而,当项目达到某种程度的复杂性之后,过程代码通常会暴露出其根本缺陷。下面让我们直接进入上一个示例的面向对象版本,并看看这样有何变化。
面向对象的 Python 磁盘监视脚本
#!/usr/bin/env python
from subprocess import Popen, PIPE
class DiskMonitor():
"""Disk Monitoring Class"""
def __init__(self,
pattern="2[0-9]%",
message="CAPACITY WARNING",
cmd = "df -h"):
self.pattern = pattern
self.message = message
self.cmd = cmd
def disk_space(self):
"""Disk space capacity flag method"""
ps = Popen(self.cmd, shell=True,stdout=PIPE,stderr=PIPE)
output_lines = ps.stdout.readlines()
for line in output_lines:
line = line.strip()
if re.search(self.pattern,line):
print "%s %s" % (self.message,line)
if __name__ == "__main__":
d = DiskMonitor()
d.disk_space()
查看该代码的面向对象版本,可以看到代码变得更加抽象。有时,太多的抽象会导致设计问题,但是在此例中,它允许您将问题分离为更多可重用的部分。DiskMonitor 类具有 __init__ method,您可以在其中定义新的参数,并且 disk_space 函数现在是该类中的一个方法。
使用这种新的样式,您无需更改原始代码即可容易地重用和自定义各个部分,而使用过程代码时则通常必须更改原始代码。面向对象的设计的一个更加功能强大、通常也被过度使用的方面是继承。继承允许您在新的类中重用和自定义现有的代码。让我们在下一个示例中看看继承可能像什么样子。
使用继承的面向对象 Python 磁盘监视脚本
#!/usr/bin/env python
from subprocess import Popen, PIPE
class DiskMonitor():
"""Disk Monitoring Class"""
def __init__(self,
pattern="2[0-9]%",
message="CAPACITY WARNING",
cmd = "df -h"):
self.pattern = pattern
self.message = message
self.cmd = cmd
def disk_space(self):
"""Disk space capacity flag method"""
ps = Popen(self.cmd, shell=True,stdout=PIPE,stderr=PIPE)
output_lines = ps.stdout.readlines()
for line in output_lines:
line = line.strip()
if re.search(self.pattern,line):
print "%s %s" % (self.message,line)
class MyDiskMonitor(DiskMonitor):
"""Customized Disk Monitoring Class"""
def disk_space(self):
ps = Popen(self.cmd, shell=True,stdout=PIPE,stderr=PIPE)
print "RAW DISK REPORT:"
print ps.stdout.read()
if __name__ == "__main__":
d = MyDiskMonitor()
d.disk_space()
如果运行这个使用继承的脚本版本,您将获得以下输出:RAW DISK REPORT:
Filesystem
Used Avail Use% Mounted on
1% /var/run
0% /var/lock
0% /dev/shm此输出与前面带标记的版本区别非常大,因为它只是使用顶部注入的 print 语句来打印的未经筛选的 df –h 命令结果。通过重写 MyDiskMonitor 类中的方法,您能够完全改变 disk_space 方法的意图。
允许您重用其他类中的属性的 Python 魔法是这个“MyDiskMonitor(DiskMonitor)”语句。您只需在定义新类的名称时,将先前的类的名称放在括号内。一旦完成此步骤,您立即可以访问其他类属性来做自己希望的事情。但是乐趣不仅于此。通过添加另一个通过电子邮件来发送标记消息的方法,也许是将其命名为 disk_alert(self),这样就可以进一步自定义新类。这是面向对象的设计的美妙之处;它允许有经验的开发人员不断重用已编写的代码,从而节省大量的时间。
遗憾的是,面向对象的编程也有其不利的一面。所有这些抽象都是以复杂性为代价的,如果抽象过度,可能会彻底地弄巧成拙。由于 Python 支持多重继承,抽象可以达到相当有害的复杂程度。您是否能够想象只是为了编写一个方法也要查看多个文件的情况?无论相信与否,这种情况的确会发生,并且代表了面向对象编程的不幸现实。
面向对象的编程的替代方案是函数式编程,并且 Python 提供了用于进行函数式以及面向对象和过程式编程的资源。在最后一个示例中,我们将研究如何以函数式的方式编写现已变得非常无聊的磁盘监视代码。
函数式的 Python 磁盘监视脚本
from subprocess import Popen, PIPE
def disk_space(pattern="2[0-9]%", message="CAPACITY WARNING:"):
#Generator Pipeline To Search For Critical Items
ps = Popen("df -h", shell=True,stdout=PIPE, stderr=PIPE)
outline = (line.split() for line in ps.stdout)
flag = (" ".join(row) for row in outline if re.search(pattern, row[-2]))
for line in flag:
print "%s %s" % (message,line)
disk_space()
查看这最后一个示例,它与您从本文中看到的所有其他代码的区别都非常大。如果您逐行浏览该代码,可以首先从 “ps”变量中以前未见过的内容开始。接下来的两行代码使用生成器表达式来处理文件对象 ps.stdout,分析该文件并在其中搜索您正在查找的行。如果您将这些代码行剪切并粘贴到交互式的 Python Shell 中,如果打印的话,您将看到概要和标志都是生成器对象。生成器对象附带有下一个方法,因而允许您通过“管道”将操作连在一起。
概要行从一行中去除新行字符,并往下将该行传递给下一个生成器表达式,后者一次一个地在每行中搜索某个正则表达式匹配项,然后将输出传递给标记。此类紧凑的工作流可以替代面向对象的编程样式,并且相当有趣。然而,这种样式也有缺点,因为代码的简洁性会导致难于调试的错误,除非独立地执行每一行代码。函数式编程还很伤脑筋,因为它让您通过将解决方案链接在一起来考虑解决问题。无论是从过程式还是从面向对象样式的角度看,这都是相当不同的。
本文有点试验性质,因为它从 Bash 和 PHP 谈到了过程、面向对象,并在最后谈到了使用相同基本代码的函数式 Python。但愿本文说明了 Python 是一种非常灵活和功能强大的语言,其他编程语言的开发人员也可以学习欣赏。随着 Python 的越来越流行,其他开发人员除了首选语言之外,学习 Python 也将变得更加重要。
Python 最近的两个最大的发展领域是 Web 开发和系统管理。就 Web 开发而言,PHP 开发人员可能很快就必须做出每周的选择,即哪个项目采用 Python 更有意义,以及哪个项目采用 PHP 更有意义。对于系统管理员、Bash 和 Perl 脚本程序员,他们经常被要求采用 Python 完成某些项目。部分是因为这是没有选择的,部分是因为许多供应商正在为他们的产品提供 Python API。在您的工具箱中准备一点 Python 决不会伤害任何人。
下载描述名字大小Sample Object Oriented Python scripts5KB
参考资料 您可以参阅本文在 developerWorks 全球站点上的
。 包括参考信息以及 PHP 功能介绍。。 介绍了 MVC Web 框架的基础。讨论了函数式编程的一般概念,并演示了在 Python 中实现函数式技术的方法。了解适合于使用 来实现程序的 Python 功能。介绍了解决程序问题的不同范式。 讨论了各种在系统编程上下文中使用生成器函数和生成器表达式的技术。:developerWorks 的“AIX and UNIX 专区”提供了大量与 AIX 系统管理的所有方面相关的信息,您可以利用它们来扩展自己的 UNIX 技能。:访问“AIX and UNIX 新手入门”页面可了解更多关于 AIX 和 UNIX 的内容。:AIX and UNIX 专区已经为您推出了很多的技术专题,为您总结了很多热门的知识点。我们在后面还会继续推出很多相关的热门专题给您,为了方便您的访问,我们在这里为您把本专区的所有专题进行汇总,让您更方便的找到您需要的内容。:访问 developerWorks 的 Open source 专区以获得大量“How-to”信息、工具和项目更新,以帮助您使用开放源代码技术进行开发,以及在 IBM 产品中使用它们。:了解最新的 developerWorks 技术事件和网络广播。:收听 Podcast 并与 IBM 技术专家保持同步。
developerWorks: 登录
标有星(*)号的字段是必填字段。
保持登录。
单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件。
在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。
所有提交的信息确保安全。
选择您的昵称
当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。昵称长度在 3 至 31 个字符之间。
您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。
标有星(*)号的字段是必填字段。
(昵称长度在 3 至 31 个字符之间)
单击提交则表示您同意developerWorks 的条款和条件。 .
所有提交的信息确保安全。
文章、教程、演示,帮助您构建、部署和管理云应用。
立即加入来自 IBM 的专业 IT 社交网络。
为灾难恢复构建应用,赢取现金大奖。
static.content.url=/developerworks/js/artrating/SITE_ID=10Zone=AIX and UNIX, LinuxArticleID=337162ArticleTitle=从脚本编写到面向对象的 Python 编程publish-date=您已经赞过此文了。
Python的神奇方法指南
原作者:Rafe Kettler
发表时间:浏览量:20709评论数:1挑错数:0
有关Python内编写类的各种技巧和方法(构建和初始化、重载操作符、类描述、属性访问控制、自定义序列、反射机制、可调用对象、上下文管理、构建描述符对象、Pickling)。你可以把它当作一个教程,进阶,或者使用参考;我希望它能够成为一份针对Python方法的用户友好指南。
构建和初始化
使操作符在自定义类内工作
描述你的类
属性访问控制
制作自定义序列
可调用对象
上下文管理
构建描述符对象
Pickling你的对象
附录:如何调用神奇方法
这份指南是几个月内最有价值的Blog投稿精华。它的主题是向大家讲述Python中的神奇方法。
何为神奇方法呢?它们是面向Python中的一切,是一些特殊的方法允许在自己的定义类中定义增加“神奇”的功能。它们总是使用双下划线(比如__init__或__lt__),但它们的文档没有很好地把它们表现出来。所有这些神奇方法都出现在Python的官方文档中,但内容相对分散,组织结构也显得松散。还有你会难以发现一个实例(虽然他们被设计很棒,在语言参考中被详细描述,可之后就会伴随着枯燥的语法描述等)。
所以,为了解决我认为在Python文档中的一大败笔,我打算用更多纯英语,实例驱动的文档来说明Python的神奇方法。然后我就开始花了几周的时间来写blog,而现在我已经完成了它们,并将它们合订成一份指南。
我希望你喜欢它。把它当作一个教程,进阶,或者使用参考;我希望它能够成为一份针对Python方法的用户友好指南。
2.构建和初始化
相信大家都熟悉这个最基础的神奇方法__init__。它令你能自定义一个对象的初始化行为。而当我调用x=SomeClass()时,__init__并不是最先被调用的。实际上有一个叫做__new__的方法,事实上是它创建了实例,它传递任何参数给初始化程序来达到创建的目的。在对象生命周期结束时,调用__del__。让我们更近地观察下这3个神奇方法吧:
__new__(cls,[...)
一个对象的实例化时__new__是第一个被调用的方法。在类中传递其他任何参数到__init__。__new__很少被使用,这样做确实有其目的,特别是当一个子类继承一个不可改变的类型(一个元组或一个字符串)时。我不打算再继续深入追求__new__的细节了,因为这不会产生多大用处,因为在内已经涵盖了一份巨详细的说明了。
__init__(self,[...)
类的初始化。它会获得初始构建调用传过来的任何东西(举例来说就是,当我们调用x=SomeClass(10,'foo'),__init__就会把传过来的10和'foo'作为参数。__init__在Python的类定义中几乎普遍被使用)
__del__(self)
如果__new__和__init__是对象的构造器,那么__del__就是析构器。它不实现声明为del x(这样的代码不会解释成x.__del__())的行为。相反,它定义为当一个对象被垃圾回收时的行为。这可能对可能需要额外清理的对象相当有用,比如sockets或文件对象。但要小心,如果对象仍处于存活状态而当被解释退出时,__del__没有保证就会被执行,因此这样的__del__不能作为良好的编码规范的替代。(就像当你完成操作总是要关闭一次连接。但事实上,__del__几乎永远不会执行,就因为它处于不安全情况被调用了。使用时保持警惕!)
把上述这些内容合在一起,就成了一份__init__和__del__的实际使用用例:
from&os.path&import&join
class&FileObject:
&&&&'''对文件对象的包装,确保文件在关闭时得到删除'''
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&def&__init__(self, filepath='~', filename='sample.txt'):
&&&&&&&&# 按filepath,读写模式打开名为filename的文件
&&&&&&&&self.file=open(join(filepath,filename),&'r+')
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&def&__del__(self):
&&&&&&&&self.file.close()
&&&&&&&&del&self.file
3.使操作符在自定义类内工作
使用Python神奇方法的优势之一就是它提供了一种简单的方式能让对象的行为像内建类型。这意味着你可以避免用丑陋,反直觉和非标准方法执行基本运算。在某些语言中,通常会这样做:
if&instance.equals(other_instance):
&&&&# do something
你也应该在Python确实会这样做,但同时它会增加用户的疑惑以及不必要的冗长。不同的库可能会对相同的运算采用不同的命名,这使得用户比平常干了更多的事。依靠神奇方法的力量,你可以定义一个方法(比如__eq__),然后带代替我们真实的意图:
if&instance&==&other_instance:
&&&&# do something
现在你看到的是神奇方法力量的一部分。绝大多数都允许我们定义为运算符本身的意义,当用在我们自己定义的类上就像它们是内建类型。
3.1 神奇方法——比较
Python有一整套神奇方法被设计用来通过操作符实现对象间直观的比较,而非别扭的方法调用。它们同样提供了一套覆盖Python对象比较的默认行为(通过引用)。以下是这些方法的列表以及做法:
__cmp__(self, other)
__cmp__是神奇方法中最基础的一个。实际上它实现所有比较操作符行为(<,==,!=,等),但它有可能不按你想要的方法工作(例如,一个实例是否等于另一个这取决于比较的准则,以及一个实例是否大于其他的这也取决于其他的准则)。如果self
other,则返回正整数。它通常是最好的定义,而不需要你一次就全定义好它们,但当你需要用类似的准则进行所有的比较时,__cmp__会是一个很好的方式,帮你节省重复性和提高明确度。
__eq__(self, other)
定义了相等操作符,==的行为。
__ne__(self, other)
定义了不相等操作符,!=的行为。
__lt__(self, other)
定义了小于操作符,<的行为。
__gt__(self, other)
定义了大于操作符,>的行为。
__le__(self, other)
定义了小于等于操作符,<=的行为。
__ge__(self, other)
定义了大于等于操作符,>=的行为。
举一个例子,设想对单词进行类定义。我们可能希望能够按内部对string的默认比较行为,即字典序(通过字母)来比较单词,也希望能够基于某些其他的准则,像是长度或音节数。在本例中,我们通过单词长度排序,以下给出实现:
class&Word(str):
&&&&'''单词类,比较定义是基于单词长度的'''
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&def&__new__(cls, word):
&&&&&&&&# 注意,我们使用了__new__,这是因为str是一个不可变类型,
&&&&&&&&# 所以我们必须更早地初始化它(在创建时)
&&&&&&&&if&' '&in&word:
&&&&&&&&&&&&print&"单词内含有空格,截断到第一部分"
&&&&&&&&&&&&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)
现在,我们可以创建2个单词(通过Word('foo')和Word('bar'))并基于它们的长度进行比较了。注意,我们没有定义__eq__ 和 __ne__。这是因为这可能导致某些怪异的行为(特别是当比较Word('foo') == Word('bar')将会得到True的结果)。基于单词长度的相等比较会令人摸不清头脑,因此我们就沿用了str本身的相等比较的实现。
现在可能是一个好时机来提醒你一下,你不必重载每一个比较相关的神奇方法来获得各种比较。标准库已经友好地为我们在模板functools中提供了一个装饰(decorator)类,定义了所有比较方法。你可以只重载__eq__和一个其他的方法(比如__gt__,__lt__,等)。这个特性只在Python2.7(后?)适用,但当你有机会的话应该尝试一下,它会为你省下大量的时间和麻烦。你可以通过在你自己的重载方法在加上@total_ordering来使用。
3.2 神奇方法——数字
就像你可以通过重载比较操作符的途径来创建你自己的类实例,你同样可以重载数字操作符。系好你们的安全带,朋友们,还有很多呢。处于本文组织的需要,我会把数字的神奇方法分割成5块:一元操作符,常规算术操作符,反射算术操作符,增量赋值,类型转换。
一元操作符
一元运算和函数仅有一个操作数,比如负数,绝对值等
__pos__(self)
实现一元正数的行为(如:+some_object)
__neg__(self)
实现负数的行为(如: -some_object)
__abs__(self)
实现内建abs()函数的行为
__invert__(self)
实现用~操作符进行的取反行为。你可以参考Wiki:bitwise operations来解释这个运算符究竟会干什么
常规算术操作符
现在我们涵盖了基本的二元运算符:+,-,*等等。其中大部分都是不言自明的。
__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__(self, other)
实现乘方,使用**操作符
__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)
实现传统除法,用/操作符
__rturediv__(self, other)
实现真实除法,注意,只有当你from __future__ import division时才会有效
__rmod__(self, other)
实现反射求模,用%操作符
__rdivmod__(self, other)
实现内置函数divmod()的长除行为,当调用divmod(other,self)时被调用
__rpow__(self, other)
实现反射乘方,用**操作符
__rlshift__(self, other)
实现反射的左按位位移,使用<<操作符
__rrshift__(self, other)
实现反射的右按位位移,使用>>操作符
__rand__(self, other)
实现反射的按位与,使用&操作符
__ror__(self, other)
实现反射的按位或,使用|操作符
__rxor__(self, other)
实现反射的按位异或,使用^操作符
Python也有各种各样的神奇方法允许用户自定义增量赋值行为。你可能已经熟悉增量赋值,它结合了“常规的”操作符和赋值。如果你仍不明白我在说什么,下面有一个例子:
x&+=&1&# 等价 x = x + 1
这些方法都不会有返回值,因为赋值在Python中不会有任何返回值。反而它们只是改变类的状态。列表如下:
__iadd__(self, other)
实现加法和赋值
__isub__(self, other)
实现减法和赋值
__imul__(self, other)
实现乘法和赋值
__ifloordiv__(self, other)
实现地板除和赋值,用//=操作符
__idiv__(self, other)
实现传统除法和赋值,用/=操作符
__iturediv__(self, other)
实现真实除法和赋值,注意,只有当你from __future__ import division时才会有效
__imod__(self, other)
实现求模和赋值,用%=操作符
__ipow__(self, other)
实现乘方和赋值,用**=操作符
__ilshift__(self, other)
实现左按位位移和赋值,使用<<=操作符
__irshift__(self, other)
实现右按位位移和赋值,使用>>=操作符
__iand__(self, other)
实现按位与和赋值,使用&=操作符
__ior__(self, other)
实现按位或和赋值,使用|=操作符
__ixor__(self, other)
实现按位异或和赋值,使用^=操作符
类型转换的神奇方法
Python也有一组神奇方法被设计用来实现内置类型转换函数的行为,如float()
__int__(self)
实现到int的类型转换
__long__(self)
实现到long的类型转换
__float__(self)
实现到float的类型转换
__complex__(self)
实现到复数的类型转换
__oct__(self)
实现到8进制的类型转换
__hex__(self)
实现到16进制的类型转换
__index__(self)
实现一个当对象被切片到int的类型转换。如果你自定义了一个数值类型,考虑到它可能被切片,所以你应该重载__index__
__trunc__(self)
当math.trunc(self)被调用时调用。__trunc__应当返回一个整型的截断,(通常是long)
__coerce__(self, other)
该方法用来实现混合模式的算术。如果类型转换不可能那__coerce__应当返回None。否则,它应当返回一对包含self和other(2元组),且调整到具有相同的类型
4.描述你的类
用一个字符串来说明一个类这通常是有用的。在Python中提供了一些方法让你可以在你自己的类中自定义内建函数返回你的类行为的描述。
__str__(self)
当你定义的类中一个实例调用了str(),用于给它定义行为
__repr__(self)
当你定义的类中一个实例调用了repr(),用于给它定义行为。str()和repr()主要的区别在于它的阅读对象。repr()产生的输出主要为计算机可读(在很多情况下,这甚至可能是一些有效的Python代码),而str()则是为了让人类可读。
__unicode__(self)
当你定义的类中一个实例调用了unicode(),用于给它定义行为。unicode()像是str(),只不过它返回一个unicode字符串。警惕!如果用户用你的类中的一个实例调用了str(),而你仅定义了__unicode__(),那它是不会工作的。以防万一,你应当总是定义好__str__(),哪怕用户不会使用unicode
__hash__(self)
当你定义的类中一个实例调用了hash(),用于给它定义行为。它必须返回一个整型,而且它的结果是用于来在字典中作为快速键比对。
__nonzero__(self)
当你定义的类中一个实例调用了bool(),用于给它定义行为。返回True或False,取决于你是否考虑一个实例是True或False的。
我们已经相当漂亮地干完了神奇方法无聊的部分(无示例),至此我们已经讨论了一些基础的神奇方法,是时候让我们向高级话题移动了。
5.属性访问控制
有许多从其他语言阵营转到Python来的人抱怨Python对类缺乏真正的封装(比如,没有办法自定义private属性,已经给出public的getter和setter)。这可不是真相哟:Python通过神奇的方法实现了大量的封装,而不是通过明确的方法或字段修饰符。请看:
__getattr__(self, name)
你可以为用户在试图访问不存在(不论是存在或尚未建立)的类属性时定义其行为。这对捕捉和重定向常见的拼写错误,给出使用属性警告是有用的(只要你愿意,你仍旧可选计算,返回那个属性)或抛出一个AttributeError异常。这个方法只适用于访问一个不存在的属性,所以,这不算一个真正封装的解决之道。
__setattr__(self, name, value)
不像__getattr__,__setattr__是一个封装的解决方案。它允许你为一个属性赋值时候的行为,不论这个属性是否存在。这意味着你可以给属性值的任意变化自定义规则。然而,你需要在意的是你要小心使用__setattr__,在稍后的列表中会作为例子给出。
__delattr__
这等价于__setattr__,但是作为删除类属性而不是set它们。它需要相同的预防措施,就像__setattr__,防止无限递归(当在__delattr__中调用del self.name会引起无限递归)。
__getattribute__(self, name)
__getattribute__良好地适合它的同伴们__setattr__和__delattr__。可我却不建议你使用它。__getattribute__只能在新式类中使用(在Python的最新版本中,所有的类都是新式类,在稍旧的版本中你可以通过继承object类来创建一个新式类。它允许你定规则,在任何时候不管一个类属性的值那时候是否可访问的。)它会因为他的同伴中的出错连坐受到某些无限递归问题的困扰(这时你可以通过调用基类的__getattribute__方法来防止发生)。当__getattribute__被实现而又只调用了该方法如果__getattribute__被显式调用或抛出一个AttributeError异常,同时也主要避免了对__getattr__的依赖。这个方法可以使用(毕竟,这是你自己的选择),不过我不推荐它是因为它有一个小小的用例(虽说比较少见,但我们需要特殊行为以获取一个值而不是赋值)以及它真的很难做到实现0bug。
你可以很容易地在你自定义任何类属性访问方法时引发一个问题。参考这个例子:
def&__setattr__(self, name, value):
&&&&self.name&=&value
&&&&# 当每次给一个类属性赋值时,会调用__setattr__(),这就形成了递归
&&&&# 因为它真正的含义是 self.__setattr__('name', value)
&&&&# 所以这方法不停地调用它自己,变成了一个无法退出的递归最终引发crash
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
def&__setattr__(self, name, value):
&&&&self.__dict__[name]&=&value&# 给字典中的name赋值
&&&&# 在此自定义行为
再一次,Python的神奇方法向我们展示了其难以置信的能力,同时巨大的力量也伴随着重大的责任。重要的是让你明白正确使用神奇方法,这样你就不会破坏其他代码。
那么,我们在关于定制类属性访问中学习了什么?不要轻易地使用,事实上它过于强大以及反直觉。这也是它为何存在的理由:Python寻求干坏事的可能性,但会把它们弄得很难。自由是至高无上的,所以你可以做任何你想做的事情。以下是一个关于特殊属性访问方法的实际例子(注意,我们使用super因为并非所有类都有__dict__类属性):
class&AccessCounter:
&&&&'''一个类包含一个值和实现了一个访问计数器。
&&&&当值每次发生变化时,计数器+1'''
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&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)
&&&&&&&&# Make this unconditional.
&&&&&&&&# 如果你想阻止其他属性被创建,抛出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)
6.制作自定义序列
很有多种方式可以让你的类表现得像内建序列(字典,元组,列表,字符串等)。这些是我迄今为止最喜欢的神奇方法了,因为不合理的控制它们赋予了你一种魔术般地让你的类实例整个全局函数数组漂亮工作的方式。在我们开始讲解这个内容之前,让我们先快速理清需求。
现在我们正在谈论如何创建你自己的序列。也是什么谈一谈protocol了。protocol在某些地方跟接口很相似。接口在其他语言中,是一组给定的方法,而你必须定义它们。然而,在Python中protocol是完全非正式的,而且不要求显式声明去实现。更进一步说,它们更像是准则。
为何我们现在要谈论protocol?因为在Python中要实现自定义容器类型会涉及使用到这其中某些protocol。首先,有一个protocol是为定义不可变容器的:为了制作一个不可变容器,你只需要定义__len__和__getitem__(稍后详述)。可变容器protocol要求所有不可变容器增加__setitem__和__delitem__。然后,如果你希望你的对象是可迭代的,那你还得定义一个会返回迭代器iterator的__iter__方法。并且这个迭代器必须遵守一个迭代protocol,也就是要求迭代器有回调方法__iter__(返回自身)和next。
隐藏在容器背后的魔法
已经迫不及待了?以下便是容器使用的神奇魔法:
__len__(self)
返回容器的长度。部分protocol同时支持可变和不可变容器
__getitem__(self, key)
定义当某一个item被访问时的行为,使用self[key]表示法。这个同样也是部分可变和不可变容器protocol。这也可抛出适当的异常:TypeError 当key的类型错误,或没有值对应Key时。
__setitem__(self, key, value)
定义当某一个item被赋值时候的行为,使用self[key]=value表示法。这也是部分可变和不可变容器protocol。再一次重申,你应当在适当之处抛出KeyError和TypeError异常。
__delitem__(self, key)
定义当某一个item被删除(例如 del self[key])时的行为。这仅是部分可变容器的protocol。在一个无效key被使用后,你必须抛出一个合适的异常。
__iter__(self)
应该给容器返回一个迭代器。迭代器会返回若干内容,大多使用内建函数iter()表示。当一个容器使用形如for x in container:的循环。迭代器本身就是其对象,同时也要定义好一个__iter__方法来返回自身。
__reversed__(self)
当定义调用内建函数reversed()时的行为。应该返回一个反向版本的列表。
__contains__(self, item)
__contains__为成员关系,用in和not in测试时定义行为。那你会问这个为何不是一个序列的protocol的一部分?这是因为当__contains__未定义,Python就会遍历序列,如果遇到正在寻找的item就会返回True。
__concat__(self, other)
最后,你可通过__concat__定义你的序列和另外一个序列的连接。应该从self和other返回一个新构建的序列。当调用2个序列时__concat__涉及操作符+
在我们的例子中,让我们看一下一个list实现的某些基础功能性的构建。可能会让你想起你使用的其他语言(比如Haskell)。
class&FunctionalList:
&&&&'''类覆盖了一个list的某些额外的功能性魔法,像head,
&&&&tail,init,last,drop,and 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):
&&&&&&&&# 如果key是非法的类型和值,那么list valuse会抛出异常
&&&&&&&&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(last):
&&&&&&&&# 获得最后一个元素
&&&&&&&&return&self.values[-1]
&&&&def&drop(self, n):
&&&&&&&&# 获得除前n个元素的序列
&&&&&&&&return&self.values[n:]
&&&&def&take(self, n):
&&&&&&&&# 获得前n个元素
&&&&&&&&return&self.values[:n]
通过这个(轻量的)有用的例子你知道了如何实现你自己的序列。当然,还有很多更有用的应用,但是它们其中的很多已经被标准库实现了,像Counter, OrderedDict, NamedTuple
你也可以通过定义神奇方法来控制如何反射使用内建函数isinstance()和issubclass()的行为。这些神奇方法是:
__instancecheck__(self, instance)
检查一个实例是否是你定义类中的一个实例(比如,isinstance(instance, class))
__subclasscheck__(self, subclass)
检查一个类是否是你定义类的子类(比如,issubclass(subclass, class))
这对于神奇方法的用例情况来说可能较小,可它的确是真的。我并不想花费太多的时间在反射方法上面,因为他们不是那么地重要。不过它们反映了在Python中关于面对对象编程一些重要的东西,而且在Python中的普遍:总是在找一种简单的方式来做某些事情,即使它能被用到的不多。这些神奇方法似乎看上去不那么有用,但当你需要使用它们的时候你会感激它们的存在(和你阅读的这本指南!)。
8.可调用对象
正如你可能已经知道,在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
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&#省略...
9.上下文管理
在Python2.5里引入了一个新关键字(with)使得一个新方法得到了代码复用。上下文管理这个概念在Python中早已不是新鲜事了(之前它作为库的一部分被实现过),但直到PEP343(http://www.python.org/dev/peps/pep-0343/)才作为第一个类语言结构取得了重要地位而被接受。你有可能早就已经见识过with声明:
with&open('foo.txt') as bar:
&&&&# 对bar执行某些动作
上下文管理允许对对象进行设置和清理动作,用with声明进行已经封装的操作。上下文操作的行为取决于2个神奇方法:
__enter__(self)
定义块用with声明创建出来时上下文管理应该在块开始做什么。注意,__enter__的返回值必须绑定with声明的目标,或是as后面的名称。
__exit__(self, &exception_type, exception_value, traceback)
定义在块执行(或终止)之后上下文管理应该做什么。它可以用来处理异常,进行清理,或行动处于块之后某些总是被立即处理的事。如果块执行成功的话,excepteion_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&# 绑定目标
&&&&&&&&&&&&&&&&&&&
&&&&def&__exit__(self, exception_type, exception_val, trace):
&&&&&&&&try:
&&&&&&&&&&&&self.obj.close()
&&&&&&&&except&AttributeError:&#obj不具备close
&&&&&&&&&&&&print&'Not closable.'
&&&&&&&&&&&&return&True&# 成功处理异常
以下是一个对于Closer实际应用的一个例子,使用一个FTP连接进行的演示(一个可关闭的套接字):
>>>&from&magicmethods&import&Closer
>>>&from&ftplib&import&:;;
>>> with Closer(FTP('')) as conn:
...&&&& conn.dir()
# 省略的输出
>>> conn.dir()
# 一个很长的AttributeError消息, 不能关闭使用的一个连接
>>> with Closer(int(5)) as i:
...&&&& i&+=&1
Not closeable.
瞧见我们如何漂亮地封装处理正确或不正确的用例了吗?那就是上下文管理和神奇方法的威力。
10.构建描述符对象
描述符可以改变其他对象,也可以是访问类中任一的getting,setting,deleting。描述符不意味着孤立;相反,它们意味着会被它们的所有者类控制。当建立面向对象数据库或那些拥有相互依赖的属性的类时,描述符是有用的。当描述符在几个不同单元或描述计算属性时显得更为有用。
作为一个描述符,一个类必须至少实现__get__,__set__,和__delete__中的一个。让我们快点看一下这些神奇方法吧:
__get__(self, instance, owner)
当描述符的值被取回时定义其行为。instance是owner对象的一个实例,owner是所有类。
__set__(self, instance, value)
当描述符的值被改变时定义其行为。instance是owner对象的一个实例,value是设置的描述符的值
__delete__(self, instance)
当描述符的值被删除时定义其行为。instance是owner对象的一个实例。
现在,有一个有用的描述符应用例子:单位转换策略
class&Meter(object):
&&&&'''米描述符'''
&&&&&&&&&&&&
&&&&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):
&&&&'''英尺描述符'''
&&&&&&&&&&&&&&&&
&&&&def&__get__(self, instance, owner):
&&&&&&&&return&instance.meter&*&3.2808
&&&&def&__set__(self, instance, value):
&&&&&&&&instance.meter&=&float(value)&/&3.2808
&&&&&&&&&&&&
class&Distance(object):
&&&&'''表示距离的类,控制2个描述符:feet和meters'''
&&&&meter&=&Meter()
&&&&foot&=&Foot()
11.Pickling你的对象
假如你花时间和其他Pythonistas打交道,那么你至少有可能听到过Pickling这个词。Pickling是一种对Python数据结构的序列化过程。如果你需要存储一个对象,之后再取回它(通常是为了缓存)那么它就显得格外地有用了。同时,它也是产生忧虑和困惑的主要来源。
Pickling是那么地重要以至于它不仅有自己专属的模块(pickle),还有自己的protocol和神奇方法与其相伴。但首先用简要的文字来解释下如何pickle已经存在的类型(如果你已经懂了可以随意跳过这部分内容)
Pickling:盐水中的快速浸泡
让我们跳入pickling。话说你有一个词典你想要保存它并在稍后取回。你可以把它的内容写到一个文件中去,需要非常小心地确保你写了正确的语法,然后用exec()或处理文件的输入取回写入的内容。但这是不稳定的:如果你你在纯文本中保存重要的数据,它有可能被几种方法改变,导致你的程序crash或在你的计算机上运行了恶意代码而出错。于是,我们准备pickle它:
import&pickle
data&=&{'foo': [1,2,3],
&&&&&&&&'bar': ('Hello','world!'),
&&&&&&&&'baz':&True}
jar&=&open('data.pk1',&'wb')
pickle.dump(data, jar)&# 把pickled数据写入jar文件
jar.close()
好了现在,已经过去了几个小时。我们希望拿回数据,而我们需要做的事仅仅是unpickle它:
import&pickle
pk1_file&=&open('data.pk1','rb')&#连接pickled数据
data&=&pickle.load(pk1_file)&#把数据load到一个变量中去
print&data
pk1_file.close()
发生了什么事?正如你的预期,我们获得了data。
现在,我要给你一些忠告:pickling并非完美。Pickle文件很容易因意外或出于故意行为而被损毁。Pickling可能比起使用纯文本文件安全些,但它仍旧有可能会被用来跑恶意代码。还有因为Python版本的不兼容问题,所以不要期望发布Pickled对象,也不要期望人们能够打开它们。但是,它依然是一个强大的缓存工具和其他常见序列化任务。
Pickling你自定义的对象
Pickling不仅可用在内建类型上,还可以用于遵守pickle协议的任何类。pickle协议有4个可选方法用于定制Python对象如何运行(这跟C扩展有点不同,但那不在我们讨论的范围内):
__getinitargs__(self)
如果你想当你的类unpickled时调用__init__,那你可以定义__getinitargs__,该方法应该返回一个元组的参数,然后你可以把他传递给__init__。注意,该方法仅适用于旧式类。
__getnewargs__(self)
对于新式类,你可以影响有哪些参数会被传递到__new__进行unpickling。该方法同样应该返回一个元组参数,然后能传递给__new__
__getstate__(self)
代替对象的__dict__属性被保存。当对象pickled,你可返回一个自定义的状态被保存。当对象unpickled时,这个状态将会被__setstate__使用。
__setstate__(self, state)
对象unpickled时,如果__setstate__定义对象状态会传递来代替直接用对象的__dict__属性。这正好跟__getstate__手牵手:当二者都被定义了,你可以描述对象的pickled状态,任何你想要的。
一个例子:
我们的例子是Slate类,它会记忆它曾经的值和已经写入的值。然而,当这特殊的slate每一次pickle都会被清空:当前值不会被保存。
import&time
class&Slate:
&&&&'''存储一个字符串和一个变更log,当Pickle时会忘记它的值'''
&&&&def&__init__(self, value):
&&&&&&&&self.value&=&value
&&&&&&&&self.last_change&=&time.asctime()
&&&&&&&&self.history&=&{}
&&&&def&change(self, new_value):
&&&&&&&&# 改变值,提交最后的值到历史记录
&&&&&&&&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&'%st %s'&%&(k, v)
&&&&def&__getstate__(self):
&&&&&&&&# 故意不返回self.value 或 self.last_change.
&&&&&&&&# 当unpickle,我们希望有一块空白的"slate"
&&&&&&&&return&self.history
&&&&def&__setstate__(self, state):
&&&&&&&&# 让 self.history = state 和 last_change 和 value被定义
&&&&&&&&self.history&=&state
&&&&&&&&self.value,&self.last_change&=&None,&None
这份指南的目标就是任何人读一读它,不管读者们是否具备Python或面对对象的编程经验。如果你正准备学习Python,那你已经获得了编写功能丰富,优雅,易用的类的宝贵知识。如果你是一名中级Python程序员,你有可能已经拾起了一些新概念和策略和一些好的方法来减少你和你的用户编写的代码量。如果你是一名Pythonista专家,你可能已经回顾了某些你可能已经被你遗忘的知识点,或着你又学习到了一些新技巧。不管你的的经验等级,我希望这次Python神奇方法的旅程达到了真正神奇的效果。(我无法控制自己在最后不用个双关语)
附录:如果调用神奇方法
Python中的一些神奇方法直接映射到内建函数;在这种情况下,调用它们的方法是相当明显的。然而,在其他情况下,那些调用方法就不这么明显了。本附录致力于揭开能够引导神奇方法被调用的非明显语法。
__new__(cls [,...])
instance = MyClass(arg1, arg2)
__new__ 在创建实例的时候被调用
__init__(self [,...])
instance = MyClass(arg1, arg2)
__init__ 在创建实例的时候被调用
__cmp__(self, other)
self == other, self > other, 等
在比较的时候调用
__pos__(self)
一元加运算符
__neg__(self)
一元减运算符
__invert__(self)
取反运算符
__index__(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)
访问任何属性时
__getitem__(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)
希望这张表格可以帮你扫清你有关语法涉及到神奇方法的问题。
来自无觅插件
很直译。。。
(C) 2015 yeeyan.org
公司全称:北京译言协力传媒科技有限公司
京ICP备号&&京公网安备99号

我要回帖

更多关于 理发师悖论 解决 的文章

 

随机推荐