书名:Python忍者秘籍
ISBN:978-7-115-53569-6
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 [美]科迪·杰克逊(Cody Jackson)
译 李俊毅
责任编辑 陈聪聪
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
Copyright © Packt Publishing 2018. First published in the English language under the title Secret Recipes of the Python Ninja.
All Rights Reserved.
本书由英国Packt Publishing公司授权人民邮电出版社有限公司出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。
本书主要介绍Python的基础安装和进阶操作。全书共有9章,介绍了Python模块,解释器,装饰器,collections,生成器、协同程序和并行处理,math模块,PyPy,增强方案以及LyX的相关使用。本书包含大量的代码示例可供读者参考并实践。
本书适合使用Python语言的算法工程师、后台工程师、测试工程师以及运维工程师阅读,也适合有一定编码基础的人员自学Python或了解进阶知识。
本书提供了一系列Python编程主题。Cody Jackson用易于理解的语言阐述了多个关于Python使用的相关主题,在这本书中,被称为Python秘籍。
前两章涵盖了Python生态系统和Python解释器的许多特性。这些内容不仅包括了语言的语法和句法,还阐述了软件的安装、包管理、维护和操作。这本书对于开发运维人员来说,十分有帮助。
在第3章、第4章和第6章中,Cody对Python语言和标准库进行了阐述,讲解了多种使用模块的方法。这些模块包括Python decorator(装饰器)、collections、math、random secrets以及statistics(统计)等,是Python的重要基础。
第5章介绍使用操作系统、线程模块以及更高级的多进程处理模块的底层并发性。这提供了许多用于提高性能的替代实现技术。第7章深入研究PyPy和RPython项目,以讲解创建高性能软件的其他方法。
Python增强方案(The Python Enhancement Proposal,PEP)是整个Python生态系统中一个重要的部分,由于PEP的存在,Python得到了广泛的应用。PEP过程是对Python语言本身以及其标准库的改变的评估和讨论。第8章解释了这个过程,帮助开发者理解改变是如何发生的,并且提供加入这个开源社区的背景知识。
除非提供有效并且可读的文档,否则一个项目不能被称为完整的项目。第9章介绍的Python生态系统有许多工具可用来创建包含代码的文档,像Sphinx、PyDoc和LyX这样的工具可以辅助创建一个有用而持久的产品。
本书的目标群体是那些对Python语言的语法和数据结构有一定了解的开发人员。开篇几章说明了深入理解Python相关的安装和操作等一系列背景知识的必要性。因此,本书是从事开发和运维工作的人员的理想选择。
此外,这本书的内容还在开发运维人员所掌握的基础知识上有所延伸。如果是从事质量工程的相关人员使用本书,可以搭配一本Python测试相关的书,会有意想不到的效果。
虽然关于Python语言和数据结构的图书有很多,但能帮助读者驾驭Python语言开发生态系统的图书却严重匮乏。
“忍者”让人想起不规则战术。比起大量的低阶代码,“秘籍”告诉我们在正确的时间应用正确的技术将会如何提升代码的质量、可用性以及性能。
Python“忍者”使用他们的秘密武器来高效地创造有价值的软件。Cody Jackson为我们揭示了这个秘密,帮助大家用Python更有效地工作。
史蒂芬·洛特(Steven F. Lott)
高级软件工程师,Python畅销书作者
科迪·杰克逊(Cody Jackson)是一位美国海军老兵,也是得克萨斯州圣安东尼奥的IT和商业管理咨询公司SoCuff的创始人。他在CACI国际公司担任建设性建模师。自1994年以来,他一直参与高科技产业的研发工作。在加入海军之前,他作为一名实验技术员在Gateway Computer CO.,Ltd工作。他在ECPI大学(ECPI University)担任计算机信息系统兼职教授。他自学Python,并且是Learning to Program Using Python系列丛书的作者。
我要感谢我的家人,感谢他们容忍我过去6个月的时间专注于写作,而忽略了他们。感谢吉多·范·罗苏姆(Guido van Rossum)创造了这样一种令人愉快的编程语言。感谢斯科特·汤普森(Scott Thompson)对本书进行了校对。感谢我的猫,它确保了我在写作时经常能得到放松。
——科迪·杰克逊
斯科特·汤普森先生目前作为一名ICS/SCADA安全工程师在CACI公司工作。他已经在美国海军从事工程控制系统方面工作超过26年。他在海军生涯中担任过电工、主推进助理和Oliver Hazard Perry级护卫舰总工程师。在退役之前,他在美国海军网络司令部工作。他拥有网络取证学(Cyber Forensics)硕士学位,曾从事事件响应、恶意软件分析、网络渗透测试、移动设备取证、Windows取证以及Linux和Python等方面的工作。
家庭一直是我事业的重要组成部分。这些年来他们为我做出了很大的牺牲,但正是他们的支持促成了我的成功。我要感谢Packt出版社和科迪·杰克逊让我成为这本书的技术审稿人。
——斯科特·汤普森
许多读者可能认为他们已经掌握了Python语言,并且知道编写利用该语言最佳特性的应用程序所需的一切。这本书的目的是深入研究Python中一些开发者从未体验过的相关技术。
本书将揭示Python中鲜为人知甚至让人有所误解的与标准库实现相关的内容,并提供对模块实际工作方式的理解。本书展示了集合和数学模块的正确实现,以及数字(如小数和分数)的相关内容,这将有助于读者拓展视野。在详细了解内部特殊方法之前,读者将了解装饰器、上下文管理器、协同程序和生成器函数等。本书探讨了CPython解释器,包括可以改变环境功能的命令选项,以及改进普通Python体验的可选交互式Shell。读者将浏览PyPy项目,在那里可以接触到几种新的方法来提高应用程序的速度和并发性。本书同样回顾了几种Python增强方案,以了解Python未来的发展趋势。最后,本书提供了编写Python代码文档的不同方法。
这本书是为那些想学习如何用新方法来改进应用程序性能的Python软件开发人员而写的。想要掌握这本书的知识,最好有一定的Python开发经验。
第1章,使用Python模块。介绍Python包、模块和名称空间,导入虚拟环境,并包装Python代码以进行使用。
第2章,使用Python解释器。探讨了Python命令行选项、定制交互式会话、在Windows操作系统上使用Python以及可选的Python交互式Shell。
第3章,使用装饰器。回顾Python函数,并说明如何用装饰器来改进它们。
第4章,使用Python collections。回顾容器并深入了解Python中可用的collections。
第5章,使用生成器、协同程序和并行处理。重点介绍Python中的迭代器以及它如何与生成器一起工作,然后介绍并发和并行处理。
第6章,使用Python的math模块。深入讲解Python是如何实现各种数学运算的。
第7章,使用PyPy提升Python性能。概述如何使用即时编译改进Python性能。
第8章,使用Python增强方案。讨论如何进行Python语言的改进,并介绍几个当前通用的方案。
第9章,使用LyX写文档。展示如何用不同的技术和工具来记录代码,撰写文档。
虽然本书的许多主题都是从初学者应该掌握的基本知识展开的,但是对Python有一定了解的读者读起来会更加容易。具体来说,本书假设读者具有使用交互式Python解释器和编写Python文件、导入模块以及面向对象编程工作的经验。
除非另有说明,否则本书均以Python 3.6为例。虽然简要地讨论了替代实现,但本书假定使用基本的CPython实现。
本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。
作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。
当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,单击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。
我们的联系邮箱是contact@epubit.com.cn。
如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。
如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。
如果您所在的学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。
如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。
“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。
“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。
异步社区
微信服务号
在本章中,我们将讨论Python模块,具体涉及以下内容。
Python模块是Python程序的最高级别组件。顾名思义,模块是模块化的,能够作为整体程序的一部分插入其他模块,从而结合起来,在创建紧密结合的应用程序时提供更好的代码分离。
模块使代码复用变得更加容易,并提供单独的命名空间,以防止代码块之间的变量阴影(variable shadowing)。variable shadowing涉及在不同的命名空间中重名的变量,而这可能导致解释器使用不正确的变量。开发人员创建的每个Python文件都被认为是一个单独的模块,允许将不同的文件导入形成最终应用程序的单个整体文件中。
实际上,任何Python文件都可以通过简单地删除“.py
”扩展名而成为一个模块,这在导入库时很常见。Python包是模块的集合,包的特殊之处在于包含了一个__init__.py
。稍后将详细介绍这些差异,现在只需知道相同的项目会有几个名称。
模块的一个关键点是它们产生单独的命名空间。命名空间(也称为范围)只是模块或组件的控制域。通常,模块内的对象在该模块外部不可见,也就是说,试图调用位于单独模块中的变量将产生错误。
命名空间也用于隔离同一程序中的对象。例如,函数内定义的变量只能在该函数运行时使用,试图从另一个函数调用该变量会导致错误。这就是为什么全局变量是可用的,它们可以被任何函数调用并相互作用。这也是全局变量不被看作最佳实践的原因,因为用户可能修改了全局变量而没有意识到这一点,从而在程序的后面部分造成中断。
命名空间基本上在内部起作用。如果在函数中调用变量,Python解释器将首先在该函数中查找变量的声明。如果它不在函数中,Python将向上移动堆栈并寻找全局定义的变量。如果还没有找到,Python将查看内置的库,这些库始终是可用的。如果仍未找到,Python将引发一个错误。在流程方面,它看起来像这样:局部范围→全局范围→内置模块→错误。
当导入模块时,对范围发现过程产生的一个微小变化,导入的模块也会检查对象调用。但需要注意的是,除非通过点命名法显式标识期望的对象,否则仍然会生成错误。
例如,如果希望生成0~1000的随机数,则不能只调用randint()
函数而不导入random
库。一旦导入了模块,公共可用的类、方法、函数和变量就可以通过使用<module_name>
和<object_name>
明确地调用它们。以下是一个例子。
>>>
randint(0, 1000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'randint' is not defined
>>> import random
>>> random.randint(0, 1000)
607
在前面的示例中,首先调用RANTIN()
。因为它不是Python内置函数的一部分,解释器对它一无所知,所以抛出了一个错误。
但是,在导入包含各种随机数生成函数的random
库之后,可以通过点命名法显式调用randint()
,即random.randint()
。这就告诉Python解释器在random
库中查找RANTIN()
,从而得到期望的结果。
更清楚地说,当将模块导入程序中时,Python会假定一些命名空间。如果执行正常导入,即import foo
,则主程序和foo
都保持它们各自的命名空间。要使用foo
模块中的函数,必须使用点命名法——以foo.bar()
明确地标识它。
另一方面,如果模块的一部分是从foo
导入(from foo impor bar
)的,那么导入的组件就成为主程序命名空间的一部分。如果所有组件都是使用通配符(from foo import *
)导入的,也会发生这种情况。
下面的示例显示了这些操作中的属性。
>>>
from random import randint
>>> randint(0, 10)
2
>>> randrange(0, 25)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'randrange' is not defined
在前面的示例中,来自random
库的randint()
函数由它自身导入,它将randint()
放入主程序的命名空间中。它允许直接调用randint()
,而不必将其定义为random.randint()
。但是,当尝试用randrange()
函数执行相同的操作时会发生错误,因为它没有被导入。
为了说明观点,我们将会创建一个嵌入式函数,这个函数将会被定义在一个封闭的函数中,并被其调用,步骤如下。
(1)nested_functions.py
包括一个嵌入式函数,并且以调用这个函数结尾。
>>>
def first_funct():
... x = 1
... print(x)
... def second_funct():
... x = 2
... print(x)
... second_funct()
...
(2)调用父函数,并检查结果。
>>>
first_funct()
1
2
(3)直接调用嵌入式函数,我们会收到一个错误。
>>>
second_funct()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'second_funct' is not defined
(4)为了和另一个模块兼容,我们导入需要的模块。
>>>
import math
(5)我们以这样的形式调用模块(module)中的sin()
函数。
>>>
math.sin(45)
0.8509035245341184
(6)尝试用下面的方式调用函数,不使用“.”来表明它所属的库会导致以下的错误。
>>>
sin(45)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'sin' is not defined
(7)以下的例子表明可以用“*”来代替函数所在的位置,来从一个模块中导入所有的函数。
>>>
from math import *
>>> sin(45)
0.8509035245341184
(8)将模块作为脚本运行的一种常见方法是直接从命令行显式调用模块,并在必要时提供所需参数。这可以通过配置模块接受命令行参数来设置,如下所示。
def print_funct(arg):
print(arg)
if __name__ == "__main__":
import sys
print_funct(sys.argv[1])
(9)print_mult_args.py
表明,如果超过一个参数需要被引用,并且值是已知的,则可以使用参数列表中各自的索引值指定每个参数。
def print_funct(arg1, arg2, arg3):
print(arg1, arg2, arg3)
if __name__ == "__main__":
import sys
print_funct(sys.argv[1], sys.argv[2], sys.argv[3])
(10)如果函数可以捕获多个参数,但数量未知,则可以使用*args
参数,如下所示。
>>>
def print_input(*args):
... for val, input in enumerate(args):
... print("{}. {}".format(val, input))
...
>>> print_input("spam", "spam", "eggs", "spam")
0.spam
1.spam
2.eggs
3.spam
代码中已命名的赋值的位置决定了其命名空间的可见性。在前面示例的步骤(1)~步骤(3)中,如果在调用first_funct()
之后立即直接调用second_funct()
,则会得到一个second_funct()
没有定义的错误。这是正常的,因为从全局作用域看,第二个函数不存在;它嵌套在第一个函数中,在第一个函数的作用域之外是看不到的。第一个函数中的所有内容都是它的命名空间的一部分,就像第二个函数中的x
值不能直接调用,必须使用second_funct()
调用才能获得它的值一样。
在前面示例的步骤(4)~步骤(7)中,math
模块被完整导入,但它保留自己的命名空间。因此,调用math.sin()
会提供一个结果,但是调用sin()
本身会导致一个错误。
然后,使用通配符导入math
模块。这告诉Python解释器将所有函数导入主命名空间,而不是将它们保存在单独的math
命名空间中。此后单独调用sin()
时,一切正常,会返回正常的结果。
这说明一点,在允许变量和函数使用相同名称的情况下,命名空间对保持代码分隔是非常重要的。通过使用点命名法,可以调用准确的对象,而不必担心命名阴影会导致错误的结果。
在前面的示例中,步骤(7)~步骤(10)中使用sys.argv()
允许Python解析命令行参数,并将它们放在一个列表中以供使用。sys.argv([0])
始终是接受参数的程序的名称,因此可以放心地忽略它。所有其他参数都存储在一个列表中,因此可以通过它们的索引值进行访问。
使用*args
告诉Python接受任意数量的参数,即允许程序接受不同数量的输入值。另外,**kwargs
也可以实现相同的效果,但是需要使用关键字:键值对。
除知道命名空间以外,在安装和使用这些模块的时候还有一些重要的术语需要知道,具体如下。
pip
是第三方模块的主要安装程序。从Python 3.4以后,pip
就已经被Python二进制安装包默认包括了。venv
一直是创建Python虚拟环境的主要工具。在Python 3.4中,它自动在所有虚拟环境中安装pip
和setuptools
。下面是dice_roller.py
的一部分,这是我在学习Python时编写的第一个Python程序中的嵌入式测试示例。
import random
def randomNumGen(choice):
if choice == 1: #d6 滚动
die = random.randint(1, 6)
elif choice == 2: #d10 滚动
die = random.randint(1, 10)
elif choice == 3: #d100 滚动
die = random.randint(1, 100)
elif choice == 4: #d4 滚动
die = random.randint(1, 4)
elif choice == 5: #d8 滚动
die = random.randint(1, 8)
elif choice == 6: #d12 滚动
die = random.randint(1, 12)
elif choice == 7: #d20 滚动
die = random.randint(1, 20)
else: # 简单错误信息
return "Shouldn't be here. Invalid choice"
return die
if __name__ == "__main__":
import sys
print(randomNumGen(int(sys.argv[1])))
在本例中,创建了一个模拟滚动的多面体骰子(通常用于角色扮演游戏)的随机数生成器。导入random
库,然后创建定义如何生成掷骰的函数。对于每一个骰子来说,设置的整数(输入的参数)表示该骰子有多少面。使用这种方法,可以输入单个整数模拟任意数量的可能值。
这个程序的关键部分在最后。if __name__ == "__main__"
部分告诉Python,它是主程序,而不是用来导入另一个程序中的。如果模块的命名空间是main
,解释器应该运行这行代码以及下面的代码。否则,在导入时,只有这行代码以上的代码对主程序是可用的(同样值得注意的是,这行代码对于与Windows操作系统的跨平台兼容性是必要的)。
当从命令行调用此程序时,将导入sys
库。然后,从命令行读取提供给程序的第一个参数,并将其作为参数传递给randomNumGen()
函数,结果被输出到屏幕上。以下是这个示例的部分运行结果。
$
python3 dice_roller.py 1
2
$ python3 dice_roller.py 2
10
$ python3 dice_roller.py 3
63
$ python3 dice_roller.py 4
2
$ python3 dice_roller.py 5
5
$ python3 dice_roller.py 6
6
$ python3 dice_roller.py 7
17
$ python3 dice_roller.py 8
Shouldn't be here. Invalid choice
以这种方式配置模块,便于用户在独立的基础上直接与模块交互。这也是在脚本上进行测试的好方法。测试仅在文件作为独立文件调用时运行,否则将忽略测试。dice_roller_tests.py
是作者编写的完整的骰子滚动模拟器程序。
import random #randint
def randomNumGen(choice):
"""获得随机数来模拟d6、d10和d100滚动"""
if choice == 1: #d6 滚动
die = random.randint(1, 6)
elif choice == 2: #d10 滚动
die = random.randint(1, 10)
elif choice == 3: #d100 滚动
die = random.randint(1, 100)
elif choice == 4: #d4 滚动
die = random.randint(1, 4)
elif choice == 5: #d8 滚动
die = random.randint(1, 8)
elif choice == 6: #d12 滚动
die = random.randint(1, 12)
elif choice == 7: #d20 滚动
die = random.randint(1, 20)
else: # 简单错误信息
return "Shouldn't be here. Invalid choice"
return die
def multiDie(dice_number, die_type):
"""将die加在一起,如2d6、4d10等"""
#---初始化变量
final_roll = 0
val = 0
while val < dice_number:
final_roll += randomNumGen(die_type)
val += 1
return final_roll
def test():
"""Test criteria to show script works."""
_1d6 = multiDie(1,1) #1d6
print("1d6 = ", _1d6, end=' ')
_2d6 = multiDie(2,1) #2d6
print("\n2d6 = ", _2d6, end=' ')
_3d6 = multiDie(3,1) #3d6
print("\n3d6 = ", _3d6, end=' ')
_4d6 = multiDie(4,1) #4d6
print("\n4d6 = ", _4d6, end=' ')
_1d10 = multiDie(1,2) #1d10
print("\n1d10 = ", _1d10, end=' ')
_2d10 = multiDie(2,2) #2d10
print("\n2d10 = ", _2d10, end=' ')
_3d10 = multiDie(2,2) #3d10
print("\n3d10 = ", _3d10, end=' ')
_d100 = multiDie(1,3) #d100
print("\n1d100 = ", _d100, end=' ')
if __name__ == "__main__": # 如果调用单独的程序可运行test()
test()
这个程序建立在以前的随机骰子程序的基础上,允许同时添加多个骰子。此外,test()
函数只在程序本身被调用以提供代码的完整性检查时才运行。如果测试函数不包含在其他代码的函数中可能会更好,因为在导入模块时仍然可以访问它,如下所示。
>>>
import dice_roller_tests.py
>>> dice_roller_tests.test()
1d6 = 1
2d6 = 8
3d6 = 10
4d6 = 12
1d10 = 5
2d10 = 8
3d10 = 6
1d100 = 26
因此,如果不希望某些代码在导入模块时被访问,请确保将其包含在代码行以下。
正如前面提到的,Python虚拟环境创建单独的Python环境,就像虚拟机可生成多个但独立的操作系统一样。在安装相同模块的多个实例时,Python虚拟环境特别有用。
假设我们正在处理一个项目,该项目需要特定库的1.2版本来提供遗留支持。现在下载了一个使用相同库的2.2版本的Python程序。如果在硬盘上的默认全局位置(例如/usr/lib/python3.6/site-packages)中安装新程序,新程序将把更新的库安装到相同的位置,从而覆盖遗留程序。由于使用旧库进行遗留支持,因此更新后的库很可能会破坏应用程序。
此外,在共享系统上(特别是如果没有管理员权限),很可能无法在系统上安装模块,至少无法在默认的全局站点包目录中安装模块。如果运气好,可以为账户安装软件,但如果不能,可以申请权限安装它,否则就没有办法安装了。
这就是Python虚拟环境发挥作用的地方。每个环境都有自己的安装目录,并且环境之间不共享库。这意味着即使更新全局库,不同环境中的每个模块版本也保持不变。这还意味着我们可以同时在计算机上安装多个版本的模块,而不会发生冲突。
虚拟环境也有自己的Shell,允许访问独立于其他任何环境或底层操作系统的Shell。本书还展示了如何通过pipenv
生成一个新的Python Shell。这样做可以确保所有命令都可以访问虚拟环境中安装的包。
以往的方式是使用venv
工具来管理虚拟环境。为了安装这个工具,可使用这条命令:sudo apt install python3-venv
。
为了以现代化的方式来管理虚拟环境,我们开发了pipenv
模块。它自动创建和管理项目的虚拟环境,以及在安装/卸载包时从Pipfile
中添加和删除包。可以用pip安装pipenv
。
Pipfile
代替原来的requirements.txt
,用于指定要包含在程序中的模块的精确版本。Pipfile
实际上包含两个单独的文件:Pipfile
和Pipfile.lock
(可选)。Pipfile
只导入模块的源位置、模块名称本身(默认为最新版本)和所需开发包的列表。下面的Pipfile.py
是来自Pipenv
站点的Pipfile
示例。
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
requests = "*"
[dev-packages]
pytest = "*"
Pipfile.lock
获取Pipfile
并为所有包设置实际的版本号,以及为这些文件标识特定的哈希值。哈希值有利于最小化安全风险,也就是说,如果一个特定的模块版本有漏洞,它的哈希值会让它很容易被识别,而不必通过版本名或其他方法进行搜索。下面的pipfile_lock.py
是一个来自Pipenv
站点的Pipfile
示例。
{
"_meta": {
"hash": {
"sha256":
"8d14434df45e0ef884d6c3f6e8048ba72335637a8631cc44792f52fd20b6f97a"
},
"host-environment-markers": {
"implementation_name": "cpython",
"implementation_version": "3.6.1",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "16.7.0",
"platform_system": "Darwin",
"platform_version": "Darwin Kernel Version 16.7.0: Thu Jun 15
17:36:27 PDT 2017; root:xnu-3789.70.16~2/RELEASE_X86_64",
"python_full_version": "3.6.1",
"python_version": "3.6",
"sys_platform": "darwin"
},
"pipfile-spec": 5,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"certifi": {
"hashes": [
"sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704",
"sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5"
],
"version": "==2017.7.27.1"
},
"chardet": {
"hashes": [
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
],
"version": "==3.0.4"
},
***further entries truncated***
创建虚拟环境的常规方法包括6个单独的步骤。
(1)创建虚拟环境。
>>>
python3 -m venv <dir_name>
(2)激活虚拟环境以使其可以使用。
>>>
source <dir_name>/bin/activate
(3)使用pip
安装必要的模块。
>>>
pip install <module>
(4)为了简化这个过程,pipenv
组合了pip
和venv
的调用,所以我们首先需要进入虚拟环境将会被放置的目录。
>>>
cd <project_name>
(5)简单地调用pipenv
来创建环境并安装需要的模块。
>>>
pipenv install <module>
(6)使用pipenv
来调用Shell
命令,并等待Shell被创建。请注意,我们已经创建了一个虚拟环境,并且命令提示符现在在该环境中被激活。图1.1包含了前面步骤中的命令。
图1.1
前面的pipenv
示例展示了开发人员进入项目所需的目录,然后调用pipenv
来同时创建、激活虚拟环境并安装所需模块的过程。
除了创建虚拟环境,如果我们创建了Python程序,还可以使用pipenv
来运行该程序。
>>>
pipenv run python3 <program_name>.py
这样做可以确保程序能使用虚拟环境中安装的所有包,从而降低出现意外错误的可能性。
当启动pipenv shell
时,将创建一个新的虚拟环境,并在文件系统中指明创建环境的位置。在本例中,创建了两个可执行文件,它们引用Python 3.6命令和默认Python命令(根据系统的不同,实际上可能引用不同版本的Python。例如,默认Python命令可以调用Python 2.7,而不是Python 3.6)。
另外,-m
选项表明Python将作为独立脚本运行模块,也就是说,它的内容将在__main__
命名空间中运行。这样意味着我们不必知道模块的完整路径,因为Python将在sys.path
中查找脚本。换句话说,对于经常会被导入另一个Python文件中的模块,可以直接从命令行运行。
在运行pipenv
的示例中,该命令利用了Python允许通过-m
选项直接运行模块或允许导入模块这一特性。在这种情况下,pipenv
导入venv
来创建虚拟环境,作为创建过程的一部分。
安装包通常通过查看PyPI的网站来寻找需要的模块,但是pip
也同样支持从版本控制、本地项目和分发文件中安装。
Python wheel是预先构建的压缩文件,与从源文件安装相比,它可以加快包的安装过程。可以将它比作为操作系统安装预先制作的二进制应用程序,而不是构建和安装源文件。
开发wheel是为了取代Python egg,Python egg在开发新的包标准之前就已经执行了wheel的功能。wheel通过指定.dis-info
目录(安装的Python包的数据库,非常接近磁盘上的格式)和实现包元数据(有助于识别软件依赖关系)改进了Python egg。
只要有可能,pip
就会自动使用wheel文件安装,不过可以使用pip install-- no-binary
禁用此功能。如果wheel文件不可用,pip
将查找源文件。Wheel文件可以从PyPI手动下载,也可以从本地存储库(local repository)提取,只需告诉pip本地文件的位置即可。
(1)使用pip
从PyPI上拉取最新版本的包。
$
pip install <package_name>
(2)也可以指定包的版本。
$
pip install <package_name>==1.2.2
图1.2是我们在pipenv
中安装的pygments
降级的示例。
图1.2
(3)可以下载软件包的最低版本。当一个包在不同版本之间有显著变化时,这是很常见的。
$
pip install "<package_name> >= 1.1"
(4)如果PyPI包有一个wheel文件可用,pip
会自动下载这个wheel文件。否则,它将会获取源代码并进行编译。
$
pip install <some_package>
(5)为了安装一个本地的wheel文件,需要提供文件的完整路径。
$
pip install /local_files/SomePackage-1.2-py2.py3-none-any.whl
wheel文件名格式分解为<package_name>-<version>-<language_version>- <abi_tag>-<platform_tag>.whl
。包名是要安装的模块的名称,后面是这个特定wheel文件的版本。
语言版本为Python 2或Python 3。它可以根据需要指定,比如py27
(Python 2.7.x)或py3
(Python 3.x.x)。
ABI(Application Binary Interface)是应用程序二进制接口。在过去,Python解释器所依赖的底层C的API(应用程序编程接口)在每次发布时都会发生变化,通常是通过添加API特性而不是更改或删除现有API来实现。Windows操作系统尤其会受到影响,每个Python特性发布都会为Python Windows的DLL创建一个新名称。
ABI涉及Python的二进制兼容性。虽然对Python结构定义的更改可能不会破坏API兼容性,但是ABI兼容性可能会受到影响。大多数ABI问题来自内存结构布局的更改。
从3.2版本开始,ABI就保证了一组有限的API特性是稳定的。指定ABI标记允许开发人员指定包与哪些Python实现兼容,例如PyPy与CPython。一般来说,这个标记被设置为none
,这意味着没有特定的ABI需求。
平台标记指定wheel
包设计要运行的操作系统和CPU。通常来说,这个包设计为各个平台的通用包,除非开发人员有特殊的理由将包限制为特定的系统类型。
如前所述,可以创建需求文件requirements.txt
来提供要一次性安装的包的列表,通过pip install -r requirements.txt
来安装。需求文件可以指定特定的或最低的版本,或者简单地指定库名,然后安装最新的版本。
应该注意的是,从需求文件中提取的文件并不一定按照特定的顺序安装。如果我们需要在安装其他包之前先安装某些包,则必须采取措施确保安装是按照顺序的,例如有多个pip install
调用。
需求文件可以显式指定包的版本号。例如,两个不同的模块(m1
和m2
)都依赖第三个模块(m3
)。模块m1
要求m3
至少是1.5版本,但m2
要求不低于2.0版本,m3
的当前版本是2.3。此外,m2
的最新版本(1.7版本)已知包含一个Bug。
可以在需求文件中使用哈希摘要来验证下载的包,以防止PyPI数据库或HTTPS证书链被破坏。这其实是一件好事,因为在2017年被上传至PyPI的10个Python库中都发现存在恶意文件。
实际上,由于PyPI在上传包时不执行任何安全检查或代码审计,因此很容易上传恶意软件。
(1)通过输入要包含在项目中的包,手动创建requirements.txt
。图1.3是一个例子。
图1.3
(2)运行pip freeze > requirements.txt
,将自动把当前安装的包定向到正确格式化的需求文件。
(3)要实现哈希检查模式,只需在需求文件中写入带有包名的摘要,如下所示。
FooProject
== 1.2 --hash=sha256:<hash_digest>
![]()
支持的哈希算法包括md5、sha1、sha224、sha224、sha384、sha256和sha512。
(4)如果存在模块冲突,或需要特殊版本控制,则提供所需的第一个模块。
m1
(5)指出第二个模块,但确保安装的版本早于已知的版本。
m2<1.7
(6)提供第三个模块,确保它至少等于所需的最低版本,但不高于可以使用的最高版本。
m3>=1.5,
<=2.0
图1.3显示了一些版本说明符的需求,下面的例子展示了在requirements.txt
中指定模块版本的一些不同方法。
flask
flask-pretty
== 0.2.0
flask-security <= 3.0
flask-oauthlib >= 0.9.0, <= 0.9.4
在本例中,模块m1
被指定为依赖包,但是版本号并不重要。在这种情况下,pip
将安装最新的版本。但是,由于m2
的最新版本存在错误,所以指定安装较早的版本。最后,m3
必须是1.5~2.0之间的版本,以满足安装要求。当然,如果这些条件之一不能满足,安装将会失败,并且会显示出有问题的库和版本号,以便进行进一步的故障排除。
值得注意的是,pip
没有真正的依赖性解析,它只需要安装指定的第一个文件。因此,有可能存在与实际需求不匹配的依赖冲突或子依赖。这就是需求文件存在的重要意义,因为它减轻了一些依赖问题。
验证哈希值还可以确保在不更改版本号的情况下无法更改包,例如在自动服务器部署中。这是提高效率的理想情况,因为它消除了只维护已批准的包的私有索引服务器的需要。
开源软件的好处是可以查看和修改源代码。如果我们正在处理一个项目并创建一个PyPI模块的本地版本,例如为一个项目定制或创建一个补丁,则可以使用requirements.txt
覆盖文件的正常下载。
约束文件是需求文件的一种改进,它仅指示安装库的哪个版本,但并不控制文件的安装。
使用约束文件的一个例子是,在使用PyPI模块的本地补丁版本时,例如ReqFile,从PyPI下载的一些软件包依赖ReqFile,但其他软件包不依赖ReqFile。我们不会为PyPI中每一个依赖ReqFile的包编写需求文件,而是会创建一个约束文件作为主记录,并将之应用于所有的Python项目。任何正在安装的且需要ReqFile的包都将看到约束文件,并从本地存储库进行安装,而不是从PyPI进行安装。
通过这种方式,每个开发人员都可以使用同一个文件,并且PyPI包所依赖的内容不再重要。正确的版本将从PyPI下载,或者根据需要使用本地版本。
(1)标记文件的内部版本。假设使用的是Git,那么标签的生成方法如下。
git
tag -a <tag_name> -m "<tag_message>"
# git tag -a v0.3 -m "Changed the calculations"
(2)上传到版本控制系统。
(3)在requirements.txt
文件中指定本地版本,如下面的例子。
git+https://<vcs>/<dependency>@<tag_name>#egg=<dependency>
#
git+https://gitlab/pump_laws@v0.3#egg=pump_laws
(4)用requirements.txt
文件编写的方式编写constraints.txt
文件。如下面的例子(这是在Apache v2.0许可下由MLDB.ai发布的)。
#
math / science / graph stuff
bokeh==0.11.1
numpy==1.10.4
pandas==0.17.1
scipy==0.17.0
openpyxl==2.3.3
patsy==0.4.1
matplotlib==1.5.1
ggplot==0.6.8
Theano==0.7.0
seaborn==0.7.0
scikit-learn==0.17
pymldb==0.8.1
pivottablejs==0.1.0
# Progress bar
tqdm==4.11.0
ipython==5.1.0
jupyter==1.0.0
jupyter-client==4.4.0
jupyter-console==5.0.0
jupyter-core==4.2.1
# validator
uWSGI==2.0.12
pycrypto==2.6.1
tornado==4.4.2
## 以下需求使用pip freeze添加:
backports-abc==0.5
backports.shutil-get-terminal-size==1.0.0
backports.ssl-match-hostname==3.5.0.1
bleach==1.5.0
***进一步截断文件***
(5)运行命令pip install –c constraints.txt
来使文件对Python发挥作用。
在前面的例子中,<vcs>
是所使用的版本控制系统,它可以是本地服务器或在线服务器,如GitHub;<tag_name>
是版本控制标记,用于标识对控制系统的特定更新。
如果所需的依赖项是项目的顶级需求,那么可以简单地替换需求文件中的特定行;如果它是另一个文件的子依赖项,那么上面的命令将作为新行添加。
约束文件与需求文件有一个关键的区别:将包放在约束文件中不会导致包被安装,而需求文件将安装列出的所有包。约束文件仅仅是控制安装包的某个版本的需求文件,但是不会控制实际的安装。
可以用各种不同的方式来处理Python包。开发人员常常需要从系统中卸载Python包或安装Python包。卸载包和安装包一样简单。
由于很容易忘记过去安装了什么包,因此pip
提供了列出当前安装的所有包以及指出哪些包已经过时的功能。1.7.1节中的示例来自Python列表和显示文档页面。
最后,在查找要安装的包时,可以从命令行找到包,而不是打开浏览器直接导航到PyPI
。
(1)卸载包,运行pip uninstall <package_name>
命令。这将卸载系统上的大多数包。
(2)通过使用-r
选项,可以使用需求文件一次删除许多包,例如pip uninstall -r <requirements_file>
。通过使用-y
选项可允许自动确认删除。
(3)通过运行pip list
列出当前安装的包,如图1.4所示。
图1.4
(4)要显示过时的包,使用pip list --outdated
,如下所示。
$
pip list --outdated
docutils (Current: 0.10 Latest: 0.11)
Sphinx (Current: 1.2.1 Latest: 1.2.2)
虽然可以一次更新所有过时的包,但这在pip
中本身是不可用的。主要有两种选择:第一种是使用sed
、awk
或grep
遍历包列表,查找过时的包并更新它们;第二种是使用pip-review
查看过时的包并更新它们。此外,不同的开发人员已经创建了许多其他工具,以及关于如何自己完成这些工作的说明,因此我们应该自己判断哪些工具适合自己。
![]()
自动升级所有Python包会破坏依赖关系。我们应该只根据需要更新包。
(5)可以使用pip show <package_name>
显示特定安装包的详细信息,如下所示。
$
pip show sphinx
Name: Sphinx
Version: 1.7.2
Summary: Python documentation generator
Home-page: http://sphinx-doc.org/
Author: Georg Brandl
Author-email: georg@python.org
License: BSD
Location: /my/env/lib/python2.7/site-packages
Requires: docutils, snowballstemmer, alabaster, Pygments,
imagesize, Jinja2, babel, six
(6)运行命令pip
搜索“query_string
”。下面的例子来自https://pip. pypa. io/en/ stable/reference/pip_ search,输出如下。
$
pip search peppercorn
pepperedform - Helpers for using peppercorn with formprocess.
peppercorn - A library for converting a token stream into [...]
在搜索包时,查询的可以是包名,也可以是简单的单词,因为pip
将在包名或包描述中找到具有该字符串的所有包。如果我们知道要做什么,但不知道包的实际名称,那么这是定位包的一种有用的方法。
使用python setup.py install
安装的包和使用python setup.py develop
安装的程序包不能通过pip
卸载,因为它们不提供关于安装文件的元数据。
还有许多其他选项可用于列出文件,例如只列出非全局包、Beta版本的包以及其他可能有用的工具等。
可以使用--verbose
选项显示更多信息,如图1.5所示。
图1.5
verbose
选项显示与默认模式相同的信息,但也包括在包的PyPI页面上可以找到的分类器信息。虽然这信息很明显可以仅仅通过PyPI站点发现,但如果是一台独立的计算机或计算机无法连接到互联网,那么明确软件是否受当前环境支持或在特定主题内查找相似的软件包是很有用的。
pip wheel
允许开发人员将所有项目依赖项以及任何已编译的文件打包到单个归档文件中。这对于在索引服务器不可用时进行安装非常有用,并且可以避免重新编译代码。但是,读者要认识到编译的包通常是特定于某个操作系统和体系结构的,因为它们通常是用C代码编写的,这意味着如果不重新进行编译,则它们通常无法跨不同的操作系统或体系结构移植。这也是哈希检查的一个很好的用途,可以确保将来的wheel是用相同的包构建的。
创建归档文件,执行以下操作。
(1)创建一个临时目录。
$
tempdir = $(mktemp -d /tmp/archive_dir)
(2)创建一个wheel文件。
$
pip wheel -r requirements.txt --wheel-dir = $tempdir
(3)让操作系统知道归档文件的位置。
$
cwd = `pwd`
(4)切换到临时目录并创建归档文件。
$
(cd "$tempdir"; tar -cjvf "$cwd/<archive>.tar.bz2" *)
为了从压缩包中安装,执行以下操作。
(1)创建一个临时目录。
$
tempdir=$(mktemp -d /tmp/wheelhouse-XXXXX)
(2)更改为临时目录并解压文件。
$
(cd $tempdir; tar -xvf /path/to/<archive>.tar.bz2)
(3)使用pip
安装未归档文件。
$
pip install --force-reinstall --ignore-installed --upgrade --no-index --no-deps $tempdir/*
在第一个示例(创建归档文件)中,首先创建一个临时目录;然后使用一个需求文件创建wheel,并将其放置在临时目录中;接下来,创建cwd
变量,并将其设置为当前工作目录(pwd);最后,发出一个组合命令,将其更改为临时目录,并在cwd
中创建临时目录中所有文件的归档文件。
在第二个示例(从压缩包中安装)中,首先创建一个临时目录;然后给出一个组合命令以更改到该临时目录并提取构成归档文件的文件;最后,使用pip
,用绑定文件将Python程序安装到临时目录中的计算机上。
--force-reinstall
将在升级时重新安装所有包,即使它们是最新的。--ignore-installed
强制重新安装,忽略包是否已经存在。--upgrade
将所有指定的包升级到可用的最新版本。--no-index
忽略包索引,只查看要解析存档的URL。--no-deps
确保没有安装包依赖项。
解释语言,如Python,通常采用源代码并生成字节码。字节码是比源代码级别低但没有机器码(汇编语言)优化的编码指令。
字节码通常在解释器中执行(解释器是虚拟机的一种类型),不过它也可以进一步被编译成汇编语言。字节码主要用于实现简单的跨平台兼容性。Python、Java、Ruby、Perl和类似的语言都是在源代码保持不变的情况下为不同的体系结构使用字节码解释器的语言。
虽然Python会自动将源代码编译成字节码,但是我们可以使用一些选项和特性来修改解释器使用字节码的方式。这些选项可以提高Python程序的性能,这是一个关键特性,因为解释语言的运行速度本质上比编译语言慢。
(1)要创建字节码,只需通过Python <program>.py
执行Python程序。
(2)当从命令行运行Python命令时,有几个“开关”可以减少已编译字节码的大小。请注意,有些程序可能希望下面的示例中删除的语句能够正常工作,所以只有在知道预期情况下才使用它们。
-O
:从需要编译的源代码中删除assert
语句,这些语句在测试程序时提供了一些调试帮助,但生产代码中通常不需要它们。-OO
:删除assert
和__doc__
字符串,以减少代码大小。(3)将程序从字节码加载到内存中比使用源代码要快,但是实际的程序执行速度并不快(由于Python解释器的特性)。
(4)compileall
模块可以为目录中的所有模块生成字节码。关于该命令的更多信息可以在Python的参考文档中找到。
当Python解释器读取源代码(.py
文件)时,生成字节码并以<module_name>. <version>.pyc
的形式存储在__pycache__
中。.pyc
扩展名表明它是编译好的Python代码文件。这种命名约定允许不同版本的Python代码同时存在于系统中。
当源代码被修改时,Python会自动检查缓存中已编译版本的日期,如果日期过期,则会自动重新编译字节码。但是,直接从命令行加载的模块不会存储在__pycache__
中,每次都会重新编译。此外,如果没有源模块,就不能检查缓存,也就是说,只有字节码的包将没有与其关联的缓存。
由于字节码是独立于平台的(通过平台的解释器运行),因此Python代码可以作为.py
(源代码文件)或.pyc
(字节码文件)发布。这就是字节码包发挥作用的地方。为了实现一定的模糊性和(主观的)安全性,Python程序可以在没有源代码的情况下发布,只提供预编译的.pyc
文件。在这种情况下,编译后的代码放在源目录中,而不是源代码文件中。
我们已经讨论了模块和包,它们可以互换使用。但是,模块和包之间有一个区别:包实际上是模块的集合,它们包含一个__init__.py
文件,该文件可以是一个空文件。
模块中用于访问特定函数或变量的点命名法也用于包中。这样,点号名称允许在不存在名称冲突的情况下访问包中的多个模块。每个包创建自己的命名空间,所有模块都有自己的命名空间。
当包中包含子包时,可以使用绝对路径或相对路径导入模块。例如,要导入setup.py
模块,可以使用一个绝对路径导入它:from video.effects.specialFX import sepia
。
(1)在制作包时,按照目录结构遵循正常的文件系统层次结构。也就是说,相互关联的模块应该放在它们自己的目录中。
(2)在package_tree.py
中显示了视频文件处理程序的可能的包。
video/ # 顶级包
__init__.py # 顶级初始化
formats/ # 文件格式子包
__init__.py # 包级初始化
avi_in.py
avi_out.py
mpg2_in.py
mpg2_out.py
webm_in.py
webm_out.py
effects/ # 视频效果子包
specialFX/ # 特效子包
__init__.py
sepia.py
mosaic.py
old_movie.py
glass.py
pencil.py
tv.py
transform/ # 转换效果子包
__init__.py
flip.py
skew.py
rotate.py
mirror.py
wave.py
broken_glass.py
draw/ # 绘制效果子包
__init__.py
rectangle.py
ellipse.py
border.py
line.py
polygon.py
(3)但是,如果我们已经在specialFX/
目录中并希望从另一个包导入,会发生什么情况呢?使用相对路径来遍历目录并通过点进行导入,就像在命令行上更改目录一样。
from
. import mosaic
from .. import transform
from .. draw import rectangle
在这个例子中,整个video
包共包括两个子包,即视频格式和视频效果。视频效果有自己的子包。在每个包中,每个.py
文件都是一个单独的模块。在模块导入期间,Python在sys.path
上查找包。
目录必须包含__init__.py
文件,这样Python才会将这些目录视为包。这可以防止具有公共名称的目录在搜索路径上进一步隐藏Python模块。它们还允许在调用Python程序时,通过-m
选项将模块作为独立程序进行调用。
初始化文件通常为空,但可以包含包的初始化代码。它们还可以包含__all__
列表,这是一个Python模块列表,每当使用from <package> import *
时,都应该导入它。
使用__all__
的原因是允许开发人员显式地指出应该导入哪些文件。这是为了防止在包中导入其他开发人员不一定需要的模块时出现过度延迟。它还降低了导入模块时出现副作用的可能性。不过这样做的问题是,开发人员需要在每次更新包时更新__all__
列表。
相对导入基于当前模块的名称。由于程序的主模块总是名为"__main__"
,因此任何将成为应用程序主模块的模块都必须使用绝对导入。
老实说,使用绝对导入通常更安全,这样可确保我们知道在导入什么。现在的大多数开发环境都提供了路径建议,因此编写自动填充的路径和使用相对路径一样简单。
如果__all__
没有在__init__
中定义,那么import *
只导入指定包中的模块,而不是所有子包或它们的模块。例如,from video.formats import *
只导入视频格式,effects/
目录中的模块将不包括在内。
这是Python程序员的最佳实践:就像《Python之禅》所述,显式优于隐式。因此,从包中导入特定的子模块是一件好事,而import *
由于具有变量名冲突的可能性而不受欢迎。
包具有__path__
属性,该属性很少使用。该属性是一个列表,其中包含包的__init__.py
文件所在目录的名称。在运行文件的其余代码之前,将访问此位置。
修改包路径会影响将来在包中搜索模块和子包。当需要扩展在包搜索过程中找到的模块数量时,这非常有用。
Python程序通常在源代码或wheel文件中提供。但是,有时开发人员需要提供特定于操作系统的文件,比如Windows.exe,以便于安装。Python为开发人员提供了许多选项来创建独立的可执行文件。
py2exe是用于创建Windows特定文件的一个选项。不幸的是,我们很难判断这个项目是如何维护的,因为到目前为止PyPI的官方网站上的最后一个版本是在2014年发布的,而py2exe的官方网站引用了2008年的版本,它似乎也只适用于Python 3.4和更老的版本。但是,如果读者认为这个程序可能有用,它确实可以将Python脚本转换成Windows的可执行文件,而不需要安装Python。
py2app是创建独立Mac包的主要工具。它的构建非常类似于py2exe,但是需要一些库依赖项,这些依赖项在相关网站中。
用于生成特定于操作系统的可执行程序的跨平台工具要多于用于特定操作系统的跨平台工具,这很正常,因为许多开发人员使用Linux作为他们的开发环境,并且很可能无法访问Windows或macOS操作系统。
对于不想自己建立多个操作系统的开发人员,有几个在线服务允许我们在线租用操作系统。例如,vmOSX网站允许访问托管的macOS;而Windows托管有多种选择,从Amazon Web Services到普通Web主机。
对于那些希望本地控制二进制文件的执行的开发者来说,cx_Freeze是Python中比较流行的创建可执行程序的方法之一。它只适用于Python 2.7或更新版本,但对于大多数开发人员来说,这应该不是问题。但是,如果我们想在Python 2代码中使用它,则必须使用cx_Freeze 5。从版本6开始,它就不再支持Python 2的代码了。
![]()
cx_Freeze创建的模块存储在ZIP文件中。默认情况下,包存储在文件系统中,但如果需要,可以包含在相同的ZIP文件中。
PyInstaller的主要目标是与第三方包兼容,在二进制创建过程中不需要用户干预就可以使外部包正常工作。它适用于Python 2.7和更新的版本。
PyInstaller提供了多种方式来打包Python代码:作为单个目录(包含可执行文件和所有必需的模块)打包、作为单个文件(自包含且不需要外部依赖)打包,或者以自定义模式打包。
大多数第三方包都可以使用PyInstaller,不需要额外的配置。这个用起来很方便,有一个位于https://github.com/pyinstaller/pyinstaller/wiki/Supported-Packages的列表,提供已知的使用PyInstaller的包。如果有任何限制,例如,只在Windows操作系统上工作,也要注意这些限制。
Cython实际上是Python的一个superset,旨在为Python代码提供类似C语言的性能。这是通过允许向Python代码中添加类型的方式来实现的。Python通常是动态类型的,而Cython允许变量的静态类型。生成的代码被编译成C代码,正常的Python解释器可以正常地执行C代码,且速度与编译后的C代码相同。
虽然Cython通常用于为Python创建扩展或加快Python处理,但是使用Cython
命令中的embed
标志将创建一个C文件,然后可以将该文件编译为一个普通的应用程序文件。
当然,这需要更多关于使用gcc
或我们选择的编译器的知识,因为我们必须知道如何在编译期间导入Python头文件,以及需要包括哪些其他目录。因此,对于不熟悉C代码的开发人员而言,不建议使用Cython,但是可以通过同时使用Python和C语言来创建功能齐全的应用程序,这是一种非常强大的方法。
Nuitka是一个相对较新的Python编译程序。它与Python 2.6及更高版本兼容,但也需要gcc
或其他C编译器。0.5.29版本是Beta版,但作者发现它能够编译当前可用的所有Python构造而不出任何问题。
Nuitka非常类似于Cython,因为它使用C编译器将Python代码转换为C代码,并生成可执行文件。它可以编译整个程序,并将模块嵌入文件中,但是如果需要,也可以自行编译单个模块。
默认情况下,生成的二进制文件需要安装Python,以及必要的C扩展模块。但是,我们可以使用--stand-alone
创建真正的独立可执行文件。
(1)编写Python程序。
(2)要创建Windows.exe文件,请创建setup.py
文件来告诉库想做什么。这主要是从Distutils库导入setup()
函数、导入py2exe
,然后调用setup()
函数并告诉它正在创建的应用程序类型(例如控制台)和主要Python文件。下面的py2exe_setup.py
是setup.py
文件中的一个例子。
from
distutils.core import setup
import py2exe
setup(console=['hello.py'])
(3)通过调用python setup.py py2exe
运行setup脚本。这将创建两个目录:build/
和dist/
。dist/
目录是放置新文件的位置,而build/
用于放置创建过程中产生的临时文件。
(4)通过进入dist/
目录并运行位于其中的.exe
文件来测试应用程序。
(5)要创建macOS.app
文件,请先创建setup.py
文件。在此步骤中需要包含应用程序所需的任何图标或数据文件。
(6)清理build/
和dist/
目录,确保没有意外包含的文件。
(7)使用别名模式构建应用程序,即不准备分发。这允许我们在打包交付之前测试程序。
(8)测试应用程序并验证它在别名模式下是否正常工作。
(9)再次清理build/
和dist/
目录。
(10)运行python setup.py py2app
并创建可分发的.app
文件。
(11)对于跨平台文件,使用cx_Freeze
的一种简单方法是使用cxfreeze
脚本。
cxfreeze
<program>.py --target-dir=<directory>
此命令还有其他选项,例如压缩字节码、设置初始化脚本,甚至排除某些模块。
如果需要更多的功能,可以创建distutils
设置脚本。命令cxfreeze- quickstart
可以用来生成一个简单的设置脚本。cx_Freeze 文档提供了一个示例setup.py
文件(cxfreeze_setup.py
)。
import sys
from cx_Freeze import setup, Executable
# 自动检测依赖关系,但可能需要微调
build_exe_options = {"packages": ["os"], "excludes": ["tkinter"]}
# GUI在Windows中有所不同(默认是控制台应用程序)
# console application).
base = None
if sys.platform == "win32":
base = "Win32GUI"
setup( name = "guifoo",
version = "0.1",
description = "My GUI application!",
options = {"build_exe": build_exe_options},
executables = [Executable("guifoo.py", base=base)])
要运行安装脚本,请运行以下命令:python setup.py build
。这将创建目录build/
,其中包含子目录exe.xxx
,其中xxx
为平台特定可执行二进制指示器。
(12)如果使用PyInstaller,则它的用法和大多数Python命令一样,是一个简单的命令。
pyinstaller
<program>.py
这将在dist/
子目录中生成二进制包。当然,在运行这个命令时,还有许多其他选项。
pyx
的文件中。例如,helloworld. pyx
。(13)在使用Cython时,创建一个setup.py
文件。
from
distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize("helloworld.pyx")
)
(14)运行以下命令创建Cython文件。
$
python setup.py build_ext --inplace
(15)这将在本地目录helloworld
中创建一个文件:在*NIX(UNIX或类UNIX操作系统)上是helloworld.so
,在Windows操作系统上是helloworld.pyd
。
(16)要使用二进制文件,只需将其正常导入Python即可。
(17)如果Python程序不需要额外的C库或特殊的构建配置,那么可以使用pyximport
库。这个库中的install()
函数允许在导入时直接加载.pyx
文件,而不必在每次代码更改时都重新运行setup.py
。
(18)要使用Nuitka编译一个包含所有模块的程序,请使用以下命令。
nuitka
--recurse-all <program>.py
(19)要编译单个模块,使用以下命令。
nuitka
--module <module>.py
(20)要编译整个包并嵌入所有模块,可以将前面的命令组合成类似的格式。
nuitka
--module <package> --recurse-directory=<package>
(21)要生成真正的跨平台二进制文件,请使用选项--standalone
,将<program>.py
目录分区复制到目标系统,然后在该目录中运行.exe
文件。
根据用户的系统配置,我们可能需要提供Microsoft Visual C runtime DLL。根据使用的Python版本,py2exe
文档提供了不同的文件可供选择。
此外,py2exe
不创建安装构建器,即安装向导。虽然我们的应用程序可能不需要向导,但Windows用户通常希望在运行.exe
文件时可以使用向导。有许多免费的、开源的和专有的安装构建器。
构建Mac二进制文件的一个好处是它们易于打包以便分发。一旦生成.app
文件,右击该文件并选择Create Archive,我们的应用程序就可以发布了。
cx_Freeze的一个常见问题是,程序不会自动检测需要复制的文件。当我们正在动态地将模块导入程序(例如插件系统)时,这种情况经常发生。
cx_Freeze创建的二进制文件是为运行它的操作系统生成的。例如,要创建Windows.exe文件,必须在Windows计算机上使用cx_Freeze。因此,要创建真正的跨平台Python程序并作为可执行二进制文件分发,我们必须能够访问其他操作系统。这可以通过使用虚拟机、云主机或简单地购买相关系统来实现。
当运行PyInstaller时,它分析提供的Python程序并在Python程序所在的文件夹中创建一个<program>.spec
文件。此外,build/
目录位于相同的位置。
build/
目录包含日志文件和用于实际创建二进制文件的工作文件。生成可执行文件后,将一个dist/
目录放置在与Python程序相同的位置,并将二进制文件放置在dist/
目录中。
Nuitka生成的可执行文件在所有平台上都具有扩展名exe
。它在非Windows操作系统上仍然可用,但建议将扩展名更改为特定系统的扩展名,以免混淆。
使用前面显示的任何命令创建的二进制文件都需要在终端系统上安装Python,以及被使用的任何C扩展模块。
如果我们已经开发了一个项目,并且希望将其发布到PyPI上,那么需要做几件事情来确保正确地上传和注册我们的项目。虽然本节将重点介绍为PyPI上的发行版配置包的一些关键特性,但它并不是包罗一切。请确保查看PyPI站点上的文档,以确保获得最新的信息。
首先要做的事情是将twine
包安装到Python环境中。twine
是用于与PyPI交互的实用程序的集合。使用它的主要原因是通过HTTPS可以对数据库的连接进行身份验证,这确保在与PyPI交互时加密用户名和密码。有些人可能不关心恶意实体是否获取了Python存储库的登录凭证,因为许多人在多个站点使用相同的登录名和密码,这意味着使用PyPI登录信息的人可能还会访问其他站点。
twine
还允许我们预先创建分发文件,也就是说,我们可以在发布包文件之前测试它们,以确保一切正常。作为它的一部分,我们可以上传任何格式的包到PyPI,包括wheel。
最后,twine
允许我们对文件进行数字预签名,并在上传文件时将.asc
文件传递到命令行,通过验证将凭证传递到GPG应用程序(而不是其他应用程序)来确保数据安全。
需要以正确的方式配置我们的项目文件,并在PyPI上将其正确列出,以便其他开发人员可以使用它们。这个过程中最重要的步骤是设置setup.py
文件,该文件位于项目的根目录中。
setup.py
文件包含项目的配置数据,特别是setup()
函数,它定义了项目的详细信息。它也是命令行接口,可用于运行与打包过程相关的命令。
包中应该包含许可证(license.txt
)。这个文件非常重要,因为在某些领域没有明确许可的包,只能由版权所有者合法使用或分发。有许可证的包可以确保创建者和用户都受到法律保护,不受侵权问题的影响。
(1)创建一个清单文件。
(2)通过定义distutils setup()
函数的选项来配置setup.py
文件。
如果需要打包源发行版中没有自动包含的文件,则清单文件也很重要。默认情况下,生成时包中包含以下文件(称为标准包含集)。
py_modules
和packages
选项隐含的Python源文件。ext_modules
或者libraries
选项中的C源文件。scripts
选项标识的任何脚本。test*.py
的脚本。readme
文件:setup.py
、setup.cfg
和README.txt
。package_data
和data_files
元数据的文件。任何不满足这些条件的文件,比如许可文件,都需要包含在MANIFEST.ini
模板文件中。清单模板是关于如何生成实际清单文件的指令列表,其中列出了要包含在源发行版中的确切文件。
清单模板可以包含或排除任何需要的文件,通配符也可用。例如,distutils
包中的manifest_template.py
显示了一种列出文件的方法。
include *.txt
recursive-include examples *.txt *.py
prune examples/sample?/build
这个示例表明清单文件应该包括根目录中的所有.txt
文件,以及example/
子目录中的所有.txt
和.py
文件。另外,所有匹配examples/sample?/build
的目录将被排除在包之外。
清单文件是在考虑上述默认值之后处理的,因此如果我们想从标准包含集中排除文件,可以显式地在清单中列出它们。但是,如果我们想完全忽略标准包含集中的所有缺省值,可以使用--no-defaults
选项完全禁用标准包含集。
清单模板中的命令顺序很重要。在处理标准包含集之后,将按顺序处理模板命令。完成此操作后,将处理最终生成的命令集,删除所有要删除的文件。生成的文件列表被写入清单文件以备将来参考,然后使用清单文件构建源发行版并存档。
重要的是,要注意清单模板不影响二进制分布,比如wheel,它只用于源文件打包。
正如前面提到的,setup.py
文件是打包过程的关键文件,setup()
函数允许定义项目的细节。
我们可以为setup()
函数提供许多参数,下面的列表将介绍其中一些参数。清单包部分(Listing Package)就是一个很好的例子。
name
:项目的名称,它将在PyPI上列出。只能接受ASCII字母及数字字符、下划线、连字符和句点等,并且必须以ASCII字符开始和结束,这是一个必填项。通过pip
提取项目名称时不区分大小写,即My.Project = My-project = my-PROJECT
,所以要确保名称本身是唯一的,而不仅仅是与其他项目相比的不同的大小写。version
:项目的当前版本。它用于告诉用户是否安装了最新的版本,以及指示他们针对哪些特定的版本测试了软件。这是一个必填项。实际上,在PEP 440上有一个文档指出如何编写版本号。versioning.py
是对项目进行版本控制的一个例子。
2.1.0.dev1 # 开发版
2.1.0a1 # Alpha版
2.1.0b1 # Beta版
2.1.0rc1 # 发布候选
2.1.0 # 最终版
2.1.0.post1 # 后发布
2018.04 # 发布日期
19 # 序列
description
:对项目进行简短的描述。当项目发布时,这些将显示在PyPI上。短描述是必需的,但长描述是可选的。url
:项目的主页URL。这是一个可选字段。author
:开发人员名称或组织名称。这是一个可选字段。author_email
:上面列出的是作者的邮箱地址。不鼓励通过拼写特殊字符来混淆电子邮件地址,例如your_name at your_organization.com
,因为这是一个计算机可读字段,可以使用your_name@your_organization.com
。这是一个可选字段。classifiers
:这些分类器对项目进行分类,以帮助用户在PyPI上找到所需项目。有一个分类器列表,但它们是可选的。一些可能的分类器包括开发状态、使用的框架、预期用例、许可,等等。keywords
:描述项目的关键字列表。建议使用搜索项目的用户可能会使用的关键字。这是一个可选字段。packages
:项目中使用的包的列表。可以手动输入列表,也可以使用setuptools.find_packages()
自动定位它们。还可以包含一个排除包的列表,以忽略不打算发布的包。这是一个必填项。列出包的一种可选方法是分发单个Python文件,该文件将包参数更改为py_modules
,然后my_module.py
将存在于项目中。
install_requires
:指定要运行的项目的最小依赖项。pip
使用这个参数自动识别依赖项,因此这些包必须是有效的、现有的项目。这是一个可选字段。python_requires
:指定项目将运行的Python版本。这将防止pip
在无效版本上安装项目。这是一个可选字段。这是一个相对较新的特性。Setuptools 24.2.0是创建源发行版和wheel以确保pip
正确识别该字段所需的最低版本。此外,需要pip 9.0.0或更新的版本,早期的版本将忽略这个字段并安装包,而不管Python版本为何。
package_data
:用于指示要安装在包中的其他文件,如其他数据文件或文档。此参数是将包名映射到相对路径名列表的字典。这是一个可选字段。data_fields
:虽然package_data
是标识其他文件的首选方法,通常已经足够了,但是有时候需要将数据文件放在项目包之外,例如,需要将配置文件存储在文件系统中的特定位置。这是一个可选字段。py_modules
:项目中包含的单文件模块的名称列表。这是一个必填项。entry_points
:可执行脚本(如插件)的字典,这些脚本是在项目中定义的或项目所依赖的。入口点提供跨平台支持,并允许pip
为目标平台创建适当的可执行表单。对于这些功能,应该使用入口点来代替脚本参数。这是一个可选字段。到目前为止,我们讨论的所有内容都只是配置和设置打包项目所需的基础知识,我们还没有打包。要实际创建可以从PyPI或其他包索引安装的包,需要运行setup.py
脚本。
(1)创建一个基于源代码的发行版。包的最低要求是一个源发行版,源发行版提供pip
安装所需的元数据和基本源代码文件。源发行版本质上是原始代码,需要在安装之前执行构建步骤,从setup.py
构建安装元数据。源发行版是通过运行python setup.py
脚本创建的。
(2)虽然源发行版是必需的,但是创建wheel更方便。强烈推荐使用wheel包,因为它们是预先构建的包,无须等待构建过程就可以安装。这意味着与使用源发行版相比,安装要快得多。
有几种类型的wheel,这取决于项目是否是纯Python环境以及它是否同时支持Python 2和Python 3。要构建wheel,必须先安装wheel包:pip install wheel
。
(3)优选的wheel包是一个通用wheel。Universal wheels是纯Python,即不包含C-code编译的扩展,并且本机同时支持Python 2和Python 3环境。通用wheel可以使用pip
安装在任何地方。
要构建一个通用wheel,使用以下命令。
python
setup.py bdist_wheel --universal
--universal
应该只在没有使用C扩展,并且Python代码同时在Python 2和Python 3上运行而不需要修改的情况下使用,比如运行2to3
。
bdist_wheel
表示该发行版是二进制发行版,而不是源发行版。当与--universal
一起使用时,它不会检查以确保使用正确,因此如果不满足条件,也不会提供警告。
通用wheel不应该与C扩展一起使用的原因是pip
更喜欢wheel而不是源发行版。由于不正确的wheel很可能会阻止C扩展的构建,因此扩展将不可用。
(4)也可以使用纯Python wheel。纯Python wheel是在Python源代码本身不支持Python 2和Python 3功能时创建的。如果可以修改代码以便在两个版本之间使用,例如通过2to3
,则可以为每个版本手动创建wheel。
要构建wheel,使用以下命令。
python
setup.py bdist_wheel
bdist_wheel
将认证代码并构建一个wheel,该wheel与任何具有相同主版本号(2.x或者3.x)的Python安装兼容。
(5)在为特定平台制作包时可以使用平台wheel。平台wheel是基于特定平台/架构的二进制构建,这是由于包含了编译好的C扩展。因此,如果我们需要编写只在macOS上使用的程序,那么必须使用平台wheel。
如果使用了与纯Python wheel相同的命令,但是bdist_wheel
检测到代码不是纯Python代码,并将构建一个wheel,那么其名称将标识它仅在特定平台上可用。
setup.py
运行时会在项目的根目录中创建新的目录dist/
,这是为了放置用于上传的分发文件的目录。这些文件仅在运行构建命令时被创建,对源代码或配置文件的任何更改都需要重新构建分发文件。
在上传到主PyPI站点之前,有一个PyPI测试站点可以用来练习。这使开发人员明确整个构建和上传过程,这样他们就不会破坏主站点上的任何东西。测试站点是半定期清理的,因此在开发时不应该依赖它作为存储站点。
另外,检查setup.py
中的长描述和短描述,以确保它们是有效的。某些指令和URL在上传过程中被禁止或删除,这就是为什么最好在PyPI测试站点上测试我们的项目,看一看我们的配置是否有任何问题。
在上传到PyPI之前,我们需要创建一个账户。在网站上手动创建账户后,可以创建$HOME/.pypirc
文件用于存储用户名和密码。上传时将引用此文件,这样就不必每次都手动输入了。但是,请注意我们的PyPI密码是以明文形式存储的,因此如果担心密码泄露,那么必须在每次上传时都手动输入密码。
一旦创建了一个PyPI账户,就可以通过twine
将发行版上传到PyPI站点。对于新的发行版,twine
将自动在站点上处理项目的注册。通常使用pip
安装twine
。
(1)创建发行版。
python
setup.py sdist bdist_wheel --universal
(2)注册项目(如果是第一次上传)。
twine
register dist/<project>.<version>.tar.gz
twine register dist/<package_name>-<version>-
<language_version>-<abi_tag>-<platform_tag>.whl
(3)上传。
twine upload dist/*
(4)以下错误信息用于提示需要注册我们的包。
HTTPError:
403 Client Error: You are not allowed to
edit 'xyz' package information
使用HTTPS安全地将用户身份验证到PyPI数据库。将包上传到PyPI的旧方法是使用python setup.py upload
,这是不安全的,因为数据是通过未加密的HTTP传输的,所以我们的登录凭证可以被嗅探。使用twine
,通过验证的TLS进行连接,以防止凭证被盗窃。
twine
还允许开发人员预先创建发行版文件,而setup.py upload
只适用于同时创建的发行版。因此,使用twine
,开发人员可以在将文件上传到PyPI之前测试它们,以确保其正常工作。
最后,我们可以使用数字签名对上传进行预签名,并将.asc
认证文件附加到twine
中进行上传。这确保了开发人员的密码被输入GPG中,而不是如恶意软件一样的其他软件。