Python高手之路(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-19 15:10:12

点击下载

作者:[法] 朱利安?丹乔(Julien Danjou)

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Python高手之路

Python高手之路试读:

前言

如果你读到这里,你肯定已经使用Python有一阵子了。你可能是通过一些文档学习的,钻研了一些已有的项目或者从头开发,但不管是哪种情况,你都已经在以自己的方式学习它了。直到两年前我加入OpenStack项目组之前,这其实也正是我个人熟悉Python的方法。

在此之前,我只是开发过一些“车库项目1”级别的Python库或应用程序,而一旦你参与开发涉及数百名开发人员并有着上万个用户的软件或库时,情况就会有所不同。OpenStack平台有超过150万行Python代码,所有代码都需要精确、高效,并根据用户对云计算应用程序的需求进行任意扩展。在有了这一规模的项目之后,类似测试和文档这类问题就一定需要自动化,否则根本无法完成。

我刚开始加入OpenStack的时候,我认为自己已经掌握了不少Python知识,但这两年,在我起步时无法想象其规模的这样一个项目上,我学到了更多。而且我还有幸结识了很多业界最棒的Python黑客,并从他们身上获益良多—大到通用架构和设计准则,小到各种有用的经验和技巧。通过本书,我想分享一些我所学到的最重要的东西,以便你能构建更好的Python应用,并且是更加高效地构建。

1作者这里的意思是规模很小,比较业余的项目。——译者注第 1 章 项目开始1.1 Python版本

你很可能会问的第一个问题就是:“我的软件应该支持Python的哪些版本?”这是一个好问题,因为每个Python新版本都会在引入新功能的同时弃用一些老的功能。而且,Python 2.x和Python 3.x之间有着巨大的不同,这两个分支之间的剧烈变化导致很难使代码同时兼容它们。本书后面章节会进一步讨论,而且当刚刚开始一个新项目时很难说哪个版本更合适。

2.5及更老的版本目前基本已经废弃了,所以不需要再去支持它们。如果实在想支持这些更老的版本,要知道再让程序支持Python 3.x会更加困难。如果你确实有可能会遇到一些安装了Python 2.5的老系统,那真没什么好办法。

2.6版本在某些比较老的操作系统上仍然在用,如Red Hat企业版Linux(Red Hat Enterprise Linux)。同时支持Python 2.6版本和更新的版本并不太难,但是,如果你认为自己的程序不太可能会在2.6版本上运行,那就没必要强迫自己支持它。

2.7版本目前是也将仍然是Python 2.x的最后一个版本。将其作为主要版本或主要版本之一来支持是正确的选择,因为目前仍然有很多软件、库和开发人员在使用它。Python 2.7将被继续支持到2020年左右,所以它很可能不会很快消失。

3.0、3.1和3.2版本在发布后都被快速地更替,并没有被广泛采用。如果你的代码已经支持了2.7版本,那么再支持这几个版本的意义并不大。

3.3和3.4版本都是Python 3最近发行的两个版本,也是应该重点支持的版本。Python 3.3和3.4代表着这门语言的未来,所以除非正专注于兼容老的版本,否则都应该先确保代码能够运行在这两个最新的版本上。

总之,在确实有需要的情况下支持2.6版本(或者想自我挑战),必须支持2.7版本,如果需要保证软件在可预见的未来也能运行,就需要也支持3.3及更高的版本。忽略那些更老的Python版本基本没什么问题,尽管同时支持所有这些版本是有可能的:CherryPy项目(http://cherrypy.org)支持Python 2.3及所有后续版本(http://docs.cherrypy.org/stable/intro/install.html)。

编写同时支持Python 2.7和3.3版本的程序的技术将在第13章介绍。某些技术在后续的示例代码中也会涉及,所有本书中的示例代码都同时支持这两个主要版本。1.2 项目结构

项目结构应该保持简单,审慎地使用包和层次结构,过深的层次结构在目录导航时将如同梦魇,但过平的层次结构则会让项目变得臃肿。

一个常犯的错误是将单元测试放在包目录的外面。这些测试实际上应该被包含在软件的子一级包中,以便:● 避免被setuptools(或者其他打包的库)作为tests顶层模块自动

安装;● 能够被安装,且其他包能够利用它们构建自己的单元测试。

图1-1展示了一个项目的标准的文件层次结构。图1-1 标准的包目录结构

setup.py是Python安装脚本的标准名称。在安装时,它会通过Python分发工具(distuils)进行包的安装。也可以通过README.rst(或者README.txt,或者其他合适的名字)为用户提供重要信息。requirements.txt应该包含Python包所需要的依赖包,也就是说,所有这些包都会预先通过pip这样的工具进行安装以保证你的包能正常工作。还可以包含test-requirements.txt,它应该列出运行测试集所需要的依赖包。最后,docs文件夹应该包括reStructuredText格式的文档,以便能够被Sphinx处理(参见3.1节)。

包中还经常需要包含一些额外的数据,如图片、shell脚本等。不过,关于这类文件如何存放并没有一个统一的标准。因此放到任何觉得合适的地方都可以。

下面这些顶层目录也比较常见。● etc用来存放配置文件的样例。● tools用来存放与工具有关的shell脚本。● bin用来存放将被setup.py安装的二进制脚本。● data用来存放其他类型的文件,如媒体文件。

一个常见的设计问题是根据将要存储的代码的类型来创建文件或模块。使用functions.py或者exceptions.py这样的文件是很糟糕的方式。这种方式对代码的组织毫无帮助,只能让读代码的人在多个文件之间毫无理由地来回切换。

此外,应该避免创建那种只有一个__init__.py文件的目录,例如,如果hooks.py够用的话就不要创建hooks/__init__.py。如果创建目录,那么其中就应该包含属于这一分类/模块的多个Python文件。1.3 版本编号

可能你已经有所了解,Python生态系统中正在对包的元数据进行标准化。其中的一项元数据就是版本号。

PEP 440(http://www.python.org/dev/peps/pep-0440/)针对所有的Python包引入了一种版本格式,并且在理论上所有的应用程序都应该使用这种格式。这样,其他的应用程序或包就能简单而可靠地识别它们需要哪一个版本的包。

PEP440中定义版本号应该遵从以下正则表达式的格式:

N[.N]+[{a|b|c|rc}N][.postN][.devN]

它允许类似1.2或1.2.3这样的格式,但需注意以下几点。● 1.2等于1.2.0,1.3.4等于1.3.4.0,以此类推。● 与N[.N]+相匹配的版本被认为是最终版本。● 基于日期的版本(如2013.06.22)被认为是无效的。针对

PEP440格式版本号设计的一些自动化工具,在检测到版本号大

于或等于1980时就会抛出错误。

最终即将发布的组件也可以使用下面这种格式。● N[.N]+aN(如1.2a1)表示一个alpha版本,即此版本不稳定或缺

少某些功能。● N[.N]+bN(如2.3.1b2)表示一个beta版本,即此版本功能已经完

整,但可能仍有bug。● N[.N]+cN或N[.N]+rcN(如0.4rc1)表示候选版本(常缩写为

RC),通常指除非有重大的bug,否则很可能成为产品的最终发

行版本。尽管rc和c两个后缀含义相同,但如果二者同时使用,

rc版本通常表示比c更新一点。

通常用到的还有以下这些后缀。● .postN(如1.4.post2)表示一个后续版本。通常用来解决发行过

程中的细小问题(如发行文档有错)。如果发行的是bug修复版

本,则不应该使用.postN而应该增加小的版本号。● .devN(如2.3.4.dev3)表示一个开发版本。因为难以解析,所以

这个后缀并不建议使用。它表示这是一个质量基本合格的发布前

的版本,例如,2.3.4.dev3表示2.3.4版本的第三个开发版本,它

早于任何的alpha版本、beta版本、候选版本和最终版本。

这一结构可以满足大部分常见的使用场景。注意  你可能已经听说过语义版本(http://semver.org/),

它对于版本号提出了自己的规则。这一规范和PEP 440部分

重合,但二者并不完全兼容。例如,语义版本对于预发布版

本使用的格式1.0.0.-alpha+001就与PEP 440不兼容。

如果需要处理更高级的版本号,可以考虑一下PEP 426(http://www.python.org/dev/peps/pep-0426)中定义的源码标签,这一字段可以用来处理任何版本字符串,并生成同PEP要求一致的版本号。

许多分布式版本控制系统(Distributed Version Control System,DVCS)平台,如Git和Mercurial,都可以使用唯一标识的散列字符串1作为版本号。但遗憾的是,它不能与PEP 440中定义的模式兼容:问题就在于,唯一标识的散列字符串不能排序。不过,是有可能通过源码标签这个字段维护一个版本号,并利用它构造一个同PEP 440兼容的版本号的。

提示  pbr(即Python Build Reasonableness,https://

pypi.python.org/pypi/pbr)将在4.2节中讨论,它可以基于项

目的Git版本自动生成版本号。

1对于Git,指的是git-describe(1)。1.4 编码风格与自动检查

没错,编码风格是一个不太讨巧的话题,不过这里仍然要聊一下。

Python具有其他语言少有的绝佳质量1:使用缩进来定义代码块。乍一看,似乎它解决了一个由来已久的“往哪里放大括号?”的问题,然而,它又带来了“如何缩进?”这个新问题。

而Python社区则利用他们的无穷智慧,提出了编写Python代码的PEP 82(http://www.python.org/dev/peps/pep-0008/)标准。这些规范可以归纳成下面的内容。● 每个缩进层级使用4个空格。● 每行最多79个字符。● 顶层的函数或类的定义之间空两行。● 采用ASCII或UTF-8编码文件。● 在文件顶端,注释和文档说明之下,每行每条import语句只导入

一个模块,同时要按标准库、第三方库和本地库的导入顺序进行

分组。● 在小括号、中括号、大括号之间或者逗号之前没有额外的空格。● 类的命名采用骆驼命名法,如CamelCase;异常的定义使用

Error前缀(如适用的话);函数的命名使用小写字符,如

separated_by_underscores;用下划线开头定义私有的属性或方

法,如_private。

这些规范其实很容易遵守,而且实际上很合理。大部分程序员在按照这些规范写代码时并没有什么不便。

然而,犯错在所难免,保持代码符合PEP 8规范的要求仍是一件麻烦事。工具pep8(https://pypi.python.org/pypi/pep8)就是用来解决这个问题的,它能自动检查Python文件是否符合PEP 8要求,如示例1.1所示。

示例1.1 运行pep8$ pep8 hello.py hello.py:4:1: E302 expected 2 blank lines, found 1 $ echo $? 1

pep8会显示在哪行哪里违反了PEP 8,并为每个问题给出其错误码。如果违反了那些必须遵守的规范,则会报出错误(以E开头的错误码),如果是细小的问题则会报警告(以W开头的错误码)。跟在字母后面的三位数字则指出具体的错误或警告,可以从错误码的百位数看出问题的大概类别。例如,以E2开头的错误通常与空格有关,以E3开头的错误则与空行有关,而以W6开头的警告则表明使用了已废弃的功能。

社区仍然在争论对并非标准库一部分的代码进行PEP 8验证是否是一种好的实践。这里建议还是考虑一下,最好能定期用PEP 8验证工具对代码进行检测。一种简单的方式就是将其集成到测试集中。尽管这似乎有点儿极端,但这能保证代码一直遵守PEP 8规范。6.7节中将介绍如何将pep8与tox集成,从而让这些检查自动化。

OpenStack项目从一开始就通过自动检查强制遵守PEP 8规范。尽管有时候这让新手比较抓狂,但这让整个代码库的每一部分都保持一致,要知道现在它有167万行代码。对于任何规模的项目这都是非常重要的,因为即使对于空白的顺序,不同的程序员也会有不同的意见。

也可以使用--ignore选项忽略某些特定的错误或警告,如示例1.2所示。

示例1.2 运行pep8时指定--ignore选项$ pep8 --ignore=E3 hello.py $ echo $? 0

这可以有效地忽略那些不想遵循的PEP 8标准。如果使用pep8对已有的代码库进行检查,这也可以暂时忽略某些问题,从而每次只专注解决一类问题。注意  如果正在写一些针对Python的C语言代码(如模

块),则PEP 7(http://www.python.org/dev/peps/pep-0007/)

标准描述了应该遵循的相应的编码风格。

还有一些其他的工具能够检查真正的编码错误而非风格问题。下面是一些比较知名的工具。● pyflakes(https://launchpad.net/pyflakes),它支持插件。● pylint(https://pypi.python.org/pypi/pylint),它支持PEP 8,默认

可以执行更多检查,并且支持插件。

这些工具都是利用静态分析技术,也就是说,解析代码并分析代码而无需运行。

如果选择使用pyflakes,要注意它按自己的规则检查而非按PEP 8,所以仍然需要运行pep8。为了简化操作,一个名为flake8(https://pypi.python.org/pypi/flake8)的项目将pyflakes和pep8合并成了一个命令,而且加入了一些新的功能,如忽略带有#noqa的行以及通过入口点(entry point)进行扩展。

为了追求优美而统一的代码,OpenStack选择使用flake8进行代码检查。不过随着时间的推移,社区的开发者们已经开始利用flake8的可扩展性对提交的代码进行更多潜在问题的检查。最终flake8的这个扩展被命名为hacking(https://pypi.python.org/pypi/hacking)。它可以检查except语句的错误使用、Python 2与Python 3的兼容性问题、导入风格、危险的字符串格式化及可能的本地化问题。

如果你正开始一个新项目,这里强烈建议使用上述工具之一对代码的质量和风格进行自动检查。如果已经有了代码库,那么一种比较好的方式是先关闭大部分警告,然后每次只解决一类问题。

尽管没有一种工具能够完美地满足每个项目或者每个人的喜好,但flake8和hacking的结合使用是持续改进代码质量的良好方式。要是没想好其他的,那么这是一个向此目标前进的好的开始。

提示  许多文本编辑器,包括流行的GNU Emacs(http://

www.gnu.org/software/emacs/)和vim(http://

www.vim.org/),都有能够直接对代码运行pep8和flake8这类

工具的插件(如Flymake),能够交互地突出显示代码中任

何不兼容PEP 8规范的部分。这种方式能够非常方便地在代

码编写过程中修正大部分风格错误。

1你可能有不同意见。

2 PEP 8 Style Guide for Python Code, 5th July 2001, Guido van Rossum, Barry Warsaw, Nick Coghlan第 2 章 模块和库2.1 导入系统

要使用模块和库,需要先进行导入。

Python之禅>>> import this The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!

导入系统是相当复杂的,不过你可能已经了解了一些基本知识。这里会介绍一些关于这一子系统的内部机理。

sys模块包含许多关于Python导入系统的信息。首先,当前可导入的模块列表都是通过sys.moudle变量才可以使用的。它是一个字典,其中键(key)是模块名字,对应的值(value)是模块对象。>>> sys.modules['os']

许多模块是内置的,这些内置的模块在sys.builtin_module_names中列出。内置模块可以根据传入Python构建系统的编译选项的不同而变化。

导入模块时,Python会依赖一个路径列表。这个列表存储在sys.path变量中,并且告诉Python去哪里搜索要加载的模块。可以在代码中修改这个列表,根据需要添加或删除路径,也可以通过编写Python代码直接修改环境变量PYTHONPATH。下面的方法几乎是相等的1。>>> import sys >>> sys.path.append('/foo/bar')$ PYTHONPATH=/foo/bar python >>> import sys >>> '/foo/bar' in sys.path True

在sys.path中顺序很重要,因为需要遍历这个列表来寻找请求的模块。

也可以通过自定义的导入器(importer)对导入机制进行扩展。Hy2正是利用的这种技术告诉Python如何导入其他非标准的.py或者.pyc文件的。

顾名思义,导入钩子机制是由PEP 302(http://www.python.org/dev/peps/pep-0302/)定义的3。它允许扩展标准的导入机制,并对其进行预处理,也可以通过追加一个工厂类到sys.path_hooks来添加自定义的模块查找器(finder)。

模块查找器对象必须有一个返回加载器对象的find_module(fullname, path=None)方法,这个加载器对象必须包含一个负责从源文件中加载模块的load_module(fullname)方法。

为了进一步说明,下面给出了Hy利用自定义的导入器导入.hy而不是.py结尾的源文件的方法,见示例2.1。

示例2.1 Hy模块导入器class MetaImporter(object): def find_on_path(self, fullname): fls = ["%s/__init__.hy", "%s.hy"] dirpath = "/".join(fullname.split(".")) for pth in sys.path: pth = os.path.abspath(pth) for fp in fls: composed_path = fp % ("%s/%s" % (pth, dirpath)) if os.path.exists(composed_path): return composed_path def find_module(self, fullname, path=None): path = self.find_on_path(fullname) if path: return MetaLoader(path) sys.meta_path.append(MetaImporter())

一旦路径被确定是有效的且指向了一个模块,就会返回一个MetaLoader对象。

Hy模块加载器class MetaLoader(object): def __init__(self, path): self.path = path def is_package(self, fullname): dirpath = "/".join(fullname.split(".")) for pth in sys.path: pth = os.path.abspath(pth) composed_path = "%s/%s/__init__.hy" % (pth, dirpath) if os.path.exists(composed_path): return True return False def load_module(self, fullname): if fullname in sys.modules: return sys.modules[fullname] if not self.path: return sys.modules[fullname] = None mod = import_file_to_module(fullname, self.path) ispkg = self.is_package(fullname) mod.__file__ = self.path mod.__loader__ = self mod.__name__ = fullname if ispkg: mod.__path__ = [] mod.__package__ = fullname else: mod.__package__ = fullname.rpartition('.')[0] sys.modules[fullname] = mod return mod

i mport_file_to_module读取一个Hy源文件,将其编译成Python代码,并返回一个Python模块对象。

uprefix模块(https://pypi.python.org/pypi/uprefix)是这个功能起作用的另一个好的例子。Python 3.0到3.2并没有像Python 2中用来表示Unicode字符串的u前缀4,这个模块通过在编译前删除字符串的前缀u 来确保在2.x和3.x之间的兼容性。

1说几乎是因为路径并不会被放在列表的同一级上,尽管根据你的使用情况它可能并不重要。

2 Hy是Python上的Lisp实现,会在9.1节介绍。

3在Python 2.3版本实现的新的带入钩子机制。

4它在Python 3.3中又被加了回来。2.2 标准库

Python本身内置的巨大标准库提供了丰富的工具和功能,可以满足你能想到的任何需求。很多Python的初学者习惯于自己写代码实现一些基本的功能,然后会惊奇地发现很多功能已经内置了,直接就可以使用。

任何时候想要自己写函数处理一些简单的工作时,请停下来先看看标准库。我的建议是至少大概浏览一遍标准库,这样下次再需要一个函数时就能知道是否可以利用标准库中已有的函数了。

后续章节会讨论其中的一些模块,如functools和itertools,下面是一些必须了解的标准库模块。● atexit允许注册在程序退出时调用的函数。● argparse提供解析命令行参数的函数。● bisect为可排序列表提供二分查找算法(参见10.3节)。● calendar提供一组与日期相关的函数。● codecs提供编解码数据的函数。● collections提供一组有用的数据结构。● copy提供复制数据的函数。● csv提供用于读写CSV文件的函数。● datetime提供用于处理日期和时间的类。● fnmatch提供用于匹配Unix风格文件名模式的函数。● glob提供用于匹配Unix风格路径模式的函数。● io提供用于处理I/O流的函数。在Python3中,它还包含

StringIO(在Python 2中有同名的模块),可以像处理文件一样处

理字符串。● json提供用来读写JSON格式数据的函数。● logging提供对Python内置的日志功能的访问。● multiprocessing可以在应用程序中运行多个子进程,而且提供

API让这些子进程看上去像线程一样。● operator提供实现基本的Python运算符功能的函数,可以使用这

些函数而不是自己写lambda表达式(参见8.3节)。● os提供对基本的操作系统函数的访问。● random提供生成伪随机数的函数。● re提供正则表达式功能。● select提供对函数select()和poll()的访问,用于创建事件循环。● shutil提供对高级文件处理函数的访问。● signal提供用于处理POSIX信号的函数。● tempfile提供用于创建临时文件和目录的函数。● threading提供对处理高级线程功能的访问。● urllib(以及Python 2.x中的urllib2和urlparse)提供处理和解析

URL的函数。● uuid可以生成全局唯一标识符(Universally Unique Identifiers,

UUID)。

这个模块清单可以作为一个快速参考,帮助你了解各个库模块的作用。如果能记住一部分就更好了。花在查找标准库上的时间越少,意味着写实际代码的时间就越多。

提示  整个标准库都是用Python写的,所以可以直接查看

它模块和函数的源代码。有疑问时只需打开代码自己一探究

竟。尽管文档中已经包含了你想知道的一切,但总还是有机

会让你学一些有用的东西。2.3 外部库

你是否有过这样的经历,收到一件不错的生日礼物或圣诞礼物,但是打开后却发现送你的人忘了买电池?Python的“内置电池”哲学让你作为程序员不会遇到这类问题,只要安装了Python,就拥有了完成任何功能所需的一切条件。

然而,Python标准库的开发者并不能预测你要实现的“任何”功能到底是什么。即使可以,大多数人也不想去处理一个几个GB的文件下载,即使可能只是需要写一个重命名文件的快速脚本。关键在于,即使拥有所有的扩展功能,仍然有许多功能是Python标准库没有涵盖的。不过,这并不是说有些事情是根本无法用Python实现的,这只是表明有些事情可能需要使用外部库。

Python标准库是安全且范围明确的:模块文档化程度很高,并且有足够多的人在经常使用它,从而可以保证在你想使用它时肯定不会遇到麻烦。而且,就算万一出了问题,也能确保在短时间内有人解决。但是,外部库就像是地图上标着“熊出没,请注意”的部分:可能缺少文档,功能有bug,更新较少或根本不更新。任何正式的项目都可能用到一些只有外部库提供的功能,但是需要谨记使用这些外部库可能带来的风险。

下面是来自一线的案例。OpenStack使用了SQLAlchemy(http://www.sqlalchemy.org/),一个Python数据库开发工具包。如果了解SQL的话会知道,数据库的结构是会发生变化的,所以OpenStack还使用了sqlalchemy-migrate(https://code.google.com/p/sqlalchemy-migrate/)来处理数据库模式的升级。一切运行良好,直到有一天它们不行了,开始出现大量bug,并且没有好转的迹象。而且,OpenStack在当时是想要支持Python 3的,然而没有任何迹象表明sqlalchemy-migrate要支持Python 3。因此,显然sqlalchemy-migrate已经死了,我们需要切换到其他替代方案。截止到作者写作时,OpenStack正准备升级到Alembic(https://pypi.python.org/pypi/alembic),虽然也有一些工作要做,但好在不是那么痛苦。

所有这些引出一个重要的问题:“如何保证我不会掉进同样的陷阱里?”很遗憾,没办法保证。程序员也是人,没什么办法可以确保目前维护良好的库在几个月后仍然维护良好。但是,在OpenStack中我们使用下列检查表来根据需要给出建议(我建议你也这么做)。● Python 3兼容。尽管现在你可能并不准备支持Python 3,但很可

能早晚会涉及,所以确认选择的库是Python 3兼容的并且承诺保

持兼容是明智的。● 开发活跃。GitHub(http://github.com)和Ohloh(http://

www.ohloh.net/)通常提供足够的信息来判断一个库是否有维护

者仍然在工作。● 维护活跃。尽管一个库可能是“结束”状态(即功能完备,不会

再加入新功能),但应该有维护者仍然在工作,以确保没有

bug。可以通过查看项目的跟踪系统来看维护者对bug的反应是

否迅速。● 与各个操作系统发行版打包在一起。如果一个库被打包在主流的

Linux发行版内,说明有其他项目依赖它,所以,如果真有什么

问题,至少你不是唯一一个抱怨的。如果打算公开发布你的软件,

那么这项检查也是很有用的。因为如果软件的依赖已经在终端用

户的机器上安装了,显然分发你的软件会更容易。● API兼容保证。没有比你的软件因为一个它依赖的库发生了变化

而使整个API崩溃更糟的了。你一定很想知道选择的库在过去是

否发生过类似的事件。

尽管可能工作量巨大,但这一检查表对于依赖同样适用。如果知道应用程序会大量依赖一个特定的库,那么至少应该对这个库的每一个依赖使用这个检查表。

不管最终使用哪个库,都要像其他工具一样对待,因为即使是有用的工具也可能会造成严重的损害。尽管不常发生,但问问你自己:如果你有一把锤子,你会拿着它满屋跑因而可能意外地损坏屋子里的东西,还是会把它放在工具架上或者车库里,远离那些贵重而易碎的东西,仅在需要的时候才拿出来?

对于外部库道理是一样的,不管它们多么有用,都需要注意避免让这些库和实际的源代码耦合过于紧密。否则,如果出了问题,你就需要切换库,这很可能需要重写大量的代码。更好的办法是写自己的API,用一个包装器对外部库进行封装,将其与自己的源代码隔离。自己的程序无需知道用了什么外部库,只要知道API提供了哪些功能即可。想要换一个不同的库?只需要修改包装器就可以了。只要它仍然提供同样的功能,那么完全不需要修改任何核心代码。也许会有例外,但应该不会太多。大部分库都被设计成只专注解决一定范围的问题,因此很容易隔离。

4.7.3节将会涉及如何使用入口点构建驱动系统(driver system),这个系统让你可以将项目的某些部分设计成可以根据需要切换的模块。2.4 框架

有许多不同的Python框架可用于开发不同的Python应用。如果是Web应用,可以使用Django(https://www.djangoproject.com/)、Pylons(http://www.pylonsproject.org/)、TurboGears(http://turbogears.org/)、Tornado(http://www.tornadoweb.org/)、Zope(http://www.zope.org/)或者Plone(http://plone.org/)。如果你正在找事件驱动的框架,可以使用Twisted(http://twistedmatrix.com/)或者Circuits(https://bitbucket.org/prologic/circuits/)等。

框架和外部库的主要不同在于,应用程序是建立在框架之上的,代码对框架进行扩展而不是反过来。而外部库更像是对代码的扩展,赋予你的代码更多额外的能力,而框架会为你的代码搭好架子,只需要通过某种方式完善这个架子就行了,尽管这可能是把双刃剑。使用框架有很多好处,如快速构建原型并开发,但也有一些明显的缺点,如锁定(lock-in)问题。因此,在决定使用某个框架前需要把这些都考虑在内。

这里推荐的为Python应用选择框架的方法很大程度上类似于前面介绍过的外部库的选择方法,适用于框架是通过一组Python库来进行分发的情况。有时它们还包含用于创建、运行以及部署应用的工具,但这并不影响你采用的标准。前面已经提到过,在已经写了大量代码之后更换外部库是十分痛苦的,但更换框架比这还要难受一千倍,因为通常需要完全重写你的应用程序。举例说明,前面提及的Twisted框架还不能完全支持Python 3。如果你基于Twisted的程序在几年之后想要支持Python 3,那么你将非常不幸,除非全部重写代码选用另一个框架或者有人最终为Twisted提供了Python 3的升级支持。

有些框架与其他框架相比更加轻量级。一个简单的比较就是,Django提供了内置的ORM功能,而Flask则没有。一个框架提供的功能越少,将来遇到问题的越少。然而,框架缺少的每个功能同时也是另一个需要去解决的问题,要么自己写,要么再千挑万选去找另一个能提供这个功能的库。愿意处理哪种场景取决于个人的选择,但需慎重选择。当问题出现时从一个框架升级至其他框架是极其艰巨的任务,就算Python再强大,对于这类问题也没有什么好办法。2.5 Doug Hellmann访谈

我曾经有幸和Doug Hellmann一起工作过数月。他在DreamHost是一位非常资深的软件开发工程师,同时他也是OpenStack项目的贡献者。他发起过关于Python的网站Python Module of the Week(http://pymotw.com/),也出版过一本很有名的Pyhton书The Python Standard Library By Example(http://doughellmann.com/python-standard-library-by-example),同时他也是Python的核心开发人员。我曾经咨询过Doug关于标准库以及库的设计与应用等方面的问题。

当你从头开发一个Python应用时,如何迈出第一步呢?它和开发一个已有的应用程序有什么不同?

从抽象角度看步骤都差不多,但是细节上有所不同。相对于对比开发新项目和已有项目,我个人在对应用程序和库开发的处理方式上有更多的不同。

当我要修改已有代码时,特别是这些代码是其他人创建的时,起初我需要研究代码是如何工作的,我需要改进哪些代码。我可能会添加日志或是输出语句,或是用pdb,利用测试数据运行应用程序,以便我理解它是如何工作的。我经常会做一些修改并测试它们,并在每次提交代码前添加可能的自动化测试。

创建一个新应用时,我会采取相同的逐步探索方法。我先创建一些代码,然后手动运行它们,在这个功能可以基本调通后,再编写测试用例确保我已经覆盖了所有的边界情况。创建测试用例也可以让代码重构更容易。

这正是smiley(https://pypi.python.org/pypi/smiley)的情况。在开发正式应用程序前,我先尝试用Python的trace API写一些临时脚本。对于smiley我最初的设想包括一个仪表盘并从另一个运行的应用程序收集数据,另一部分用来接收通过网络发送过来的数据并将其保存。在添加几个不同的报告功能的过程中,我意识到重放已收集的数据的过程和在一开始收集数据的过程基本是一样的。于是我重构了一些类,并针对数据收集,数据库访问和报告生成器创建了基类。通过让这些类遵循同样的API使我可以很容易地创建数据收集应用的一个版本,它可以直接将数据写入数据库而无需通过网络发送数据。

当设计一个应用程序时,我会考虑用户界面是如何工作的,但对于库,我会专注于开发人员如何使用其API。通过先写测试代码而不是库代码,可以让思考如何通过这个新库开发应用程序变得更容易一点儿。我通常会以测试的方式创建一系列示例程序,然后依照其工作方式去构建这个库。

我还发现,在写任何库的代码之前先写文档让我可以全面考虑功能和流程的使用,而不需要提交任何实现的细节。它还让我可以记录对于设计我所做出的选择,以便读者不仅可以理解如何使用这个库,还可以了解在创建它时我的期望是什么。这就是我用在stevedore上的方法。

我知道我想让stevedore能够提供一组类用来管理应用程序的插件。在设计阶段,我花了些时间思考我见过的使用插件的通用模式,并且写了几页粗略的文档描述这些类应该如何使用。我意识到,如果我在类的构造函数中放最复杂的参数,方法map()几乎是可互换的。这些设计笔记直接写进了stevedore官方文档的简介里,用来解释在应用程序中使用插件的不同模式和准则。

将一个模块加入Python标准库的流程是什么?

完整的流程和规范可以在Python Developer's Guide(http://docs.python.org/devguide/stdlibchanges.html)中找到。

一个模块在被加入Python标准库之前,需要被证明是稳定且广泛使用的。模块需要提供的功能要么是很难正确实现的,要么是非常有用以至于许多开发人员已经创建了他们自己不同的变种。API应该非常清晰并且它的实现不能依赖任何标准库之外的库。

提议一个新模块的第一步是在社区通过python-ideas邮件列表非正式地了解一下大家对此的感兴趣程度。如果回应很积极,下一步就是创建一个Python增强提案(PythonEnhancement Proposal,PEP),它包括添加这个模块的动因,以及如何过渡的一些实现细节。

因为包的管理和发现工作已经非常稳定了,尤其是pip和Python Package Index(PyPI),因此在标准库之外维护一个新的库可能更实用。单独的发布使得对于新功能和bug修复(bugfix)的更新可以更频繁,对于处理新技术或API的库来说这尤其重要。

标准库中的哪三个模块是你最想人们深入了解并开始使用的?

最近我做了许多关于应用程序中动态加载扩展方面的工作。我使用abc模块为那些作为抽象基类进行的扩展定义API,以帮助扩展的作者们了解API的哪些方法是必需的,哪些是可选的。抽象基类已经在其他一些语言中内置了,但我发现很多Python程序员并不知道Python也有。

bisect模块中的二分查找算法是个很好的例子,一个广泛使用但不容易正确实现的功能,因此它非常适合放到标准库中。我特别喜欢它可以搜索稀疏列表,且搜索的值可能并不在其中。

collections模块中有许多有用的数据结构并没有得到广泛使用。我喜欢用namedtuple来创建一些小的像类一样的数据结构来保存数据但并不需要任何关联逻辑。如果之后需要添加逻辑的话,可以很容易将namedtuple转换成一个普通的类,因为namedtuple支持通过名字访问属性。另一个有意思的数据结构是ChainMap,它可以生成良好的层级命名空间。ChainMap能够用来为模板解析创建上下文或者通过清晰的流程定义来管理不同来源的配置。

许多项目(包括OpenStack)或者外部库,会在标准库之上封装一层自己的抽象。例如,我特别想了解对于日期/时间的处理。对此你有什么建议吗?程序员应该坚持使用标准库,还是应该写他们自己的函数,切换到其他外部库或是开始给Python提交补丁?

所有这些都可以。我倾向于避免重复造轮子,所以我强烈主张贡献补丁和改进那些能够用来作为依赖的项目。但是,有时创建另外的抽象并单独维护代码也是合理的,不管在应用程序内还是作为一个新的库。

你提到的例子中,OpenStack里的timeutils模块就是对Python的datetime模块的一层很薄的封装。大部分功能都简短且简单,但通过将这些最常见的操作封装为一个模块,我们可以保证它们在OpenStack项目中以一致的方式进行处理。因为许多函数都是应用相关的,某种意义上它们强化了一些问题决策,例如,字符串时间戳格式或者“现在”意味着什么,它们不太适合作为Python标准库的补丁或者作为一个通用库发布以及被其他项目采用。

与之相反,我目前正致力于将OpenStack的API服务项目从早期创建时使用的WSGI框架转成采用一个第三方Web开发框架。在Python中开发WSGI应用有很多选择,并且当我们可能需要增强其中一个以便其可以完全适应OpenStack API服务器的需要时,将这些可重用的修改贡献对于维护一个“私有的”框架似乎更可取。

当从标准库或其他地方导入并使用大量模块时,关于该做什么你有什么特别的建议吗?

我没有什么硬性限制,但是如果我有过多的导入时,我会重新考虑这个模块的设计并考虑将其拆到一个包中。与上层模块或者应用程序模块相比,对底层模块的这种拆分可能会发生得更快,因为对于上层模块我期望将更多片段组织在一起。

关于Python 3,有什么模块是值得一提而且能令开发人员有兴趣深入了解的?

支持Python 3的第三方库的数量已经到了决定性的时刻。针对Python 3开发新库或应用程序从未如此简单过,而且幸亏有3.3中加入的兼容性功能使同时维护对Python 2.7的支持也很容易。主要的Linux发行版正在致力于将Python 3默认安装。任何人要用Python创建新项目都应该认真考虑对Python 3的支持,除非有尚未移植的依赖。目前来说,不能运行在Python 3上的库基本会被视为“不再维护”。

许多开发人员将所有的代码都写入到应用程序中,但有些情况下可能有必要将代码封装成一个库。关于设计、规划、迁移等,做这些最好的方式是什么?

应用程序就是“胶水代码”的集合用来将库组织在一起完成特定目的。起初设计时可以将这些功能实现为一个库,然后在构建应用程序时确保库的代码能够很好地组织到逻辑单元中,这会让测试变得更简单。这还意味着应用程序的功能可以通过库进行访问,并且能够被重新组合以构建其他应用程序。未能采用这种方法的话意味着应用程序的功能和用户界面的绑定过于紧密,导致很难修改和重用。

对于计划开始构建自己的Python库的人们有什么样的建议呢?

我通常建议自顶向下设计库和API,对每一层应用单一职责原则(Single Responsibility Principle,SRP)(http://en.wikipedia.org/wiki/Single_responsibility_principle)这样的设计准则。考虑调用者如何使用这个库,并创建一个API去支持这些功能。考虑什么值可以存在一个实例中被方法使用,以及每个方法每次都要传入哪些值。最后,考虑实现以及是否底层的代码的组织应该不同于公共API。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载