第二十七章 调试、配置和优化
27.1调试Python代码
添加print语句并不能取代但不调试。Python调试程序pdb允许设置断点、检查并设置变量,浏览源码。类似于gdb。很多命令也与gdb相同。本书按照如下形式介绍命令:Longway(abbreviation),例如continue(c) ,就是完整命令为continue而简写为c。也可通过help(h)来获得命令列表。
参见附录B了解IDE,提供了更好的调试器。
27.1.1开始和停止调试程序
为了使用调试程序,要导入pdb模块。然后通过如下命令开启调试:
pdb.run(statement[,globals[,locals]])
此处的语句statement是用于执行的代码,同时传递全局名字空间和私有名字空间,以字典的形式存于globals和locals中。
调试程序在确定代码开始运行之后停止,并等待进入。启动调试器之后,运行程序之前可以设置断点。
>>> import pdb
>>> pdb.run('import DebugMe')
> C:/PYTHON20/<string>(0)?()
(Pdb) print "这里可以执行一些命令"
这里可以执行一些调试命令
(Pdb) fred=25
(Pdb) fred
25
通过pdb提示符可以做很多事情。
runeval函数等同于run。只是runeval将返回调试程序中所执行语句的值。runcall(function[,arguments...])调试function函数,并传递参数,最后返回function的返回值。
post_mortem(traceback)函数将进入面向特定回溯的事后分析调试。pm函数开始调试最近的回溯,他是post_mortem(sys.last_traceback)的同义词。
set_trace函数立即调入调试程序。将其置入AssertionError的代码中,将起到一定的作用。
退出调试器可用quit(q)命令。
27.1.2检查状态
list(l)可以列出正在调试的代码。用"list 首行 末行"显示一定的代码区间。默认时显示当前行的上下五行。
where(w)显示当前的栈跟踪,同时通过up(u)和down(d)在堆栈中移动(注意在IDLE命令行解释程序下运行w将显示额外的10条栈帧,那是IDLE自己的。
通过print(p)显示变量的值,其后可以跟表达式,变量,甚至函数。对于直接显示变量值,也可以省略print,直接输入变量名。
下例可以在刚刚运行过的程序中找到错误的行:
>>> import pdb
>>> pdb.pm()
27.1.3设置断点
break(b)设置断点。两种方式:
break [name:]index
在指定函数中设断点,但仅在condition为真时才能实现。应该给予断点以连续ID数,一般从1始。
不带参数的break(b)打印当前所有断点的列表
clear(cl)可清除断点。传递ID值则删除指定断点。不带参数则删除所有断点。
disable禁用而不是删除断点,使之暂时失效。参数同上
enable开启被禁用的断点
ignore id[count]忽略断点ID达到count次。
tbreak指令与break格式相同,设置只启用一次后立即删除的临时断点。
condition id[expr]将把断点ID赋予条件expr,仅在条件为真时断点有效。忽略expr则为无条件断点,同break。
27.1.4运行
continue(c)再一次运行程序
return(r)返回当前函数之前保持程序状态,在返回之前断点
next(n)单步执行,跳过函数
step(s)单步执行,进入函数
27.1.5Aliases
alias[name[command]]将创建一个名为name并可执行的command的alias。alias可以带参量,这些参量将会取代命令中的%1,%2并依此类推,而%*将被所有的参量代替。调用没有传递命令的alias将显示出面向name的当前命令,调用不带参量的alias将列出当前的alias。他们仅与pdb命令行中输入的第一个单词有关。alias是别名的意思,可以代表变量,表达式甚至一段Python语句。在代表python语句时冒号后不必换行,确实需要时可用分号代替换行。
可将alias的定义放在.pdbrc文件中,将此文件放在主目录或当前目录下,用于调试之前设置一些东西。优先执行主目录下的,然后是本地文件。
27.1.6调试技巧
析构器(Destructor)中的程序错误(bug)特别难以捕获。析构器将可抛出的所有异常都放入到stderr中,并且被忽略掉。因此,在调用pdb.set_trace之前,用析构器是个版本:
def __del__(self):
try:
self.cleanup()
except:
#如果无法获取,则只有这种办法了
pdb.set_trace()
程序运行结束后,Python会释放所有已导入模块,之后释放对象的资源。但是对os.remove的调用可能会带来None对象,而None对象不具有删除属性。一个书上每听懂的技巧:"将下划线作为前缀添加到一个模块水平变量的前方,将在其他成员面前将其破坏。"
27.2使用docstrings工作
通过使用docstrings可以将代码和文档保存在一起。也可通过pydoc模块将代码的docstrings提取出来保存到文本或HTML文档中。
还可以交互性的使用pydoc。调用pydoc.help(object)可浏览此对象的Python文档。
同时还可以接受来自命令行的pydoc。参数是不带.py的模块名
python pydoc.py 模块名
使用-w选项将文档写入HTML文件:
python pydoc.py -w urlib
pydoc有着多个选项,没有参数时也可以运行,而且还可作为文档WEB服务器来运行。可以阅读PYTHONPATH中所有模块的文档。
Python1.5以上可以运行pydoc,从Python2.1开始内置pydoc。
27.3自动测试
python通过unittest模块进行自动测试,他也称为PyUnix。而doctest将使文档与代码同步。他们都是Python2.1新增的。
27.3.1使docstrings和代码同步
doctest有利于防止过时的文档。可将一个成功解释程序部分中的文字粘贴到docstrings中,(by gashero)然后运行doctest.testmod(module)来重新运行解释程序部分,确保输出结果一致。
要注意的一点就是需要在模块中导入自己,即:
import 自己的模块
这样是给doctest.testmod(module)提供模块名用的。而在docstrings中最好是从交互模式直接复制而来,包括提示符和程序显示。可以有多个。而doctest模块自动识别提示符之后的命令,并验证是否与输出相符。
一般是在模块的主函数(Python中指在if __name__=="__main__":段内的代码)中调用doctest。如果测试成功则没有任何输出,可用-v选项查看doctest的工作过程。
27.3.2单元测试
unittest模块实际上是Kent Back单元测试结构的Python版本,拥有与JUnitCppUnit同样的杰出血统。
需要创建一个unittest.TestCase的子类用于测试。子类覆盖runTest方法执行测试功能,使用assert标记错误。
class XXXX(unittest.TestCase):
def runTest(self):
Line='one,two,"three,three","fo,ur",five'
assert 函数(Line)=应有的值
可以通过调用TestRunner的运行方法来交互的运行测试。
>>> TestRunner=unittest.TextTestRunner()
>>> TestRunner.run(module.function())
测试完成会有结果提示
还可以接受来自命令行的测试。一种是改变脚本的unittest.main()
if _name_=='_main_':
unittest.main()
接着来自命令行的脚本将运行所有的测试实例。
测试组用于组合相关测试实例的手动方式,支持在列表中添加测试实例的addTest(TestCase)方法。此函数返回一组测试实例:
def CSVSuit():
suite=unittest.TestSuite()
suite.add(function())
suite.add(function())
return suite
如果定义了一个可返回TestCase或TestSuit对象的函数,比如如上,则可像如下一样调用来自子命令行的单元测试:
python unittest.py CSV.CSVSuite
重复测试任务:TestCase类支持在主runTest方法前后调用setUp和tearDown方法。这些方法不需要在代码中重复同一设置以及清除步骤即可建立测试环境。没看懂。
27.4发现瓶颈
Python是高级语言,常用于速度要求不高的场合。有时也需要优化。以下是一些优化的经验法则:
1.最后优化。由于生命周期短,在重写代码或删除代码等优化方法方面不值得花费太多时间。
2.通过在实际运行期间进行测试来检查优化效果。
3.评价除了最明显优化之外的所有优化。这可以帮助其他人理解代码。避免有人为了提高易读性而舍弃某些优化。
27.4.1配置代码
为了快速配置一条语句,导入profile模块,接着调用:
profile.run(statement)
输出结果包括总的调用次数和CPU时间。然后是区分各个函数的调用信息,包括调用次数和原始调用次数ncalls、总运行时间tottime、平均CPU时间percall、此函数及其子函数所耗费的CPU累计时间cumtime、平均积累时间percall=cumtime/ncalls、函数位置信息,包括文件名、行数、函数名,第一行为run传递的代码。
如果在IDLE或者PythonWin中对Python shell窗口进行配置时,那么在IDE的构架中,任何将在标准输出设备中打印出的代码都将激发函数调用。这些函数调用将显示在配置程序的输出中。运行来自命令行的Python将围绕此问题工作。
27.4.2使用Profile对象
Profile类可通过run(command[,globals[,locals]])方法配置指定指令。为了配置函数调用,可用runcall(function[,argument...])。
在运行完后,调用print_stats方法打印出统计或通过dump_stats(filename)将统计(不可读格式)写入指定文件。
调用Profile.run(command[,filename])会创建一个Profile对象。Profile类可以带有子类,比如HotProfile类就是Profile的子类,以更快的速度计算工那嘎少的数据,并忽略调用者与被调用者之间的关系。
27.4.3校准配置程序
在发生事件与配置文件进行记录之间有一小段很小的滞后时间。并且不能自由调用time.clock。在整个函数调用过程中会增加滞后时间,同时降低定时信息的准确性。可通过添加fudge factor来校准配置文件来弥补此滞后。并且可使配置文件的统计更加准确。为了校准配置文件,需调用他的calibrate()方法:
>>> import profile
>>> Prof=profile.Profile()
>>> "%f" % Prof.calibrate(100000)
'0.000017'
所返回的数字就是fudge factor。为了使用这个因此,需要编辑代码库(lib/profile.py),在trace_dispatch方法中把如下:
t=t[0]+t[1]-self.t
替换为:
t=t[0]+t[1]-self.t-(滞后误差)
带有校准的配置文件较为准确一些。但是偶尔会报告某个函数耗时为负数,只是误差不同而已。
27.4.4定制统计
模块pstats提供可用于对配置运行中所收集的统计进行排序和打印的Stats类。
Stats构造函数可带有一个或多个文件名或Profile对象。
例如如下代码将会使一个命令运行多次,还联合统计:
def ProfileSeveralRuns(Command,Times):
if Times<1: return
StatsFiles=[]
for RunIndex in range(Times):
FileName="stats %d"%(RunIndex)
profile.run(Command,FileName)
StatsFiles.append(FileName)
#传递一个文件名用于构造函数
AggregateStats=pstats.Stats(StatsFiles[0])
#传递多个文件名
if len(StatsFiles)>1:
AggregateStats.add(*(StatsFiles[1:]))
AggregateStats.print_stats()
通常可用strip_dirs来整理可访问各个函数文件名的路径。
也可通过调用sort_stats(field[,...])来改变统计的顺序。field是排序字段,按照优先顺序排序,前一个字段相同时才进行第二字段的排序。字母按照升序,数字按照降序,可用reverse_order方法可用来反转顺序。如下列出可用字段:
cumulative 函数累计耗费时间
calls 函数调用总数
time 函数耗费时间(不包括子函数)
name 函数名
file 源文件名
module 源文件名(和file同一意义)
line 源码行数
pcalls 函数的原始调用总数
stdname 标准名字
nfl 名字/文件/行。sort_stats('nfl')等同于sort_stats('name','file','line')
print_stats([restrictions...])方法可打印出此统计。可将多个变量传递给所打印代码行的筛选程序。传递整数n则只打印前n行,传递0到1之间的小数将打印出这些行的百分比。传递一个表达式(字符串)则只打印出文件名与此表达式相匹配的代码行。
此例运行一些代码,然后打印出最耗时的函数统计:
>>> Prof=profile.Profile()
>>> Prof.run('import FancyPrimeFinder')
<profile.Profile instance at 00854E54>
>>> MyStats=pstats.Stats(Prof)
>>> MyStats.sort_stats('time')
<pstats.Stats instance at 007E48DC>
>>> MyStats.strip_dirs().print_Stats(10) #显示前10位
Profile和Stats中的大多数方法将返回对象本身,这使得将几个方法调用链成一行变的容易。
print_callers([restrictions...])方法显示每一函数的调用者。左边是所调用的函数,右边是调用者,括号中是调用次数。类似的,print_callees([restrictions...])在左列中显示出每一函数,在右边则是所调用的函数。
27.5常见优化技巧
以下各节都是加速Python程序的技巧。有时,将函数作为C的扩展模块编写是加速函数的最好方法。
27.5.1排序
对数字和字符串,使用sort()方法进行排序是很快的。如果需要定制排序,如两个对象比较,则可将比较函数传递到sort。也可定义一个_cmp_方法为类定制排序。而将函数传递给sort速度更快一些。
PointList.sort(Point)
PointList.sort(Point._cmp_) #这个更快一些
当对一列对象进行排序时,关键是如何尽快找到排序的关键。并且关键值应该易于排序,如数字,并且最好是独一无二的。
27.5.2循环
使用面向大范围内循环的xrange比range方法需要更少的内存,还可以节省时间。而且两种方式都比while循环更快。也可以通过调用相反的map函数来取消循环。
27.5.3I/O(输入/输出)
每次调用文件的readline方法都非常慢。而调用readlines将文件全部传入内存则快的多,但是占用大量内存。另一个办法是读入整块代码,在Python2.1中可用xreadlines方法:
#1.如下是较慢的方法:
while True:
FileLine=file.readline()
if FileLine=="": break #EOF
DoStuff(FileLine)
#2.快速的方法,但是可能有内存问题
FileLines=file.readlines()
for FileLine in FileLines:
DoStuff(FileLine)
#3.比方法1要快,而不需要太多内存。可用于含此方法的文件对象
while True:
FileLines=file.readlines(100)
if len(FileLines)==0: break #EOF
for FileLine in FileLines:
DoStuff(FileLine)
#4.快速而简单,要求Python2.1
for FileLine in file.xreadlines():
DoStuff(FileLine)
27.5.4字符串
含有串连运算符+的字符串很慢,使用%运算符会更快一些,并使用更少内存。
HTMLString=HTMLHeader+HTMLBody+HTMLFooter #慢
HTMLString=%s%s%s"%(HTMLHeader,HTMLBody,HTMLFooter) #快
如果还不知字符串会有多长,则可考虑使用string.join来联合,而不是加在一起:
#慢方法
Str=''
for X in range(10000):
Str+='X'
#快速方法,至少快了10倍
List=[]
for X in range(10000):
List.append('X')
Str=string.join(List,'')
在使用指定表达式时,可用re.compile而不是直接使用re.search和re.match来创建可重用的指定表达式对象。可节约很多时间,因为解析正则表达式是很慢的。如下例:
PATTERN+r"^[0-9]+(/.[0-9]+)*$" #搜索版本号
ValidDottedDecimal-re.compile(PATTERN)
for Index in range(100):
re.search(PATTERN,'2.4.5.3') #很慢
for Index in range(100):
ValidDottedDecimal.search('2.4.5.3') #快速方法
27.5.5线程
如果脚本只使用一个线程,可通过强迫使解释程序在检查其他线程花费更少的时间来节约部分时间。sys.setcheckinterval(codes)方法将在遍过字节码之后告诉Python考虑切换线程。默认的检查间隔是10,如果设的大一点(如1000)可以改善性能。但在作者的电脑上可以忽略不计。
27.6扔掉废物-回收站
Python不需要明显的分配和释放内存。通过引用计数(reference counting)来清除内存。当没有引用时,会自动释放内存:
>>> class Thingy:
... def __init__(self,Name):
... self.Name=Name
... def __del__(self): #在对象被回收时被Python调用
... print 'Deleting:',self.Name
>>> A=Thingy('x')
>>> A='jifaew'
Deleting: x
>>> A=Thingy('X')
>>> B=Thingy('Y')
>>> A.ref=B #'Y'的引用计数从1到2,ref是A一个成员而已
>>> B=None #'Y'的引用计数变为1
>>>A=None
Deleting: X
Deleting: Y
内置函数del不能删除一个对象,但是可以删除一个变量,并减小引用计数。
27.6.1引用记数和Python代码
引用记数和自动回收站不同(与Java中一样)。当两个对象互相持有对方的引用时,Python将不会释放他们。如果一个对象不再有用,而却没有释放内存,则称为此对象被泄漏了(leaked)。通常只有等待程序结束才会清除泄漏内存。所以要在确定对象无用时手动删除所有引用。
27.6.2引用记数和C/C++代码
在C扩展模块中,必须跟踪每一个Python对象的引用记数。丢失引用计数将会导致内存流失(与内存泄漏相对)。并可能导致内存信息转储。
Py_INCREF(x)和Py_DECREF(x)宏将增加和减少Python对象x的引用记数。可以在C扩展模块中自由的控制引用记数,自己定制是否需要在函数入口增加引用记数或者使用弱引用。
在编写C扩展时,跟踪引用记数是很重要的。Python通过链接开通的Py_REF_DEBUG和Py_TRACE_REFS将提供有关引用记数调试的额外信息。也可通过_Py_PrintReferences打印出所有对象以及他们的引用记数。
27.7小结
略...
完成...
-------------------------------------------------------
第二十八章 安全与加密
28.1密码检查
在要求用户输入密码时,不进行回显,防止别人看到,使用getpass模块。这个模块对不同系统有不同的实现形式,但是至少都会警告用户:密码偶尔可能回显示出来。
Windows版本使用的是msvcrt模块中的getch()函数,但是一些IDE中的示例却有差异。所以如果要测试getpass,需要在命令行解释器上。
使用getpass([prompt])获取密码之后返回密码。如下例:
>>> import getpass
>>> def getLogin():
... name=raw_input('Name: ')
... passwd=getpass.getpass('Password: ')
... return name,passwd
getpass同样具有getuser()函数获取用户的登陆名。比如我的Win2k系统显示为'Administrator'。getuser检查LOGNAME,USER,LNAME, USERNAME环境变量的值,并返回第一个不为空的值。如果所有模块都失败了,而此系统有pwd模块(UNIX),那么将使用此模块,否则异常。
第38章讲了可以访问用户帐户数据库的pwd模块。也可通过crypt模块检查用户输入密码是否正确。
大多数GUI工具包都有自己的用于输入密码的方法。如wxPython就是在一个文字区域中显示星号。
具有readline模块的UNIX模块输入的密码不会记录在命令历史中。而且为了避免漏洞,getpass将使用自己的raw_input实现,叫_raw_input。
28.2在限制环境中运行
当确实需要允许执行别人的Python程序时。
28.2.1rexec沙盒
用于防止他人恶意代码。此模块允许在一个沙盒中运行Python代码。所谓沙盒就是可以控制访问资源的限制环境。比如限制使用socket,限制访问指定目录文件等等。通过rexec,可以更为安全的使用这些来自未必值得信任来源的Python程序。
第34章的ihooks模块还可以实现自定义模块导入行为。
在创建RExec实例之前,可以裁减类变量中的部分来限制功能(而这些类变量的改变不会影响已经创建的实例)。
ok_path是一个数组,包含导入模块时可用于搜索的路径。默认时与sys.path相同。
ok_builtin_modules是一个内置模块数组,这些数组可安全的导入,默认值如下:
audioop imageop parser strop
array marshal regex struct
binascii math pcre time
cmath md5 rotor
errno operator select
ok_posix_names是一个数组,包含允许来自os模块的函数,默认值如下:
error readlink getpid getgid
fstat stat getppid geteuid
listdir times getcwd getegic
lstat uname getuid
ok_sys_names是一个数组,包含来自sys模块的变量和函数,此模块限制访问可用的程序,默认值包含如下:
ps1 copyright platform maxint
ps2 version exit
nok_builtin_names则包含程序不可使用内置函数名的数组。默认包含open、reload、_import_。因此仍然允许访问类似map之类的函数(大多数内置函数相对安全)。
RExec可监听程序对import、reload、unload函数的调用并通过内部的模块加载程序和导入程序。此程序让常见导入使用变成了异常指令(hook)来发送这些调用。可以覆盖rexec的r_reload(module)、 r_unload(modules)、r_import(module[,globals[,locals],fromlist]]]) 来支持定制。如果加载一些不安全的模块r_import将引起ImportError异常。
把对open的调用发送给RExec.r_open(filename[,mode[,bufsize]]) 。默认情况下只能打开只读文件,如果需要可以自己覆盖。
一旦拥有了RExec对象,就可以调用r_eval(code)、r_exec(code)、 r_exec-file(filename)方法在限制对象中执行Python代码。每种方法都将执行新的沙盒中的_main_模块。r_eval可以接受字符串,甚至编译过的代码对象,有返回值。
r_exec可以带有由一行或多行Python代码的字符串组成的参数,也可传递编译过的代码对象。
可在33章了解有关代码对象和Python自我测试(Introspection)。
RExec的add_module(modulename)加载模块并返回模块对象。由于_main_也是一个模块,可作为与正常环境之间的网关。例如传递变量:
>>> r=rexec.RExec()
>>> rmain=r.add_module('__main__')
>>> rmain.happyFactor=10
>>> r.r_eval('happyFactor * 2')
20
对每一个r_<func>方法(如r_eval和r_exec)。RExec也有对应的类似行为的s_<func>方法。只是s_<func>有权访问stdin、stdout、stderr的限制版本。限制版本包含如下方法:
fileno read seek writelines
flush readline tell
isatty readlines write
RExec处理一些安全问题的同时还需考虑一些其他问题。如RExec不能防止代码无限循环和耗尽内存。
28.2.2使用要塞类(class fortress)
通过使用Bastion模块,可以为适合使用rexec的对象创建包装。包装后的对象具有相同属性(by gashero),但是位于限制环境中的代码仅在包装允许访问时才可以访问。
Bastion(object[,filter[,name[,bastionclass]]])
可以创建一个包装,filter接受参数名并在可用时返回True,默认的filter可访问所有不是以下划线开头的属性。如果返回False,则包装会引发AttributeError。name是打印时需要用到的名字,另一个可替代的包装类是bastionclass。
下例是不允许下划线开头和set开头的方法执行。
def noPrivateOrSet(name):
if name[:1]=='_':
return False
if name[:3]=='set':
return False
return True
import Bastion,rexec
e=Environment() #被包装的类,自定义的
be=Bastion.Bastion(e,noPrivateOrSet)
这样再次调用不可访问的主代码时将引发AttributeError异常。
Bastion过滤程序相当于给容易产生漏洞的类加了一个防火墙。限制可访问的内容。而rexec是限制程序的出口。
28.3创建消息指纹
消息摘要Digest类似于数字化指纹或散列信息。原始数据的改变,指纹也会随之改变。用于验证不可靠的媒体传递的数据是否依然正确。
创建一个具有可选择指纹值的文件在数学上不可行。
28.3.1MD5
md5模块实现了MD5消息摘要算法(由MIT和RSA数据安全公司开发)并生成128位的消息摘要。此摘要为字符串类型,任意字节的"长字符串"类型。可通过new([msg])函数创建新md5对象。接着重复调用此对象的update(msg)方法直到已经传递完成所有数据。任何时间调用digest()可得到当时的md5校验和:
>>> import md5
>>> m=md5.new()
>>> data=open('c://temp//skeleton.exe','rb').read()
>>> m.update(data)
>>> m.digest()
'/252/221/205/274/015/317/032/304/207/266/312~S/032/204'
在new(msg)的参数中也可以一次将所有消息传递完成。
消息以二进制形式存在,所以md5的hexdigest()返回摘要的十六进制版本。
如果两个字符串有开始的公共部分,可以先处理公共部分,之后通过copy()方法复制md5对象,然后接着用update(msg)添加数据。
28.3.2SHA
sha模块可实现美国国家标准协会(National Institute of Standard)和计数的安全散列信息运算法则(Technology's Secure Hash Algorithm)。只是略微比MD5慢,但可以生成160位的摘要,有更高的强度。
使用方法相同:
>>> import sha
>>> s=sha.new()
>>> s.update('Python')
>>> s.hexdigest()
'6e3604888c4b4ec08e2837913d012fe2834ffa83'
同样具有update(msg)、digest()、hexdigest()、copy()方法。
28.3.3其他用途
只要消息略有改动就会带来完全不同的摘要。摘要也用于快速比较大的对象。例如图像的摘要用于防止添加图像时的重复。
28.4使用20世纪40年代的加密方法
rotor模块通过使用成组的排列或转子(Rotor)来实现基本的加密方案。就是字符映射,初始的转子位置是解密的关键。
二战中德军的"基于转子的加密方法"就是有名的例子。他们的Enigma(类似于打字机)来传递命令。盟军获取了几台之后最终破解了密码。可在电影《U-571》中看到这个故事的搞笑版本。
rotor.newrotor(key[,numrotors])可创建新Rotor对象。key包含着二进制数。numrotors默认为6,更多的rotors可以加强安全性,但是很耗费资源:
>>> import rotor
>>> r=rotor.newrotor('Will I ever tire of spam?',10)
>>> msg=r.encrypt('Move into position at midnight')
>>> msg
'5/232/001A/267/312/375d/340I/375/201/315}/324/214/311...
>>> r.decrypt(msg)
'Move into position at midnight'
双方都需要密钥。
使用encrypt(msg)和decrypt(msg)分别完成加密和解密。如果消息分为多个部分,可按顺序调用encryptmore(msg)和decryptmore(msg),当然消息段开始之前要初始化一下转子。例如在第一段消息到来时先使用encrypt(msg)来加密,后面的数据都用encryptmore(msg),因为encrypt已经内含了初始化转子的功能。解密也相同。
虽然加密强度不高,也相对容易破解,但是如果隐蔽好一些信息还是很好用的。
28.5小结
略...
完成...
-----------------------------------------------------
第三十七章 Windows平台
37.1使用win32all
37.1.1数据类型
Python的大部分模块是可移植的,但是为了使用一些操作系统功能,也有系统相关的部分。Python Extensions for Windows又称为win32all ,封装了大部分的WindowsAPI。目前win32all保存在ActiveState中,是ActivePython发布的一部分。win32all的软件包包含了一些文档。
WindowsAPI使用结构体的地方,win32all常使用词典,词典的关键字是结构的数据成员的名称,值是对应值。
例如NetUserGetInfo可以返回用户信息:
typedef struct _USER_INFO_10 {
LPWSTR usri10_name;
LPWSTR usri10_comment;
LPWSTR usri10_usr_comment;
LPWSTR usri10_full_name;
}USER_INFO_10;
对应的词典:
>>> win32net.NetUserGetInfo(None,"Administrator",10)
{'full_name':u'','name':u'Administrator','usr_comment':u'', 'comment':u'Built-in account for administering the computer/domains'}
37.1.2错误处理
win32api模块把任何API错误转换为异常win32api.error。这个错误有个成员args,形式为(ErrorCode,FunctionName,Info)。
>>> win32net.NetUserGetInfo(None,'Doctor Frungy',10)
Traceback (innermost last):
File "<pyshell#51>", line 1, in ?
win32net.NetUserGetInfo(None,"Doctor Frungy",10)
api_error: (2221, 'NetUserGetInfo', 'The user name could not be found.')
37.1.3找到自己所需要的
WindowsAPI是很大的,可以参考《Programming Windows》(1998年,Charles Petzold著)。如果对win32all本身有兴趣,可参考《Python Programming on Win32》(2000年,Mark Hammond和Andy Robinson著)。
win32all还是没有包括所有的API,可以自己另外写C扩展,可参见win32all的源码。关于C扩展参见29章。
37.2示例:使用某些Windows API
使用了win32com模块提供的常数,win32help模块提供了帮助系统,win32clipboard模块访问剪贴板。文本编辑器。
复制文本到剪贴板:
def DoCopy(self):
#TextWindow类型是Tkinter.Text
Selection=self.TextWindow.tag_ranges(Tkinter.SEL)
if len(Selection)>0:
SelectedText=self.TextWindow.get(Selection[0],/
Selection[1])
#操作剪贴板之前要先锁住,用后解锁
win32clipboard.OpenClipboard(0)
#SetClipboardText是SetClipboardData(text,CF_TEXT)的简化
win32clipboard.SetClipboardText(SelectionText)
win32clipboard.CloseClipboard()
粘贴文本:
def DoPaste(self):
win32clipboard.OpenClipboard(0)
PasteText=win32clipboard.GetClipboardData(/
win32con.CF_TEXT)
win32clipboard.CloseClipboard()
self.TextWindow.insert(Tkinter.INSERT,PasteText)
调用系统帮助:
def DoHelp(self):
#是间接调用winhelp的API
win32help.WinHelp(0,'Editor.hlp',win32con.HELP_INDEX)
37.3访问Windows注册表
注册表采用树的形式,每个结点称为键。每个键可由一个或多个值。每个顶层键称为配置单元(Hive)。
最后备份好注册表。
37.3.1用win32all访问注册表
为了检查已经存在的键,可用:
win32all.RegOpenKeyEx(Hive,Subkey,0[,Sam])
其中Hive是要打开的键,定义在win32con中:
HKEY_CLASSES_ROOT
HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE
HKEY_USERS
当中的一个。
Subkey是要打开的子键,是字符串。Sam是一个标志组合,表示要访问的级别。通常使用win32con.KEY_ALL_ACCESS,但是如果不打算修改注册表,还可用KEY_READ(默认值)会更安全一些。下值:
常数 | 权限 |
---|---|
KEY_ALL_ACCESS | 完全访问 |
KEY_READ | 读访问 |
KEY_WRITE | 写访问 |
KEY_CREATE_LINK | 创建符号连接 |
KEY_CREATE_SUB_LINK | 创建子键(包括在KEY_WRITE中) |
KEY_ENUMERATE_SUB_KEYS | 子键迭代(包括在KEY_READ中) |
KEY_EXECUTE | 读访问 |
KEY_NOTIFY | 修改访问(包括在KEY_READ中) |
KEY_QUERY_VALUE | 子键读访问(包括在KEY_READ中) |
KEY_SET_VALUE | 修改子键值(包括在KEY_WRITE中) |
这个函数的第一个参数不一定要是个配置单元;可以是任何注册表的键句柄。这时的子键名称应该使用相对路径。
RegOpenKeyEx函数返回一个键句柄,通过这个句柄,可以调用
RegQueryValueEx(KeyHandle,Name)
来检索键值,其中Name是值的名称(如果用''则是默认键值)。返回值形如(Value,ValueType)元组。也可调用:
RegSetValueEx(KeyHandle,Name,0,ValueType,Value)
来设置值。其中ValueType是值的数据类型,见下值:
常数 | 含义 |
---|---|
REG_SZ | 字符串 |
REG_DWORD | 32位整数 |
REG_BINARY | 二进制数据 |
REG_MULTI_SZ | 字符串数组 |
完成了注册表键之后,要关闭:
RegCloseKey(KeyHandle)
访问远程Windows的注册表,先获取:
RegConnectRegistry(SystemName,Hive)
其中Hive是来自win32con的配置单元常数之一,除了HKEY_CLASSES_ROOT或HKEY_CURRENT_USER之外。参数SystemName是形如//computername的字符串
37.4示例:设定IE主页
设置Internet Explorer的主页:
import win32api
import win32con
SubKey="SOFTWARE//Microsoft//Internet Explorer//Main"
StartPageKey=win32api.RegOpenKeyEx(/
win32con.HKEY_CURRENT_USER,SubKey,0,/
win32con.KEY_ALL_ACCESS)
(OldURL,ValueType)=win32api.RegQueryValueEx(StartPageKey,/
"Start Page")
Print OldURL
NewURL="http://www.google.com"
win32api.RegSetValueEx(StartPageKey,"Start Page",0,/
win32con.REG_SZ,NewURL)
win32api.RegCloseKey(StartPageKey)
37.4.1键的创建、删除和定位
win32api函数RegCreateKey(Hive,Subkey)在指定配置单元创建一个子键,并返回新键的句柄。
RegDeleteKey(Hive,SubkeyName)删除指定键,不可删除含子键的键
RegDeleteValue(KeyHandle,Name)删除指定键的指定值
RegEnumKey(KeyHandle,Index)检索指定键的子键的名称。如果该键没有指定索引的子键,则产生一个异常(win32api.error)。
RegEnumValue(KeyHandle,Index)检索指定键的值。返回一个元组,形如(ValueName,Value,ValueType)。
也可用RegQueryInfoKey函数迭代而不触发异常。
37.4.2示例:键的递归删除
删除注册表项的函数,与RegDeleteKey不同的是,可以递归删除含有子键的键。KillKey.py
import win32api
import win32con
def KillKey(ParentKeyHandle,KeyName):
KeyHandle=win32api.RegOpenKeyEx(ParentKeyHandle,/
KeyName,win32con.KEY_ALL_ACCESS)
while True:
try:
#将子键设为0,因为旧的删除之后下一个变为0
SubKeyName=win32api.RegEnumKey(KeyHandle,0)
except:
break
KillKey(KeyHandle,SubKeyName)
print "Deleting",KeyName
win32api.RegDeleteKey(ParentKeyHandle,KeyName)
#示例,删除一些键
RootKey=win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE,/
"SYSTEM",win32con.KEY_ALL_ACCESS)
win32api.RegCreateKey(RootKey,"Junk")
win32api.RegCreateKey(RootKey,"Junk//Stuff")
win32api.RegCreateKey(RootKey,"Junk//Stuff//Wooble")
win32api.RegCreateKey(RootKey,"Junk//Stuff//Weeble")
win32api.RegCreateKey(RootKey,"Junk//More stuff")
#删除如上的键
KillKey(RootKey,"Junk")
37.4.3其他注册表函数
RegQueryInfoKey(KeyHandle)把键的元数据返回到一个元组,形如(SubKeyCount,ValueCount,ModifiedTime)。其中ModifiedTime如果不为0,则是上次修改时间,从1600年1月1日开始经过的ns数的1/100计算的。
对注册表的修改不会立即生效。而是在关闭注册表键的一段时间后生效,调用RegFlushKey(KeyHandle)可以立即生效。
RegSaveKey(KeyHandle,FileName)将会把注册表的键(及其子键)保存到文件当中。
RegLoadKey(Hive,SubKey,FileName)可以从硬盘恢复注册表(by gashero)设置。但是必须自己激活特权,详见win32security API文档。
37.4.4用_winreg访问注册表
标准库_winreg也封装了Windows注册表API。因为基础的API是相同的,所以接口很相似。如下对应:
_winreg函数 | win32api函数 |
---|---|
CloseKey | RegCloseKey |
ConnectRegistry | RegConnectRegistry |
CreateKey | RegCreateKeyEx |
DeleteKey | RegDeleteKey |
DeleteValue | RegDeleteValue |
EnumKey | RegEnumKey |
EnumValue | RegEnumValue |
FlushKey | RegFlushKey |
LoadKey | RegLoadKey |
OpenKey | RegOpenKeyEx |
QueryInfoKey | RegQueryInfoKey |
QueryValueEx | RegQueryValueEx |
SaveKey | RegSaveKey |
SetValueEx | RegSetValueEx |
37.5使用msvcrt goodies
msvcrt模块封装了一些Windows上的组件,来自于VC++的运行时库。
37.5.1控制台I/O
调用sys.stdin.readline可以读取用户的输入一行。用Curses可以处理单个字符的输入,在大多数UNIX系统上可用。在Windows系统上提供了类似的功能。
函数getch()读取用户的一次按键,并返回结构字符串。同步方法,会阻塞。Ctrl+Break键会中止getch()的处理:
import msvcrt
while True:
print msvcrt.getch()
按下特殊键,比如F1会在按键缓冲区中放入2个字符,第一个是换码字符,如chr(0)或chr(224),两个字符共同构成特殊键编码。
在getch()函数运行时,即使是win32下的Ctrl+Z、Ctrl+C、Esc等等均无法停止这个函数。而如上例的Ctrl+Break会直接停止解释器退出。
函数ungetch(char)与getch()相反,把一个字符放回按键缓冲区。每次只能放一个字符。如果按键缓冲区正有字符在等待,则kbhit()返回True。函数putch(char)将特定字符串写到控制台而无需缓冲。
如下代码慢慢的输出一些文字:
for Char in "Hello, there!":
time.sleep(0.1)
msvcrt.putch(Char)
37.5.2其他函数
函数setmode(FileDescriptor,Flag)将指定文件设为行结束转换模式。其中FileDescriptor是文件描述符,与os.open返回的相同。Flag是os.O_TEXT或os.O_APPEND、os.O_RDONLY。
函数get_osfhandle(FileDescriptor)可为指定的文件描述符提供一个文件句柄。
函数heapmin整理堆栈,释放不用的块以便使用,只可以在NT/2000下使用,不可用于Win9x系统。
37.6小结
略...
完成...