第七章 面向对象的Python
7.1概述面向对象的Python
Python从一开始就支持面向对象,Python的支持也比较简明。数据成员,方法成员,继承。
7.2创建类和实例对象
使用class关键字,其下直接写文档字符串,并用"对象.__doc__"引用文档字符串。每个方法的第一个参数必须是self,这是对对象本身的引用。所有的方法必须在对象的实例上运算。
构造方法:def __init__(self,...):
对象可以包含两种数据成员,直接在类之内,方法之外的是类变量,不属于某个实例,任何一个实例对她的更改也会反映到属于同一个类的其他实例上。另外就是实例变量,定义在方法内,用self引用。
class Wallet: #类声明
balance=0 #类变量
def __init__(self,count): #构造函数
pass
def getMoney(self): #方法
return self.m #实例数据成员m
def setMoney(self,m):
self.m=m
7.2.1创建实例对象
对象=类(参数)
使用类名作为函数名来创建类,并返回实例,参数(by gashero)将会传递到__init__方法。之后用圆点访问对象的属性和方法。
类的实例使用词典__dict__保存属性和对应的值。这样obj.attribute与obj.__dict__['attribute']相同。
每个对象还包含一些固定的属性。
__name__类名,Wallet
__module__所属模块名,__main__
__class__定义类的位置,<class __main__.Wallet at 010C1CFC>
__doc__文档字符串,"..."
7.2.2有关访问属性的更多信息
任何时候都可以添加,删除和修改类的属性和对象。直接给一个以前未定义的属性赋值就创建了属性。删除用del 对象.属性。修改就是再赋值。
修改类定义会影响所有实例。即运行时用类名给一个以前未定义的属性赋值,则所有所属对象都可以使用这个属性了。即便是类和对象有同名属性,则在通过对象访问时是优先访问对象,即便是通过赋值创建一个属性也是优先给对象创建一个属性而不影响类的同名属性。类也可以作为C语言中struct的灵活实现。
可以使用函数类访问类的属性:
getattr(obj,name[,default])
hasattr(obj,name)
setattr(obj,name,value)
delattr(obj,name)
方法也可以有属性,但用于引用属性时不要加()。
>>> class SomeClass:
... def deleteFiles(self,mask):
... os.destroyFiles(mask)
... deleteFiles.htmldoc='<bold>Use with care!</bold>'
>>> hasattr(SomeClass.deleteFiles,'htmldoc')
True
>>> SomeClass.deleteFiles.htmldoc
'<bold>Use with care!</bold>'
方法是Python2.1中的新增特性
7.3从其他类中派生新类
class 子类(父类[,父类2]):
子类(SuperMotorcycle)继承了其父类(GetAwayVehicle)的属性,并可以直接使用这些属性。子类中还可以定义自己的属性。对同名属性,子类的屏蔽父类的。
7.3.1多继承
当派生一个新子类时,不会只限制一个父类,即多继承。当搜索一个属性在当前对象找不到时在搜索所属类,之后是广度优先的向上级类搜索,对同一个层次的父类从左到右搜索。
使用__bases__属性返回序列包含各个层次的父类,前面加上模块名。
使用多继承是不利于理解的,所以最好回避,当需要用到多继承时首先应该考虑重新设计。
多继承的优秀例子是mix-ins。他是超越类的某些部分,以便定制行为的小类。主要用于Socket等编程。
7.3.2创建定制的列表类
UserList类位于UserList模块,提供了一个可以扩展的列表。创建时接受一个列表存放在内部的data成员。可以自己给子类添加成员完成不同的输出。
7.3.3创建定制的字符串类
使用UserString模块中的UserString类。扩展子类。data成员是原字符串。
UserString模块中还提供了MutableString类。可以修改字符串中的某个字符。
>>> m=MutableString('2+2=5')
>>> m
'2+2=5'
>>> m[4]='4'
>>> m
'2+2=4'
MutableString通过超越__setitem__方法来完成他的魔数。此方法是Python调用的特殊方法,用于处理上述基于索引的赋值。后面介绍。
7.3.4创建一个定制的词典类
在UserDict模块中引入了UserDict类,给用户扩展,同样提供data存储原词典。
下面的例子创建一个词典对象,使用不存在的键读取时返回None,而不再是返回异常。
>>> from UserDict import *
>>> class NoFailDict(UserDict):
... def __getitem__(self,key):
... try:
... value=self.data[key]
... except KeyError:
... value=None
... return value
7.4隐藏私有数据
其他语言中都提供一定的类封装机制。但Python认为私有数据成员不实用,所以没有广泛的使用,但仍然支持。
双下划线开头的变量无法被外部访问,子类也无法访问。但实际上Python只是通过以损坏的名字_className__attrName来访问。我们也可以这样访问,但是很没有必要。
7.5识别类成员
类定义以及实例对象都包含自己的数据类型。(by gashero)由于类型是实例或类,所有的类定义具有相同的类型,所有的实例对象也都是具有相同的类型。查看一个对象是否是一个类的实例用isinstance(obj,class)。检查子类的关系用issubclass(class,class)
检查各级的类关系也可以用__name__属性:
>>> seeding.__class__.__name__
'Oak'
>>> seeding.__class__.__bases__[0].__name__
'Tree'
Python对象可以在运行时动态添加属性甚至是方法指针,所以增加了动态使用的灵活性。
7.6重载标准行为
对应于C++中的运算符重载。比如添加一个__add__方法完成对象的加法,对应于加号运算符:
>>> class Vector:
... def __init__(self,a,b):
... self.a=a
... self.b=b
... def __str__(self):
... return 'Vector(%d,%d)' %(self.a,self.b)
... def __add__(self,other):
... return Vector(self.a+other.a,self.b+other.b)
>>> v1=Vector(2,10)
>>> v2=Vector(5,-2)
>>> print v1+v2
Vector(7,8)
如上的例子就提供了加法运算符的重载还有str()函数的重载。
operator模块提供了很多函数可供重载,也可以为自己的类定义新行为。另外加法还可以定义几种不同的行为。如__add__、__iadd__、 __radd__就是分别对应(x+y)、(x+=y)、x不包含__add__方法时用于x+y。但是假如不是非常必要最好还是不要用运算符重载,不利于理解。
7.6.1重载的基本功能
下表列出一些可供重载的功能:
__init__(self[,args...]) obj=className(args)
__del__(self) del obj
__call__(self[,args...]),callable function obj(5)
__getattr__(self,name) obj.foo
__setattr__(self,name,value) obj.foo=5
__delattr__(self,name) del obj.foo
__repr__(self) 'obj' or repr(obj)
__str__(self) str(obj)
__cmp__(self,x) cmp(obj,x)
__lt__(self,x) obj<x
__le__(self,x) obj<=x
__eq__(self,x) obj==x
__ne__(self,x) obj!=x
__gt__(self,x) obj>x
__ge__(self,x) obj>=x
__hash__(self) hash(obj)
__nonzero__(self) nonzero(obj)
注意,采用del语句,如果对象的引用计数最后变为0则不会调用__del__方法。
有人把你的对象实例当作函数对待时就调用__call__方法,使用callable(obj)函数,用户可以测试“调用能力”,该函数确定这个对象是否可调用,返回True/False。
Python只在通过实例词典搜索之后调用__getattr__函数,而且基类会发生空处理。如果属性不存在应该产生AttributeError异常。在用__setattr__函数赋值时应该确保赋值给实例变量,而不是self.__dict__[name]=val。防止递归的__setattr__。如果自定义类包含了__setattr__方法,则Python总是会调用他设置的成员变量值,即使实例词典已经包含要设置的变量也一样。
hash函数和cmp函数密切相关。没有__cmp__则不可实现__hash__。如果没有__hash__而有__cmp__,则实例的对象不可作为词典键。散列值是32位整数,并且认为相等的值应该拥有相等的散列值。
nonzero函数完成了真值测试,一边实现返回0或1。如果没有实现则查找__len__实现,如果还是没有则所有对象实例看作True值。
__lt__,__gt__和其他方法可以实现rich comparisons的支持,对于这种比较,可以在不同类型的比较之间完全的控制对象的行为。如果存在的话,Python会在查找__cmp__方法之前调用这些方法。例:
>>> class Simple:
... def __cmp__(self,obj):
... print '__cmp__'
... return True
... def __lt__(self,obj):
... print '__lt__'
... return False
>>> s=Simple()
>>> s<5
__lt__ # Python uses rich comparisons first.
False
>>> s>5
__cmp__
True # Uses __cmp__ if there are no rich comparison.
rich comparison方法可以返回return NotImplemented,以便通知Python并不想处理特殊的比较。
尽管__cmp__方法一定返回一个整数,来表示比较的结果,但是rich comparison方法可以返回任意类型,而且当特殊的比较无意义时,产生异常。
rich comparisons是Python2.1中的新特性。
7.6.2重载数字运算符
以便响应一些数学运算,如果左侧运算符并不包含定义(__add__)的方法,则会调用右侧的运算符(__radd__)。
我的例子:测试add、radd、iadd:
class Add:
def __init__(self,val): # a=Add(5)
self.val=val
def __add__(self,obj): # a+5
print 'add',obj
return self.val+obj
def __radd__(self,obj): # 5+a
print 'radd',obj
return self.val+obj
def __iadd__(self,obj): # a+=5,之后的a变为整数型
print 'iadd',obj
return self.val+obj
可供重载的运算符:
__add__(self,obj)、__radd__、__iadd__ obj+10.5
__sub__(self,obj)、__rsub__、__isub__ obj-16
__mul__(self,obj)、__rmul__、__imul__ obj*5.1
__div__(self,obj)、__rdiv__、__imul__ obj/15
__mod__(self,obj)、__rmod__、__imod__ obj%2
__divmod__(self,obj)、__rdivmod__ divmod(obj,3)
__pow__(self,obj[,module])、__rpow(self,pow) pow(obj,3)
__neg__(self) -obj
__pos__(self) +obj
__abs__(self) abs(obj)
__invert__(self) ~obj
7.6.3重载序列和词典运算符
用于创建了自己的序列或映射数据类型。
__len__(self) len(obj)
__getitem__(self,key) obj['cheese']
__setitem__(self,key,value) obj[5]=(2,5)
__delitem__(self,key) del obj['no']
__setitem__(self,i,j,sequence) obj[1:6]='Hello'
__delslice__(self,i,j) del obj[5:7]
__contains__(self,obj) x in obj
对__getitem__(self,key)中的key还可以有三个参量用于支持切片操作。可选为整型或key.start、key.stop、key.step。如果传递的key有类型错误则你的实现应该抛出TypeError异常:
raise TypeError
当然也可以按照索引抛出IndexError异常。
7.6.4重载按位运算符
__lshift__(self,obj)、__rlshift__、__ilshift__ obj<<3
__rshift__(self,obj)、__rrshift__、__irshift__ obj>>1
__and__(self,obj)、__rand__、__iand__ obj&17
__or__(self,obj)、__ror__、__ior__ obj | obj
__xor__(self,obj)、__rxor__、__ixor__ obj ^ 0xFE
7.6.5重载类型转换
用作强制转换到一些常见类型:
__int__(self) int(obj)=53
__long__(self) long(obj)=22L
__float__(self) float(obj)=3.5
__complex__(self) complex(obj)=2+3j
__oct__(self) oct(obj)='012'
__hex__(self) hex(obj)='0xFE'
如果存在的话,Python就会调用__coeroe__(self,obj)方法,在运算之前统一数字类型。实现应该返回一个2项元组,包含转换类型后的self和obj,如果不支持这种转换则返回None。
7.7使用弱引用
Python使用了自动垃圾收集。使用引用计数。计数到达0时撤消对象。
弱引用就是当引用计数为0时如果只剩下了弱引用则还是会撤消。即弱引用是不计入引用计数的。使用weakref模块。是Python2.1的新特性。
7.7.1创建弱引用
通过调用weakref模块内的ref(obj[,callback]),就可以创建弱引用。其中obj是想要引用的对象。callback是一个可选的函数对象,用于当Python需要进行撤消对象时所回调的一个函数。此callback函数只获取一个参数就是弱引用的对象。
下例是对Socket的一个弱引用:
>>> ref=weakref.ref(a)
>>> from socket import *
>>> import weakref
>>> s=socket(AF_INET,SOCK_STREAM)
>>> ref=weakref.ref(s)
>>> s
<socket._socketobject instance at 007B4A94>
>>> ref
<weakref at 0x81195c; to 'instance' at 0x7b4a94>
>>> ref() #call it to access the reference object.
<socket._socketobject instance at 007B4A94>
一旦没有对于对象的更多引用,Python就会撤消对象,而弱引用返回None。通过弱引用,很多对象是不可访问的。
weakref模块中的getweakrefcount(obj)和getweakrefs(obj)两个函数返回弱引用数目和对象的一列引用。
弱引用可用于创建对象的告诉缓存,创建代价很高。可以声明一个弱引用序列(如列表或词典),来保存对象的弱引用,这些对象是可以用于共享和重用的,并且这个列表是不影响对象的释放的。如一个固定连接地址的socket可以防止反复连接同一个地址。例:
import weakref
from socket import *
socketCache={}
def getSocket(addr):
'Returns an open socket object'
if socketCache.has_key(addr):
sock=socketCache[addr]()
if sock:
return sock
sock=socket(AF_INET,SOCK_STREAM)
sock.connect(addr)
socketCache[addr]=weakref.ref(sock)
return sock
weakref模块中的mapping([dict[,weakkeys]])函数返回一个弱词典(用可选词典dict中的值初始化)。如果weakkeys为0(默认值),词典就会自动删除如下项,即他的值不再包含对她的强引用。如果weakkeys不为0,则词典就会自动删除某些项,即他的键不再包含对他的强引用的项。
7.7.2创建代理对象
代理对象是弱引用对象,其行为与他们引用的对象类似。这样就不必先使用弱引用获得基本对象,再操作基本对象。而是通过weakref的proxy(obj[,callback])函数创建代理。使用代理对象就如同使用他引用的对象一样,而不计入引用计数。
callback参数与ref函数的作用相同。在Python删除了引用的对象之后,可使用weakref.ReferenceError中的代理结果。
Python的2.1版本是发现有垃圾时理解清除,以后的版本可能会有所改变。
7.8小结
略...
完成...
------------------------------------------------------
第八章 输入和输出
8.1打印到屏幕
产生输出最简单的方式就是使用print语句,转换成字符串后输出。打印每个表达式之前使用str函数转换成字符串。如果不想在输出时各个由逗号分隔的表达式之间有空格,可以手动转换成字符串后用字符串连接,就是使用加号。
如果在输出的语句行末加了一个结尾逗号,则print语句不会输出换行。
Python使用stdout(sys模块)的softspace属性跟踪是否要在打印下一项之前输出一个空格。所以利用这个特性可以去掉逗号产生的空格。
>>> import sys
>>> def joinEm(a,b)
... print a,
... sys.stdout.softspace=0
... print b
>>> joinEm('Thanks','giving')
Thanksgiving
print语句的扩展格式允许把输出重定向到文件,从Python2.0开始
>>> print >> sys.stderr,"File Not Found!"
File not found
8.2访问键盘输入
8.2.1raw_input
raw_input([prompt])函数用于从标准输入读取一行,并以字符串的格式(删除末尾的换行符)返回。
读取的参数作为提示。读取时如果遇到文件末尾则产生EOFError异常。
8.2.2input
input([prompt])函数等效于raw_input,只是假定输入都是有效的表达式而返回计算的值。
>>> input('Enter some Python:')
Enter some Python:[x*5 for x in range(2,10,2)]
[10,20,30,40]
注意input根本不能防止错误,如果传递的表达式有误,则会抛出一定的异常。
第38章会介绍UNIX系统的readline模块提供历史跟踪和完成。
在支持curses的系统上(典型为UNIX),可使用curses模块提供一次读取多个字符,而不必是一行,用getch函数。在Windows下用msvcrt模块的getch函数。见37章。
8.3打开、关闭及定位文件
8.3.1open
读文件之前先要打开open(name[,mode[,bufsize]])
mode参数模式,是一个字符串(对应C中的fopen函数的模式参数)。
值 | 说明 |
---|---|
R | 打开,用于读操作 |
W | 创建一个文件,用于写,覆盖以前文件 |
A | 打开,尾部添加,如不存在则创建 |
r+ | 打开,进行读写操作(文件必须存在) |
w+ | 创建新文件,尽心读写,覆盖以前文件 |
a+ | 打开,读和尾部添加,文件不存在时创建 |
如果不指定模式,默认为r。在模式之后还可以添加一个参数,t用于文本模式,b用于二进制模式。如'w+b'。省略缓冲大小bufsize则使用默认的。0用于非缓冲,1指一次缓冲一行,其他指定缓冲区大小,但是有些系统会向下舍入到2的最近次幂。
open函数会产生IOError异常。
os模块提供了fdopen、popen、popen2、popen3函数,以附加的方式获得文件对象,见第10章。利用socket.makefile函数也可以创建一个类似文件的套接字对象,见15章。
8.3.2文件对象信息
拥有了文件对象以后可以用name、fileno()、isatty()、mode、 closed来获取对象状态信息。
name是文件名,mode是文件打开模式字符串,closed是否已经关闭,已经关闭的为True。isatty()是判断是否连接到一个终端,filono()获取文件描述数字,用于其他系统相关大的操作。
8.3.3close
文件对象的close()方法用于刷新写入信息,并关闭文件。
>>> f=open('foo.txt','wt')
>>> f.write('Foo!')
>>> f.close()
8.3.4文件位置
tell()方法返回文件指针的当前位置。
seek(offset[,from])方法用于更改文件的当前位置。from参数表示位置的相对值,0表示从开始搜索,默认;1表示相对当前位置;2表示相对文件末尾。
在Windows系统上以文本模式打开的文件会自动把'/n'转换成'/r/n' 。
8.4写文件
write(str)方法把字符串写到打开的文件中(Python字符串可能包含二进制数据,而不仅仅是文本。write自动把换行符'/n'添加到字符串末尾。
writelines(list)方法输出字符串列表,写入文件。但是不会在每个字符串行末加入换行。
与stdout一样,所有的文件对象都有softspace属性,决定是否在各个输出的数据之前加入空格,可以修改来去掉这个空格等。
truncate([offset])方法用于删除从文件当前位置到文件尾的所有内容,也可以指定位置。
flush()方法将缓存数据写入到文件。
8.5读文件
read([count])方法将返回从文件读出的一定数目的字节,如果到了文件末尾可能会返回比较少的字符。如果未指定自己数则将返回文件剩余的字节。
readline([count])方法返回一行信息,其中包括末尾的换行字符。通过指定count,让此方法返回指定数目的字节或者整行,哪个先发生就先返回哪一个。
readlines([sizehint])方法重复调用readline,返回一列读取的行。sizehine参数限制了一次读入内存的数据,而不是一直到文件尾。
读到文件末尾时read和readline会返回空字符串''。readline方法将返回空列表[]。
下面的语句一次完成了打开文件,读取所有行,并删除换行字符:
>>> [x.strip() for x in open('read.txt').readlines()]
readlines方法一个缺点是将列表返回之前必须把整个文件读入内存(如果没有指定sizehints参数则不得不一次又一次调用readlines直到直到文件末尾)。xreadlines相类似,但是只在需要的时候才把数据读入内存:
>>> for line in open('read.txt').xreadlines():
... print line.strip().upper()
xreadlines函数是Python2.1中的新特性。
8.6访问标准I/O
sys模块提供了3个总能够使用的文件对象:stdin、stdout、stderr 。有些IDE,如PythonWin实现自己版本的stdin、stdout、input等等。所以重定向会产生不同的行为,自己实验一下。
input和raw_input会从stdin中读取,print会写到stdout。所以重定向输入和输出的一种容易方式就是将自己的文件对象放到其中:
>>> import sys
>>> sys.stdout=open('fakeout.txt','wt')
>>> print "Now who's going to the restaurant?"
>>> sys.stdout.close()
>>> sys.stdout=sys.__stdout__
>>> open('fackout.txt').read()
"Now who's going to the restaurant?/012"
这三个标准I/O文件的默认值位于sys的__stdin__、__stdout__、 __stderr__中;并且他们是很好的Pythonista,当读者胡乱操作时可以使变量自动恢复默认值。
经由os.system或os.popen启动的外部程序不会搜索sys.stdin或sys.stdout。他们的输入和输出来自常规源,而不管自己做过如何更改。
8.7使用类似文件的对象
很多函数要求传递文件对象,但是通常不要求行为类似于文件的对象。下面是一个实现输出翻转的例子:
>>> import sys,string
>>> class Reverse:
... def write(self,s):
... s=list(s)
... s.reverse()
... sys.__stdout__.write(string.join(s,''))
... sys.__stdout__.flush()
一种检测输入是来自控制台还是批处理调用:
import sys
if sys.stdin.isatty():
print '解释器模式' #包括从控制台直接运行
else:
print '批处理模式' #被其他脚本调用,或输入重定向
通过sys.stderr=Reverse()类似的方法来更改错误输出的方向。
Python里面实现一个具有文件类似行为或部分行为的类不需要其他语言中的实现接口,继承基类等等,只要自己实现对象的对应方法即可。
可以自己编写类替换标准I/O来用于GUI程序的输出显示等等。
自从Python2.1开始,可以在某个实现readlines方法的类似文件的对象周围创建xreadlines对象:
import xreadlines
obj=SomeFileLikeObject()
for line in xreadlines.xreadlines(obj):
... do some work ...
8.8小结
略...
完成...
------------------------------------------------------
第十章 处理文件和目录
10.1检索文件和目录信息
大多数操作系统将文件存储在目录树中。区分相对路径和绝对路径。
os模块提供了很多操作系统的接口,而os.path模块提供了从给定路径获取其他信息的能力。可以移植。例如为了将文件名和目录名连接在一起,并且具有可移植性可用如下:
>>> print os.path.join('maps','aa.bmp')
maps/aa.bmp #在Windows系统下
>>> print os.path.join('maps','aa.bmp')
maps/aa.bmp #在UNIX类系统下
可以检查os.name得知加载了哪个操作系统的模块,但应该尽可能少使用。
10.1.1逐点方法
access(path,mode)函数测试当前进程是否具有读写可执行指定路径的许可权。模式参数可以是os.R_OK(读权限)、os.W_OK(写权限)、os.X_OK(可执行权限)的组合。也可以用os.F_OK模式确定路径是否存在。也可用os.path.exists(path)函数
access的反函数是os.chmod(path,mode),给当前用户设置路径的模式。模式参数是数字,组合下表八进制数字而成。注意是八进制,一定要有前导0。
值 | 说明 |
---|---|
0400 | 所有者 读 |
0200 | 所有者 写 |
0100 | 所有者 可执行,搜索目录 |
0040 | 组成员 读 |
0020 | 组成员 写 |
0010 | 组成员 可执行,搜索目录 |
0004 | 其他人 读 |
0002 | 其他人 写 |
0001 | 其他人 可执行,搜索目录 |
不同操作系统的permission的方式会不同,依赖特殊性为之前,应该测试一下。
函数os.path.isabs(path),如果给定路径是绝对路径则为True。
os.path的四个函数isdir(path)、isfile(path)、islink(path)、 ismount(path)用于测试文件系统入口种类。安装点mount测试两个文件系统的连接位置。在Windows下ismount对c:/或//endor/的路径返回True。
信息结点是UNIX文件系统数据结构,它保存了有关目录入口的信息。每个目录入口都是由设备编号和信息结点编号唯一标志的。下面的某些例程会返回信息结点编号,仅对UNIX有效,其他系统无效。
os.path.getsize(path)获取检索文件的大小
os.path.getatime(path)和os.path.getmtime(path)分别获取路径的最后一次访问时间(by gashero)和修改时间。以1970年开始的秒数。可用time模块的ctime()函数转换为标准时间。
os.utime(path,(sec,sec))设置"接触"时间,如果(sec,sec)设为None则为当前时间
兼容UNIX的系统都包含os.chown(path,userID,groupID)函数用于改变路径的所有权。例如:
os.chown('a.png',os.getuid(),os.getgid())
非Windows系统包括os.path.samefile(path1,path2)和os.path.sameopenfile(f1,f2)两个函数,如果给定的路径或文件对象指向同一个入口,即驻留在同一个设备的同一个信息结点,将返回真。用于检测软硬链接文件。
10.1.2I-want-it-all方法
可以调用os.stat(path)返回一个10元素元组包含多种信息,前一节的多数函数也是在后台调用这个函数并丢弃一些成分。返回的元组可用一些有特殊意义的索引来发现成分的意义。stat是一个独立于os模块的包,使用前要用import stat导入。如下表:
ST_SIZE 文件大小/字节
ST_ATIME 最后访问时间(1970)
ST_MTIME 最后修改时间(1970)
ST_MODE 模式(可用值见后)
ST_CTIME 最后一次更改状态的时间(访问,修改,chmod,chown)
ST_UID 所有者用户ID
ST_GID 所有者组ID
ST_NLINK 信息结点链接数
ST_INO 信息结点编号
ST_DEV 信息结点的设备
例如获取文件的大小,按照元组方式:
>>> os.stat('c://winnt//uninst.exe')[stat.ST_SIZE]
299520
一旦拥有了路径的模式值(stat.ST_MODE),就可以使用stat提供的其他函数测试某些类型的路径入口。如下表:
S_ISREG(mode) 正则文件,普通文件
S_ISDIR(mode) 目录
S_ISLNK(mode) 符号链接
S_ISFIFO(mode) FIFO(命名管道)
S_ISSOCK(mode) 套接字
S_ISBLK(mode) 特殊的块设备
S_ISCHR(mode) 特殊的字符设备
当对一个符号链接路径来调用os.stat将会返回被引用的信息。
os.lstat(path)行为与os.stat类似,只是用于返回符号链接本身的信息。
os.fstat函数返回已经打开的文件描述符的状态。
在UNIX类OS上可以用os.samestat(stat1,stat2)查看两组状态是否引用了同一个文件,会比较设备及信息结点编号。
Python标准库还包含statcache模块,行为与os.stat类似,只是会存储结果以备后用。
可以调用forget(path)删除特殊存储的入口,也可调用reset()恢复所有入口。forget_prefix(prefix)能够删除给定前缀的所有入口,而forget_except_prefix(prefix)用于删除排除指定前缀的入口。forget_dir(prefix)会删除一个目录下的所有入口,但是不去递归处理子目录。删除一个存储入口意味着stat调用将会再次调用操作系统进行检查。
10.2建立并仔细分析路径
10.2.1联接路径部件
os.path.join(part[,part...])可以把任意数目的路径链接到有效路径。用于在各种操作系统之间可移植的形成路径。如:
>>> print os.path.join('c:','r2d2','c3po','r5d4')
c:/r2d2/c3po/r5d4
使用的分隔符在os.sep中定义。链接形成的路径会自动形成当前操作系统的路径。父路径和当前路径分别使用os.pardir或os.curdir。
10.2.2把路径分为几块
使用os.path.split函数之一分块:
>>> os.path.split(r'c:/temp/foo.txt')
('c://temp','foo.txt')
>>> os.path.splitdrive(r'c:/temp/foo.txt')
('c:','//temp//foo.txt')
>>> os.path.splitext(r'c:/temp/foo.txt')
('c://temp//foo','.txt') #扩展名
>>> os.path.splitunc(r'//endor/temp/foo.txt')
('endor//temp','//foo.txt') #机器名和挂载点
splitdrive函数也存在于UNIX系统,但是某些路径只返回('',path)元组;splitunc函数只可以在Windows下使用。
os.path.dirname(path)和os.path.basename(path)都是简写形式,他们返回的信息组合起来与split函数返回的信息相同。
>>> os.path.basename(r'c:/temp/foo.txt')
'foo.txt'
10.2.3路径的其他修改工具
os.path.normcase(path)函数规范路径的大小写,在区分大小写的平台上变成小写,在其他平台上保持不变。在Windows平台上用反斜线替换正斜线。在Windows平台上还会删除多余的反斜线,并且在含有多级父目录".."的路径中转换到真实路径。
os.path.abspath(path)规范路径,转换为绝对路径
os.path.expandvars(path)函数搜索给定路径,以查找$varname表单和${varname}表单的变量名,可以使用$$打印$:
>>> os.environ.update({'WORK':'work','BAKFILE':'kill.bak'})
>>> p=os.path.join('$WORK','${BAKFILE}')
>>> print os.path.expandvars(p)
work/kill.bak
os.path.expanduser(path)函数可以使用用户主目录替换开始位置的"~"符号或"~username"。对"~"符号,意味着当前用户,这个变量将从当前环境变量中的HOME读取值。在Windows上如果没有HOME,会试图链接HOMEDRIVE和HOMEPATH,如果失败了则返回未更改的路径。在我的win2k上返回的是C盘根目录下的。UNIX使用pwd模块定位这个用户的主目录。
10.3列出目录及其匹配文件名
几种检索文件名列表的方法
os.listdir(dir)返回一个列表,包含给定目录中所有文件:
>>> os.listdir('c://sierra')
['LAND','Half-Life','SETUP.EXE']
dircache模块提供了它自己的listdir函数,保留一个告诉缓存,以增加重复调用的性能(而且使用目录上的修改时间来检测告诉缓存入口是否需要更新)。
返回的列表也是一个引用,而不是副本,也就是对它的修改会反映到后来。
该模块也包含annotate(head,list)函数,此函数能够在列表中的任何一个目录后添加一个斜线。用于方便的区分目录和文件。
使用head参数,可以链接到列表中的每一个项,以便annotate能够调用os.path.isdir。
os.path.commonprefix(list)函数获取路径的列表,并返回所有项共有的最长前缀。
os.path.walk(top,func,arg)函数遍历以top开头的目录树,每个目录中调用func。函数func应该获取三个参数:arg(在walk中传递)、dirname(正在访问的目录名)、names(目录中目录入口的列表)。例如下例输出d:/games目录及其子目录下可执行文件名:
>>> def walkfunc(ext,dir,files):
... goodFiles=[x for x in files if x.find(ext)!=-1]
... if goodFiles:
... print dir,goodFiles
...
>>> os.path.walk('d://games',walkfunc,'.exe')
d:/games/Half-Life ['10051013.exe']
d:/games/q3a ['quake3.exe']
d:/games/q3a/Extras/cs ['sysinfo.exe']
采用fnmatch模块,可以测试文件名是否匹配特殊的模式。星号匹配所有内容,问号匹配单个字符:
>>> import fnmatch
>>> fnmatch.fnmatch('python','p*n')
True
>>> fnmatch.fnmatch('python','pyth?n')
True
可以将要匹配的字符放入方括号中:
>>> fnmatch.fnmatch('python','p[a,e,i,o,u,y,0-9]thon')
True #匹配vowel或数字
>>> fnmatch.fnmatch('p5thon','p[a,e,i,o,u,y,0-9]thon')
True
>>> fnmatch.fnmatch('p5thon','p[!0-9]thon')
False #不匹配其中的数字,用叹号!
fnmatch模块也包含fnmatchcase(filename,pattern)函数,强制区分大小写匹配,不管文件系统是否区分。
glob模块通过返回所有匹配搜索模式的路径,使fnmatch模块具备更高级的匹配功能:
>>> import glob
>>> for file in glob.glob('c://da*//?ytrack//s*.*[y,e]'):
... print file
c:/dave/pytrack/sdaily.py
c:/dave/pytrack/std.py
c:/dave/mytrack/StkHistInfo.py
c:/dave/mytrack/sdkaccess.2exe
10.4获得环境和参量信息
10.4.1环境变量
当导入os模块时,会用当前的环境变量填充evviron目录,更改会立即生效并在进程间共享:
>>> os.environ['SHELL']
'/usr/local/bin/tcsh'
>>> os.environ['BOO']='2+2'
>>> print os.popen('echo $BOO').read() #在win32上用%BOO%
4
使用的词典实际上是UserDict的一个子类,指定的值是字符串。
10.4.2当前工作目录
最开始的当前工作目录是Python解释器的目录。如下是读写当前目录。
>>> os.chdir('/usr/home')
>>> os.chdir('..')
>>> os.getcwd()
'/usr'
10.4.3命令行参数
sys.argv是一个列表,包含了传递的参数。第一个参数是Python脚本的全文件名。如果在Python解释器的交互模式,则这个列表是空的。取得参数的个数用len(sys.argv)。
10.5示例:递归的grep实用程序
一个递归的grep程序rgrep。可以递归查找子目录文件中的指定字符串。下例是匹配'd*.py'和'h*'文件中查找'def':
D:/Dev/pytrack>/rgrep.py def d*.py h*
D:/Dev/pytrack/dataio.py 185 def __init__(self,sTick):
D:/Dev/pytrack/histInfo.py 9 def sum(self,count):
D:/Dev/pytrack/old/dataio.py 12 def __init__(self,sTick):
... ...
程序的源代码:
#!/usr/bin/env python
import sys,os,fnmatch
def walkFunc(arg,dir,files):
"调用os.path.walk遍历目录"
pattern,masks=arg
#遍历文件掩码
for file in files:
for mask in masks:
if fnmatch.fnmatch(file,mask):
#文件名匹配
name=os.path.join(dir,file)
try:
#读取搜索到的文件
data=open(name,'rb').read()
#快速检查
if data.find(pattern)!=-1:
i=0
data=data.split('/n')
#逐行检查
for line in data:
i+=1
if line.find(pattern)!=-1:
print name,i,line
except(OSError,IOError):
pass
break
if __name__=='__main__':
if len(sys.argv)<3:
print 'Usage: %s pattern file [files...]' %/
sys.argv[0]
else:
try:
os.path.walk(os.getcwd(),walkFunc,(/
sys.argv[1],sys.argv[2:]))
except KeyboardInterrupt:
print '** Halted **'
注意:因为UNIX shell通常在程序之前先扩展通配符,所以需要使用星号等通配符时需要放入引号内。
可以把这个程序作为更强大搜索工具的起点。性能也很不错,但是因为要一次读入整个文件来进行搜索,所以有崩溃的可能。
10.6复制、重命名及删除路径
存在于os和shutil模块中。shutil模块主要提供了在命令shell中能够找到的特征。
10.6.1复制和链接
shutil.copyfile(src,dest)函数可以把文件从src位置复制到dest位置。相同的功能还有shutil.copy(src,dest),也会复制文件的许可权限。
shutil.copy2(src,dest)函数与上面类似,但是还要复制文件的最后一次访问时间和最后一次修改时间。
shutil.copyfileobj(src,dest[,buflen])复制类似文件的两个对象,并且可以指定缓冲区长度,传递给read函数。
shutil.copymode(src,dest)复制文件的许可权限(另见os.chmod)。也会复制最后一次访问时间和最后一次修改时间。
shutil.copytree(src,dest[,symlinks])函数使用copy2递归复制整个目录树。如果dest是存在的,则会产生异常。如果symlinks参数为True ,则符号链接也会成为新树中的符号链接。如果忽略symlinks或为False ,则副本中包含链接引用文件的副本。
在支持链接的平台上,os.symlink(src,dest)会创建一个符号链接,并命名为dest。而且os.link(src,dest)会创建一个硬链接。
10.6.2重命名
os.rename(old,new)重命名
os.renames(old,new)重命名,会创建和删除路径的
10.6.3删除
os.remove(filename)删除文件
os.rmdir(dir)删除空目录
os.removedirs(dir)删除空目录及所有空的父目录
如果目录不为空则rmdir和removedirs均无法删除。使用如下函数:
shutil.rmtree(path[,ignore_errors[,onerror]])
可以递归删除子目录及其文件。默认时,ignore_errors为0,如为1则忽略错误,继续处理。onerror参数用于指定一个函数在发生错误时调用,有三个参数,下例:
>>> def errFunc(raiser,problemPath,excInfo):
... print raiser.__name__,'出错于',problemPath
>>> shutil.rmtree('c://temp//foo',0,errFunc)
rmdir 出错于 c:/temp/foo/bar/yeah
rmdir 出错于 c:/temp/foo/bar
传递给error函数的参量是产生异常情况的函数对象,存放问题的特殊情况,等效于sys.exc_info()调用。
小心使用rmtree,免得整个硬盘的文件都丢失了。
10.7创建目录和临时文件
os.mkdir(dir[,mode])创建新目录,mode指定权限,符合os.chmod的权限设置。缺省的mode具有每个人的读写可执行权限。
os.makedirs(dir[,mode])创建新目录,适应需求创建中间不存在的目录。
tempfile模块提供了临时文件的操作。临时文件就是临时存储的,不在乎存储路径和文件名的文件,用于暂时保存大量的内存数据。
tempfile.mktemp([suffix])函数把绝对路径返回给唯一的临时文件名,调用时文件并不存在,并自动为文件加上后缀(如果提供的话)。调用这个函数只是为了找到一个不存在的文件名,每次调用都不同。
可以设置tempfile.tempdir变量告知临时目录位置。如果最终无法找到合适的临时目录则会使用当前目录。
tempfile.gettempprefix()放回临时文件的前缀,经由tempfile.template设置。
临时文件其实来自tempfile.TemporaryFile类。提供了一个类似文件的对象,而不必在乎清除。创建实例使用如下方法:
tempfile.TemporaryFile([mode[,bufsize[,suffix]]])
例如下列程序求出一列数字的位数:
>>> def digitCount(high):
... import tempfile
... f=tempfile.TemporaryFile()
... for i in range(1,high+1):
... f.write(i)
... f.flush()
... f.seek(0)
... return len(f.read())
>>> digitCount(12)
15 # len('123456789101112')=15
>>> digitCount(100000)
488895
mode的默认值为'w+b',可以读写数据(二进制和文本均可)。bufsize传递给open函数,suffix传递给mktemp。在UNIX系统上是不包含文件的目录入口的,很安全。其他系统上close时会删除临时文件,当Python garbage收集对象时也会删除临时文件。
在UNIX系统上,os模块包含3个函数,用于处理临时文件。os.tmpfile()创建一个可以读写的新文件对象。不包含目录入口,关闭时自动删除。
os.tmpnam()函数返回一个到唯一文件名的绝对路径,适合作为临时文件用,但是这时并没有创建文件。
os.tempnam([dir[,prefix]])与如上类似,但是可以指定存放目录,并且可以指定临时文件名的前缀。
10.8比较文件和目录
使用filecmp模块。
filecmp.cmp(f1,f2[,shallow[,use_statcache]])用于比较两个文件。shallow默认为1,即两个文件都是正则文件,且大小和修改时间相同则为True。如果两个文件不同,或shallow=0,则函数会比较两个文件的内容。use_statcache默认为0,且cmp将调用os.stat获得文件信息,如果为1则调用statcache.stat。
filecmp.cmpfiles(a,b,common[,shallow[,use_statcache]])获取两个目录中的文件名列表,并返回三元组,包括比较后相同的文件列表,不相同的文件列表,不属于正则文件而未比较的文件列表。shallow和use_statcache参数与上面相同。
filecmp.dircmp类生成通用文件的列表,并作其他比较工作。
filecmp.dircmp(a,b[,ignore[,hide]])可以创建实例。之后调用实例的report()方法可以报告文件异同,适合阅读的格式。ignore参数是将要忽略的一列文件名,默认值为['RCS','CVS','tags']。hide是清单中不需要显示的文件名列表,默认值为[os.curdir,os.pardir]。
dircmp.report()方法将比较结果打印到终端。
dircmp.report_partial_closure()进行同上的操作,也比较共同的直接子目录。
dircmp.report_full_closure()可以进入递归比较所有子目录。
创建dircmp对象后,有如下的属性可供访问:
left_list 通过hide和ignore筛选之后的a中选项
right_list 通过hide和ignore筛选之后的b中选项
common a和b中的选项
left_only 只包含a中的选项
right_only 只包含b中的选项
common_dirs 在a和b中的找到的子目录
common_files 在a和b中的找到的文件
common_funny 在a和b中的找到的项,但a和b之间的类型不同,而且os.stat会报告这个项的错误
same_files 相同的common_files
diff_files 不同的common_files
funny_files 不能比较的common_files
subdirs dircmp对象的词典-键是common_dirs
Python还提供了ndiff模块来检查两个不同文件之间的所有详细信息。这个模块在Tools/Scripts/ndiff.py。
10.9处理文件描述符
10.9.1通用文件描述符函数
使用文件描述符是一种处理文件的低级方式。
采用os.open(file,flags[,mode])函数可以创建文件描述符,flags参数可用表10.5(见下)的多种组合值,mode值是传递给os.chmod用的:
>>> fd=os.open('fumble.txt',os.O_WRONLY | os.O_CREAT)
>>> os.write(fd,'I like fudge')
12 # 写入了12字节
>>> os.close(fd)
>>> open('fumble.txt').read() #使用推荐的方式打开
'I like fudge'
os.dup(fd)函数返回给定描述符的副本,且os.dup2(fd1,fd2)使得fd2成为fd1的一个副本,需要时应该先关闭fd2。
给定文件描述符,可以使用os.fdopen(fd[,mode[,bufsize]])创建一个打开的Python文件对象(也连接到同一个文件)。可选的mode和bufsize参数与Python open函数所使用的相同。
表10.5文件描述符打开标志:
名称 | 说明 |
---|---|
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_RDWR | 读写 |
O_BINARY | 二进制模式打开 |
O_TEXT | 文本模式打开 |
O_CREAT | 如果文件不存在则创建 |
O_EXCL | 如果创建而文件存在则返回错误 |
O_TRUNC | 把文件大小设为0 |
O_APPEND | 写操作内容附加到文件末尾 |
O_NONBLOCK | 非阻塞操作 |
os模块也包含其他标志,如O_DSYNC、O_RSYNC、O_SYNC和O_NOCTTY ,他们的行为是平台相关的,具体了解UNIX open页面。
参考:对于新的伪终端,os.openpty返回两个文件描述符,见38章。
下面的os文件描述符函数将严密的镜像他们的文件方法对应物,其中大多数是在第8章的"输入输出"中介绍的:
close(fd) isatty(fd) lseek(fd,pos,how) read(fd,n)
write(str) fstat(fd) ftruncate(fd,len)
UNIX系统可以使用os.ttyname(fd),检索文件描述符所表示的终端设备名字。
>>> os.ttyname(1) # 1 是stdout
'/dev/ttyv1'
10.9.2管道
管道是一种进程间通信技术,用于读写数据,如果读写文件一样。使用os.pipe()可以创建一对文件描述符,通过管道连接:
>>> r,w=os.pipe() #一个用于读,一个用于写
>>> os.write(r,'Pipe dream')
>>> os.write(r,'Pipe dream')
10
>>> os.read(r,1000)
'Pipe dream'
在UNIX系统上,os.mkfifo(path[,mode])函数将创建一个命名管道(FIFO),可用于进程间通信。mode的默认值为每个人的读写许可(0666)。在磁盘上创建了FIFO之后,可以象处理其他文件一样。
10.10其他的文件处理技巧
10.10.1任意访问文本文件中的信息行
linecache模块可以返回文件中读者想要的任何一行:
>>> import linecache
>>> linecache.getline('linecache.py',5)
'that name./012'
打开文件时存储特定行信息,之后访问时便无需读硬盘,行编号以1开始。如果害怕占用大量内存,可用linecache.clearcache()清空高速缓存,调用linecache.checkcache()会清除无效的存储项。
注意:如果找不到用户的文件,会在模块搜索路径中寻找。因为这个模块设计为读取模块中的行,可打印异常请求的追踪。
10.10.2使用内存映射文件
内存映射文件,mmap模块中,就像可变文件的字符串混合。内存映射文件可以与很多操作字符串的例程共同使用,例如re模块。且内容的更改也可以选择提交给文件。
创建mmap对象需要一个已打开的文件描述符,并有读写权限,以及一个长度参数(指定了内存映射使用的字节数):
>>> f=open('mymap','w+b')
>>> f.write('And now for sth diff')
>>> f.flush()
>>> import mmap
>>> m=mmap.mmap(f.fileno(),45)
>>> m[5:10] #允许对其进行切片
'ow fo'
>>> m[5:10]='ew fi'
>>> m[5:10]
'ew fi'
>>> m.flush(); m.close() #注意还有
1
>>> open('mymap').read()
'And new fir sth diff'
在Windows下创建mmap会接收第三个参数,用于命名一个映射,因为在Windows允许一个文件拥有多个映射。同一个映射使用同一块内存空间。如果映射不存在则创建,如果存在则打开这个映射。
UNIX版本将会获取flags和prot参数。flags可以是MAP_PRIVATE或MAP_SHARED(默认),分别表示是否在进程间共享映射。prot参数是多个参数的逻辑OR,指定映射的保护类型,如PROT_READ | PROT_WRITE(默认)。
最好避免可选标志,便于移植。
可以使用mmap.size()获得mmap对象的大小,并使用mmap.resize(newsize)更改大小:
>>> m.size()
50
>>> m.resize(100)
调用mmap.flush([offset,size])将更改写入硬盘,可选取只写入部分。mmap.read_byte()和mmap.write_byte(byte)方法可一次读写一个字节,传递字节,并作为长度为1的字符串返回。使用mmap.move(dest,src, count)可以把数据从内存映射文件的一个位置复制到另一个位置,把count字节从src复制到dest。
10.10.3重复处理几个文件
fileinput类允许重复处理几个文件,就像是单个文件一样,适合于处理命令行上传递的多个文件。
>>> import fileinput
>>> for line in fileinput.input():
... print line
上面的例子重复处理sys.argv[1:]中列出的文件,并打印每一行。函数input(files,inplace,backup),如果没有传递files列表,则使用命令行变量。为'-'的任一文件或命令行参数都会从stdin读取。如果inplace参数为1,则fileinput将会把每个文件复制到备份中,并把stdout上的输出路由到原始文件中,允许逐个文件修改。如果inplace为1,而且还为backup提供了一个值(.ext格式),则当创建原始文件的备份时,fileinput就会把backup的值当作扩展名来使用,而且当完成时,不会删除备份。
fileinput.filename()获得当前处理的文件名。
fileinput.isstdin()判断当前文件是否是stdin。
fileinput.lineno()返回已经读取的行的全部行号。
fileinput.filelineno()放回当前文件内的这个行号。
fileinput.isfirstline()查看是否是该文件的第一行。
fileinput.nextfile()跳过当前文件的其余部分,移到序列中的下一个文件。
fileinput.close()关闭序列并退出。
细分fileinput.FileInput类,可以定制fileinput的功能。
完成...