[《Python2.1宝典》笔记] 27、28、37章

第二十七章 调试、配置和优化

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是用于执行的代码,同时传递全局名字空间和私有名字空间,以字典的形式存于globalslocals中。

调试程序在确定代码开始运行之后停止,并等待进入。启动调试器之后,运行程序之前可以设置断点。

>>> 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并可执行的commandaliasalias可以带参量,这些参量将会取代命令中的%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

如果定义了一个可返回TestCaseTestSuit对象的函数,比如如上,则可像如下一样调用来自子命令行的单元测试:

python unittest.py CSV.CSVSuite

重复测试任务:TestCase类支持在主runTest方法前后调用setUptearDown方法。这些方法不需要在代码中重复同一设置以及清除步骤即可建立测试环境。没看懂。


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行,传递01之间的小数将打印出这些行的百分比。传递一个表达式(字符串)则只打印出文件名与此表达式相匹配的代码行。

此例运行一些代码,然后打印出最耗时的函数统计:

>>> 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

ProfileStats中的大多数方法将返回对象本身,这使得将几个方法调用链成一行变的容易。

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循环

使用面向大范围内循环的xrangerange方法需要更少的内存,还可以节省时间。而且两种方式都比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.searchre.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'的引用计数从12refA一个成员而已

>>> 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_DEBUGPy_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检查LOGNAMEUSERLNAMEUSERNAME环境变量的值,并返回第一个不为空的值。如果所有模块都失败了,而此系统有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则包含程序不可使用内置函数名的数组。默认包含openreload_import_。因此仍然允许访问类似map之类的函数(大多数内置函数相对安全)

RExec可监听程序对importreloadunload函数的调用并通过内部的模块加载程序和导入程序。此程序让常见导入使用变成了异常指令(hook)来发送这些调用。可以覆盖rexecr_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)

RExecadd_module(modulename)加载模块并返回模块对象。由于_main_也是一个模块,可作为与正常环境之间的网关。例如传递变量:

>>> r=rexec.RExec()

>>> rmain=r.add_module('__main__')

>>> rmain.happyFactor=10

>>> r.r_eval('happyFactor * 2')

20

对每一个r_<func>方法(r_evalr_exec)RExec也有对应的类似行为的s_<func>方法。只是s_<func>有权访问stdinstdoutstderr的限制版本。限制版本包含如下方法:

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,则包装会引发AttributeErrorname是打印时需要用到的名字,另一个可替代的包装类是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消息摘要算法(MITRSA数据安全公司开发)并生成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)的参数中也可以一次将所有消息传递完成。

消息以二进制形式存在,所以md5hexdigest()返回摘要的十六进制版本。

如果两个字符串有开始的公共部分,可以先处理公共部分,之后通过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 HammondAndy 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)

#SetClipboardTextSetClipboardData(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):

#是间接调用winhelpAPI

win32help.WinHelp(0,'Editor.hlp',win32con.HELP_INDEX)


37.3访问Windows注册表

注册表采用树的形式,每个结点称为键。每个键可由一个或多个值。每个顶层键称为配置单元(Hive)

最后备份好注册表。

37.3.1win32all访问注册表

为了检查已经存在的键,可用:

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_ROOTHKEY_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,则是上次修改时间,从160011日开始经过的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+ZCtrl+CEsc等等均无法停止这个函数。而如上例的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返回的相同。Flagos.O_TEXTos.O_APPENDos.O_RDONLY

函数get_osfhandle(FileDescriptor)可为指定的文件描述符提供一个文件句柄。

函数heapmin整理堆栈,释放不用的块以便使用,只可以在NT/2000下使用,不可用于Win9x系统。


37.6小结

...


完成...



 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值