4 Python工程打包部署
Python程序在提供给用户使用时,要脱离Python开发环境运行,此时,需要对python工程进行打包。
常用的Python打包工具有PyInstaller, py2exe等。
4.1 PyInstaller
-
特点
一条命令即可完成打包。
以GPL标准许可发布,但可用于闭源商业性质的打包使用。
早先的版本不支持Python 3.x版本,最新的版本已经支持,并且具有跨平台特性,首选打包工具。
-
安装
pip install pyinstaller
pyinstaller.exe将安装到Python安装目录的Scripts目录下,和pip工具在相同路径。
-
打包命令简介
基本命令形式:
pyinstaller yourprogram.py
其中yourprogram.py是程序入口源文件。
-p指令
这个指令后面可以增加pyinstaller搜索模块的路径。因为应用打包涉及的模块很多。这里可以自己添加路径。不过经过笔者测试,site-packages目录下都是可以被识别的,不需要再手动添加。
-F指令
注意指令区分大小写。这里是大写。使用-F指令可以把应用打包成一个独立的exe文件,否则是一个带各种dll和依赖文件的文件夹。
-w指令
直接发布的exe应用带命令行控制台窗口,在指令内加入-w命令可以屏蔽。
--icon指令
为打包的exe指定图标。
-v指令
指定版本信息。
以上是最常用的几个可选参数指令,更多详细功能介绍可以参考PyInstaller官网:
如果Python项目工程比较复杂,可以编写一个python脚本辅助执行打包命令。
官网地址:
-
特点
py2exe也是比较常用的打包工具,py2exe 是在 Distutils 的基础上扩展了一个新的 "命令"。
所以使用py2exe进行打包,需要编写一个带有setup指令的脚本。
-
安装
pip install py2exe
SourceForge网站提供有历史版本的下载:
http://prdownloads.sourceforge.net/py2exe
安装完成后,libsite-packagespy2exesamples目录下含有使用py2exe打包的样例。
-
使用
可以参考官方的使用教程:
http://www.py2exe.org/index.cgi/Tutorial
-
注意
使用py2exe打包完成后,包含有一个名为library.zip的压缩包,压缩包内包含了所有python源文件编译后的的pyc或者pyo字节码文件。但是pyc和pyo很容易被反编译回Python源码,这一点需要注意。
5 Python常用编码技巧
5.1 Python编程规范
Python官方PEP8标准编程规范:
https://www.python.org/dev/peps/pep-0008/
python集成开发环境当中介绍的PyCharm,在编码过程中自带有官方PEP8标准的编程规范提示,推荐使用。
实际使用时,可以根据公司或者部门的代码特点进行更新或者扩展。
5.2 时间模块
-
等待一段时间
import time
time.sleep(5)
-
获得当前时间,时间差
方法1:
import datatime
import time
start_t = datetime.datetime.now() #get current time
time.sleep(3)
end_t = datetime.datetime.now()
tdiff = (end_t - start_t).seconds
方法2:
import time
t1 = time.localtime
time.sleep(3)
t2 = time.localtime
tdiff = time.mktime(t2) - time.mktime(t1)
if int(my_time_diff) > 30:
print “30 seconds time out”
5.3 序列类型
序列类型包括元组,字典和字符串,以下语法这三种类型全部适用。
以常量字符串a = “1234567890”,进行举例说明:
-
索引访问
>>> a[3]
'4'
>>> a[2:5]
'345'
>>> a[:5]
'12345'
>>> a[5:]
'67890'
>>> a[:]
'1234567890'
>>> a[2:9:2]
'3579'
>>> a[::2]
'13579'
>>> a[-2]
'9'
>>> a[::-1]
'0987654321'
5.3.2 序列类型的操作
长度:len()
连接:+
重复:*
判断元素是否在序列中:in
最大值:max()
最小值:min()
比较是否相同:cmp(elem1, elem2)
5.4 多级字典
多级字典不能一次性赋值,比如
level2_dict[“lvl1”][“lvl2”] = “result”
这样就会报错。只能分两步。
level2_dict[“lvl1”] = {}
level2_dict[“lvl1”][“lvl2”] = “result”
字符串的基本方法这里不做过多介绍,主要介绍一些字符串的实际使用技巧。
5.5.1 字符串的打印
Python的打印是不需要加入’ ’就能换行的,这点要注意,和其他的语言不太一样。
5.5.2 格式化字符串
-
通过%实现
>>> "my name is %s, I'm %d years old."%("mike", 15)
"my name is mike, I'm 15 years old."
-
字符串类format方法
>>> "my name is {0}, I'm {1} years old.".format("mike", 15)
"my name is mike, I'm 15 years old."
按照参数名称:
>>> "my name is {name}, I'm {age} years old.".format(name="mike", age="15")
"my name is mike, I'm 15 years old."
5.5.3 数字和字符串转换
my_str = str(my_num)
my_num = int(my_str)
- 通过string类方法
>>> import string
>>> a="12345"
>>> string.atoi(a)
12345
>>> b="123.678"
>>> string.atof(b)
123.678
5.5.4 正则表达式
Python对于字符串的操作十分简单方便,结合内置正则表达式re模块,几乎可以完成所有字符串操作的需求。
>>> import re
>>> tt = "Tina is a good girl, she is cool, clever, and so on..."
>>> rr = re.compile(r'w*oow*')
>>> print(rr.findall(tt))
['good', 'cool']
>>> print(re.match('comw*.', 'comwww.runcomoob').group())
comwww.
>>> print(re.search('dcom', 'www.4comrunoob.5com').group())
4com
>>> a = "123abc456"
>>> print(re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(0)) # 索引0表示返回匹配的整体
123abc456
>>> print(re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(1))
123
>>> print(re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(2))
abc
>>> print(re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(3))
>>> iter = re.finditer(r'd+', '12 drumm44ers drumming, 11 ... 10 ...')
>>> for i in iter:
print(i)
print(i.group())
print(i.span())
<_sre.SRE_Match object at 0x02517F38> # 匹配到的Match对象
12 # 匹配到的子串
(0, 2) # 匹配到的字串的索引
<_sre.SRE_Match object at 0x02526250>
44
(8, 10)
<_sre.SRE_Match object at 0x02517F38>
11
(24, 26)
<_sre.SRE_Match object at 0x02526250>
替换匹配到的字串
>>> text = "Mike is a handsome boy, he is cool, clever, and so on..."
>>> print(re.sub(r's+', '-', text))
Mike-is-a-handsome-boy,-he-is-cool,-clever,-and-so-on...
并获得替换的个数:
>>> print(re.sub("g.t", "have", 'I get A, I got B ,I gut C'))
I have A, I have B ,I have C
>>> print(re.subn("g.t", "have", 'I get A, I got B ,I gut C'))
('I have A, I have B ,I have C', 3)
>>> print(re.split('d+', 'one1two2three3four4five5'))
['one', 'two', 'three', 'four', 'five', '']
5.6 断言
Python中的assert是判断表达式是否为True的断言关键字,例如判断实例是否属于一个类:
assert isinstance(rsm_info_preview, RsmKeyInfo)
5.7 系统命令
-
路径的操作
ll_test_path = os.path.join(os.path.dirname(__file__),"win32", "IEDriver.dll")
os.path.dirname(__file__)表示当前file的位置。
Import sys
sys.exit()
注意有的时候在函数和类并不能使整个程序退出,注意尽量在主函数里退出才可以。
os.system(“xxx”)
阻塞命令,需要等待系统命令调用完成才能返回,成功返回0,失败返回1。
os.startfile(“xxx”)
非阻塞命令,单纯启动命令,执行后立即返回,与目标应用不存在从属关系。相当于双击操作。
os.popen(“xxx”)
执行后,启动一个进程,返回文件描述符对象,利用该对象可以捕获该进程的输出。
-
命令行
commands模块
纯命令行调用模块,可以获得命令的输出或(且)返回值。
-
进程高级
subprocess模块
更为高级的进程管理模块,提供比os.popen更加高级的接口,可以实现复杂的多进程管理。
5.8 lambda
在Python中,lambda是匿名函数的关键字。
lambda可以定义一句代码覆盖的方法或者函数。大部分情况下,lambda函数当做参数传递给其他方法或者函数,在方法或者函数内部实现动态调整计算实现的过程。类似模板方法设计模式的思路。
5.8.1 基本语法
>>> add = lambda x, y: x + y
>>> add(2,3)
5
如上面例子,lambda表达式中,除了lambda关键字,冒号左边等价于def函数的入参,冒号右边等价于def函数的函数体中的return返回值。
整个lambda表达式可以作为一个函数的引用,赋值给一个变量(即例子中的add)。
5.8.2 常用内建函数
lamda结合内建函数map, reduce, filter可以实现强大的序列类型(元组,列表,字符串)的处理功能。
-
map
内建函数map可以得到入参序列中逐个元素按照lambda函数处理后返回的序列。省去循环代码实现的过程。
>>> list1 = range(5)
>>> print list1
[0, 1, 2, 3, 4]
>>> map(lambda x: x*2, list1)
[0, 2, 4, 6, 8]
如上代码实现了列表中每个元素乘2的效果。
-
reduce
内建函数reduce进行两两元素的迭代计算,每次迭代,将上一次的迭代结果(第一次时为initial的元素,如没有initial则为列表的第一个元素)与下一个元素一同执行一个二元的lambda函数,最终得到一个计算结果。
>>> list1
[0, 1, 2, 3, 4]
>>> reduce(lambda x, y: x + y, list1)
10
>>> reduce(lambda x, y: x + y, list1, 2)
12
如上代码实现了列表求和的效果。
-
filter
该内建函数的作用相当于一个筛选器。lambda函数是一个布尔函数,filter调用这个布尔函数,将每个列表中的元素依次经过一次筛选,选出使lambda返回值是Ture的元素的序列。
>>> list1
[0, 1, 2, 3, 4]
>>> filter(lambda x: x % 2 == 1, list1)
[1, 3]
如上代码实现了筛选列表中所有奇数的效果。
5.9 字符串也是代码
5.9.1 eval
eval可以执行字符串参数的表达式,并返回表达式执行完成的结果。
>>> eval("((1+2)*3.0/4)**2")
5.0625
>>> eval("'hello '*2")
'hello hello '
>>> list1 = eval("[1,0.5,[3,4], 'hello python', {1:'one'}]")
>>> list1
[1, 0.5, [3, 4], 'hello python', {1: 'one'}]
>>> type(list1)
<type 'list'>
5.9.2 exec
exec可以执行字符串中的代码。
>>> exec("print 'hello python'")
hello python
>>> exec("for i in range(3):print i")
0
1
2
5.9.3 区别和使用
eval需要传入的是一个表达式,执行完成后会返回一个结果;exec则是执行字符串内包含的python代码,并不会返回结果。两者不能混用。
因为eval和exec的存在,大大增加了python代码的灵活性。程序可以在运行时状态动态生成代码去执行,可以留作热更新的接口,免去重新编译和打包的过程。不过也因此,带来编码安全的隐患,恶意的字符串注入,将可以执行任意代码。使用时需谨慎。
5.10 yield
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
如下就生成了一个斐波那契数列的生成器:
>>> def fib(max):
a, b = 1, 1
while a < max:
yield a
a, b = b, a+ b
>>> fib_gen = fib(15)
>>> fib_gen.next()
1
>>> fib_gen.next()
1
>>> fib_gen.next()
2
>>> fib_gen.next()
3
>>> fib_gen.next()
5
>>> fib_gen.next()
8
>>> fib_gen.next()
13
>>> fib_gen.next()
Traceback (most recent call last):
File "<pyshell#100>", line 1, in <module>
fib_gen.next()
StopIteration
可以看到,在使用next()的时候要注意生成器的生成边界。
在for循环当中,会自动调用 next(),并在触发StopIteration异常时自动捕获终止:
>>> for n in fib(15):
print n
1
1
2
3
5
8
13
yield的实时性更高,只有在调用的时候才真正执行,可以控制对于系统资源的消耗。
5.11 dir, setattr, getattr,hasattr
这四个函数是Python的内建函数,结合起来使用可以获取对象的属性信息,动态调整对象的属性和值。
>>> class BaseClass(object):
'''
classdocs
'''
def __init__(self, first, second, third=3, forth=4):
self.first = first
self.second = second
self._third = third
self.__forth = forth
print "in init"
def __del__(self):
print "in del"
def first_func(self):
print "in first func"
def _second_func(self, para1):
pass
def __third_func(self, para1, para2=0):
pass
>>> A = BaseClass(1, 2)
>>> dir(A)
['_BaseClass__forth', '_BaseClass__third_func', '__class__', '__del__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_second_func', '_third', 'first', 'first_func', 'second']
>>> getattr(A, "first")
1
>>> getattr(A, "first_func")()
in first func
>>> hasattr(A, "five")
False
>>> setattr(A, "five", 5)
>>> A.five
5
>>> hasattr(A, "five")
True
这一组内建函数为Python的面向对象特性进行了灵活性扩展,不过完全越过了对于类中成员的保护,在使用时需要注意成员访问的权限问题。
作者|lurayvis 撰写初稿,fhk精美更新
来源:华为云社区 作者:lurayvis