Skip to content Skip to main navigation Skip to footer

Python: 当你从头开发一个Python应用时,如何迈出第一步呢?

本文出自《Python高手之路》中的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。

SQLAlchemy是应用这些原则的绝好例子。声明式ORM、数据映射和表达式生成层都是单独的。开发人员可以自行决定对于API访问的正确的抽象程度,并基于他们的需求而不是被库的设计强加的约束去使用这个库。

当你随机看Python程序员的代码时遇到的最常见的编程错误是什么?

Python的习惯用法和其他语言的一个较大的不同在于循环和迭代。例如,我见过的最常见的反模式是使用for循环过滤一个列表并将元素加入到一个新的列表中,然后再在第二个循环中处理这个结果(可能将列表作为参数传给一个函数)。我通常建议将过滤循环改成生成器表达式,因为生成器表达式,更有效也更容易理解。列表的组合也很常见,以便它们的内容可以以某种方式一起被处理,但却没有使用itertools.chain()。

还有一些我在代码评审时给出的更细小的建议,例如,使用dict()而不是长的if:then:else块作为查找表,确保函数总是返回相同的类型(如一个空列表而不是None),通过使用元组和新类将相关的值合并到一个对象中从而减少函数的参数,以及在公共API中定义要使用的类而不是依赖于字典。

有没有关于选择了一个“错误”的依赖的具体的例子是你亲身经历或目睹过的?

最近,我有个例子,pyparsing(https://pypi.python.org/pypi/pyparsing)的一个新发布取消了对Python 2的支持,这给我正在维护的一个库带来了一点儿小麻烦。对pyparsing的更新是个重大的修改,而且是明确标识成这样的,但是因为我没有在对cliff(https://pypi.python.org/pypi/cliff)的设置中限制依赖版本号,所以pyparsing的新发布给cliff的用户造成了问题。解决方案就是在cliff的依赖列表中对Python 2和Python 3提供不同的版本边界。这种情况突显了理解依赖管理和确保持续集成测试中适当的测试配置的重要性。

你怎么看待框架?

框架像任何工具类型一样。它们确实有帮助,但在选择框架时要特别谨慎,应确保它能够很好地完成当前的工作。

通过抽取公共部分到一个框架中,你可以将你的开发精力专注于应用中独特的方面。通过提供许多类似运行在开发模式或者写一个测试套件这样的引导代码,它们还可以帮你让一个应用程序迅速达到一个可用的状态而不是从头开发。它们还可以激励你在应用程序开发过程中保持一致,这意味着最终你的代码将更易于理解且更可重用。

虽然使用框架时还有其他一些潜在的缺点需要注意。决定使用某个特定框架通常能够反映应用程序本身的设计。如果设计的限制不能从根本上符合应用程序的需求,那么选择错误的框架会令应用的实现变得更难。如果你试着使用与框架建议不同的模式或惯用方式,你最终将不得不同框架做斗争。

长按二维码可以关注公众号

《Python高手之路》,点击阅读原文可实现购买。

阅读原文

原文:http://www.jianshu.com/p/6e1d130bc589

0 Comments

There are no comments yet

Leave a comment

Your email address will not be published.