本篇文章由网友“小黑”投稿发布。这篇文章是接着上一篇的内容来写的。mkdir方法的语法格式如下 os.mkdir(path [, mode=0777]) path参数用于指定目录的相对或者绝对路径...

    页面导航: 前言:

    本篇文章由网友“小黑”投稿发布。

    这篇文章是接着上一篇的内容来写的。

    相关英文教程的下载链接,以及Python 2.7.8的C源代码的下载链接,请参考上一篇文章。

    文章中的测试代码都是通过python 2.7.x的版本来执行的。

os模块的mkdir方法:

    mkdir方法的语法格式如下:

os.mkdir(path [, mode=0777])

    path参数用于指定目录的相对或者绝对路径。mode用于指定目录的读写执行的访问权限,在windows系统中,mode参数会被忽略掉。

    下面是一个简单的例子:

import os

os.mkdir('mydir')
os.mkdir('/home/black/test/mydir2')


    这段代码的执行结果如下:

black@slack:~/test$ ls
test.py
black@slack:~/test$ python test.py
black@slack:~/test$ ls -l
total 12
drwxr-xr-x 2 black users 4096 Nov 14 13:07 mydir
drwxr-xr-x 2 black users 4096 Nov 14 13:07 mydir2
-rw-r--r-- 1 black users   66 Nov 14 13:07 test.py
black@slack:~/test$ umask
0022
black@slack:~/test$ 


    上面生成的目录的访问权限是根据 (mode & ~umask & 0777) 公式计算出来的。由于mode的缺省值是0777,并且通过umask命令可以看到,umask的缺省值是0022。因此,生成的mydir与mydir2目录的访问权限就是(0777 & ~0022 & 0777) = 0755 ,也就是rwxr-xr-x的访问权限了,group与other部分的写权限被umask给屏蔽掉了。

    当然我们也可以通过umask命令修改屏蔽位:

black@slack:~/test$ rmdir mydir*
black@slack:~/test$ umask 0000
black@slack:~/test$ umask
0000
black@slack:~/test$ umask -S
u=rwx,g=rwx,o=rwx
black@slack:~/test$ python test.py
black@slack:~/test$ ls -l
total 12
drwxrwxrwx 2 black users 4096 Nov 14 13:29 mydir
drwxrwxrwx 2 black users 4096 Nov 14 13:29 mydir2
-rw-r--r-- 1 black users   66 Nov 14 13:07 test.py
black@slack:~/test$ 


    在重新执行test.py程式之前,需要先将刚才创建的mydir与mydir2删除掉,否则,os.mkdir方法在执行时就会抛出 OSError: [Errno 17] File exists 的错误(也就是目录已存在的错误)。上面通过将umask设置为0000,从而取消屏蔽位,这样再次执行test.py程式后,得到的两个目录就都是0777(即rwxrwxrwx)的权限了。可以通过umask -S来查看哪些权限位没有被屏蔽。

    在通过import导入os模块的时候,os模块又会去导入posix模块,并通过posix模块中的posix_mkdir的C函数去执行创建目录的操作(posix模块定义在Python源代码的Modules/posixmodule.c文件中)。从posix_mkdir的C源码里可以看到,在Windows系统中,会通过CreateDirectoryW或者CreateDirectoryA的API接口函数来创建目录,这两个API接口函数都不需要mode访问权限参数。在Linux系统中,则会通过mkdir的C库函数去创建目录,可以通过man 2 mkdir来查看该库函数的详情,在mkdir的man手册里就可以看到(mode & ~umask & 0777)的公式了。感兴趣的读者可以根据这些信息阅读相关的C源码。

os模块的getcwd与chdir方法:

    通过os模块的getcwd方法可以获取当前的工作目录,一般情况下是执行脚本所在的目录,Python会优先在当前的工作目录内搜索模块文件,很多方法的相对路径也是相对于当前工作目录的。getcwd的语法格式如下:

os.getcwd() -> path

    该方法会将当前工作目录的路径,以字符串的形式作为结果返回。例如下面这段代码:

import os

print 'current working directory:', os.getcwd()


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
current working directory: /home/black/test
black@slack:~/test$ 


    我们还可以通过chdir方法来改变当前的工作目录,该方法的语法格式如下:

os.chdir(path)

    path参数用于指定新的工作目录的路径信息,例如下面这段代码:

import os

print 'current working directory:', os.getcwd()
os.mkdir("Phone")
print 'mkdir Phone'
os.chdir("Phone")
print 'change working directory to Phone'
os.mkdir("subPhone", 0757)
print 'mkdir subPhone in Phone'


    这段代码的执行结果如下:

black@slack:~/test$ umask
0000
black@slack:~/test$ ls
test.py
black@slack:~/test$ python test.py
current working directory: /home/black/test
mkdir Phone
change working directory to Phone
mkdir subPhone in Phone
black@slack:~/test$ ls -l
total 8
drwxrwxrwx 3 black users 4096 Nov 14 15:22 Phone
-rw-r--r-- 1 black users  217 Nov 14 15:22 test.py
black@slack:~/test$ ls -l Phone
total 4
drwxr-xrwx 2 black users 4096 Nov 14 15:22 subPhone
black@slack:~/test$ 


    由于chdir将当前的工作目录修改为新建的Phone目录了。因此,mkdir在创建subPhone目录时,就会在Phone目录内进行创建了。

os模块的rmdir方法:

    通过os模块的rmdir方法可以将指定的目录给删除掉,其语法格式如下:

os.rmdir(path)

    path参数用于指定需要删除的目录的路径信息,需要注意的是rmdir只能删除空的目录,也就是该目录内不能包含子目录或文件。如果使用rmdir删除非空目录的话,就会抛出错误。例如下面这段代码:

import os

os.rmdir("subPhone")
print 'delete subPhone success'
os.rmdir("Phone")
print 'delete Phone success'


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
delete subPhone success
Traceback (most recent call last):
  File "test.py", line 5, in <module>
    os.rmdir("Phone")
OSError: [Errno 39] Directory not empty: 'Phone'
black@slack:~/test$ 


    由于Phone是非空的目录,所以在执行rmdir方法时就抛出了OSError错误。rmdir方法的path参数也可以是绝对路径,例如:os.rmdir("/home/black/test/subPhone") 之类的。

文件对象的flush方法:

    在上一篇文章的示例代码中已经提到过flush方法,该方法可以将文件缓存中的内容写入磁盘文件。当然flush方法并不保证数据一定会写入磁盘中,因为在某些系统中,系统底层可能还有一层数据缓存,如果要确保数据写入到磁盘上的话,在执行完文件对象的flush方法后,还需要执行os.fsync()方法,才能确保数据被写入磁盘。

    该方法的语法格式如下:

fileObject.flush()

    下面是一个简单的例子:

import os

fo = open('test.txt', 'w')
fo.write('hello world')
print 'write "hello world" to test.txt'
raw_input('before flush:')
fo.flush()
os.fsync(fo.fileno())
raw_input('after flush:')


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
write "hello world" to test.txt
before flush:
after flush:
black@slack:~/test$ 


    代码中用到的raw_input在上一篇文章中已经介绍过了,可以等待并接受用户的输入,当出现before flush:提示符时,可以使用编辑器打开test.txt文件,此时,由于还没有执行flush与os.fsync操作,因此,test.txt的内容应该是空的。按回车符让raw_input结束等待,并继续执行脚本,当出现after flush:提示符时,再查看test.txt文件,就可以看到之前写入的hello world的数据了,也就是文件对象的flush方法与os.fsync方法将文件缓存里的数据写入到了磁盘中。

    上面这个例子的大概执行过程就是:fo.write --> 将数据写入文件缓存 --> fo.flush --> 将文件缓存写入系统缓存 --> os.fsync --> 将系统缓存同步到磁盘。

文件对象的fileno方法:

    文件对象的fileno方法可以返回整数形式的文件描述符,通过文件描述符,就可以对指定的文件进行各种底层的I/O操作。例如上面在介绍flush方法时,示例代码中的os.fsync方法就可以接受文件描述符来同步磁盘文件数据。

    fileno方法的语法格式如下:

fileObject.fileno() -> integer

    下面是一个简单的例子:

import sys

print 'std in file desc:', sys.stdin.fileno()
print 'std out file desc:', sys.stdout.fileno()
fo = open('test.txt','w')
print 'test.txt file desc:', fo.fileno()


    sys.stdin是与标准输入设备相关的文件对象,sys.stdout则是与标准输出设备相关的文件对象,这段代码的执行结果如下:

black@slack:~/test$ python test.py
std in file desc: 0
std out file desc: 1
test.txt file desc: 3
black@slack:~/test$ 


    通过这些整数形式的文件描述符,我们可以对相关的文件执行一些底层的I/O操作,例如下面这段代码(目前这段代码只能在Linux系统中运行,因为windows平台下,Python官方并没有提供fcntl模块):

import fcntl

fo = open('test.txt', 'w')
fd = fo.fileno()
print('fd is', fd)
print('before flock')
fcntl.flock(fd, fcntl.LOCK_EX)
print('after flock')
raw_input('Press Enter to Exit:')


    要测试这段代码,需要在两个终端中分别运行python test.py,第一个终端执行test.py时,当执行完flock加锁操作并出现'Press Enter to Exit'提示符时,就可以切换到第二个终端运行test.py,当第二个终端对应的test.py执行到fcntl.flock时,进程就会被挂起,因为第一个终端里的test.py已经对文件加了锁,只有当第一个终端里的进程结束后,第二个终端里的进程才能恢复执行。

    在这个例子中,就通过将文件描述符传递给fcntl.flock方法来实现系统底层的文件加锁操作。

文件对象的isatty方法:

    isatty方法可以判断文件对象是否与某个tty终端设备相关联,例如,最常见的就是sys.stdin与标准输入设备相关联,sys.stdout则与标准输出设备相关联。标准输入与标准输出设备通常对应为命令行终端。

    该方法的语法格式如下:

fileObject.isatty() -> True or False

    当isatty方法返回True时,表示文件对象与终端设备相关联,否则,返回False。

    下面是一个简单的例子:

import sys
import os

print 'sys.stdout.isatty()', sys.stdout.isatty()
print 'sys.stdin.isatty()', sys.stdin.isatty()
print 'os.isatty(sys.stdout.fileno())', os.isatty(sys.stdout.fileno())
fo = open('test.txt', 'w')
print 'test.txt isatty:', fo.isatty()


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
sys.stdout.isatty() True
sys.stdin.isatty() True
os.isatty(sys.stdout.fileno()) True
test.txt isatty: False
black@slack:~/test$ 


    可以看到,我们还可以使用os模块的isatty方法来进行检测,但是os.isatty方法需要接受整数形式的文件描述符作为输入参数,因此上面代码中就用到了文件对象的fileno方法来返回整数形式的文件描述符。

文件对象的迭代与next方法:

    文件对象是可以直接进行迭代操作的,例如下面这段代码(代码位于test.py脚本中):

fo = open('test.txt', 'r')
for x in fo:
	print x


    假设test.txt文件的内容如下:

hello world
welcome to black world
I'm Black

    那么test.py脚本的执行结果就是:

black@slack:~/test$ python test.py
hello world

welcome to black world

I'm Black

black@slack:~/test$ 


    可以看到,文件对象在进行迭代操作时,每迭代一次,就会从文件中读取出一行数据出来。

    文件对象还有一个next方法,每执行一次next方法,文件对象就会进行一次迭代。next方法的语法格式如下:

fileObject.next() -> data

    它可以返回迭代出来的行数据。

    如果文件读到了结尾位置处,再执行next方法就会抛出StopIteration异常。因此,我们也可以使用next方法并结合StopIteration异常来将文件中的所有数据都读取出来。例如下面这段代码:

fo = open('test.txt', 'r')
while(True):
	try:
		x = fo.next()
		print x
	except StopIteration:
		break


    这段代码的执行结果与前面使用for语句进行迭代的结果是一样的。

    当文件对象进行迭代操作时,Python内部会通过file_iternext这个C函数去执行迭代操作(该C函数定义在Objects/fileobject.c文件中)。通过阅读相关C源码可以看到,当第一次进行迭代操作的时候,文件对象内部会创建一个f_buf的缓冲区,数据会先从文件中读取到该缓冲区,再从缓冲区里读取出行数据。可以通过文件对象的seek方法来释放掉该缓冲区。

    此外,文件对象的read,readline,readlines方法在执行时,会先检查f_buf缓冲区,如果该缓冲区里还有数据没读取出来的话,Python会认为迭代操作还在进行中,这些方法就会抛出异常,因此,read,readline,readlines方法不可以与文件对象的迭代操作混合使用。如果在执行了next方法后,要使用read之类的方法的话,可以先通过seek方法将f_buf释放掉,再执行read之类的方法。例如下面这段代码:

import os

fo = open('test.txt', 'r')
x = fo.next()
print x
fo.seek(0, os.SEEK_SET)
print fo.read(11)


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
hello world

hello world
black@slack:~/test$ 


    如果将fo.seek语句去掉的话,read方法在执行时,就会出现 ValueError: Mixing iteration and read methods would lose data 的错误,也就是read与迭代操作不能混合使用(混合使用会导致数据丢失)。

文件对象的readline方法:

    readline方法的语法格式如下:

fileObject.readline([size]) -> next line from the file, as a string

    如果没提供size参数的话,每执行一次readline方法就会将一行数据作为结果返回。如果提供了size参数,那么readline最多只会返回size个字节的数据,如果在读取过程中遇到换行符,则只读取到换行符。如果已经到了文件结尾处,则readline会返回空字符串。

    下面是一个简单的例子:

fo = open('test.txt', 'r')
while(True):
	s = fo.readline()
	if(s != ''):
		print s
	else:
		break


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
hello world

welcome to black world

I'm Black

black@slack:~/test$ 


    如果将上面例子中的fo.readline()替换为fo.readline(5) ,那么得到的结果就会是:

black@slack:~/test$ python test.py
hello
 worl
d

welco
me to
 blac
k wor
ld

I'm B
lack

black@slack:~/test$ 


    也就是readline每次最多只读取5个字节的数据,如果在5个字节的数据中包含换行符的话,则只读取到换行符。

文件对象的readlines方法:

    readlines方法的语法格式如下:

fileObject.readlines([size]) -> list of strings, each a line from the file

    该方法会返回一个列表,列表中的每个成员都对应为文件中的一行数据。如果没指定size参数,那么就会将文件中所有的行数据都读取出来。

    如果指定了size参数,size参数也只对8192字节以上的比较大尺寸的文件起作用,对于几百个字节的小尺寸文件不起作用。

    通过阅读readlines的底层C函数file_readlines的源码(定义在Objects/fileobject.c文件中),可以看到,readlines在读取文件数据时,会先将数据读取到buffer缓冲区。该缓冲区的最小尺寸由SMALLCHUNK宏来定义,最小是8192字节。readlines会先循环将buffer缓冲区里的所有行数据都读出来并添加到列表中后,再去将读取出来的数据的字节值与size参数进行比较。

    例如,size为9200,那么readlines在将8192字节的数据处理完后,发现读取的数据的字节值还小于9200,那么就会再去读并处理8192字节的数据,再与size比较,此时读出来的16K数据的尺寸大于9200,才会停止读操作。在这个例子中,就通过size参数来限制readlines只能读取两块数据,每块数据的尺寸为8K。当然在实际执行时,第一次读了8192字节,第二次不一定就会读取8192字节,因为如果前一个8192字节的最后一行数据不完整的话,那么下一次读取出来的数据就可能小于8K。而且如果某一行数据很大的话,有可能一次处理的数据会大于8K。

    因此,size参数只能大概的指定读取多少个字节的数据,运气好可以很接近,运气差则可能相差个几百或者上千个字节。因为,实际读出来的数据尺寸会受到buffer的尺寸大小,以及文件中每行数据的尺寸大小的影响。

    下面是一个简单的例子:

fo = open('test.txt')
lst = fo.readlines()
print lst


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
['hello world\n', 'welcome to black world\n', "I'm Black\n"]
black@slack:~/test$ 


    在这个例子中,由于test.txt的文件尺寸很小,因此,本例中给readlines设置size参数没有什么含义,得到的结果都是一样的。

文件对象的truncate方法:

    truncate方法的语法格式如下:

fileObject.truncate([size])

    当没提供size参数时,truncate会使用当前文件指针的偏移值作为截取尺寸,并将文件大小设置为该尺寸,该尺寸之后的内容都会丢失。例如,假设当前的文件指针位于文件的开头的话,也就是偏移值为0,那么truncate就会将文件的大小设置为0,也就是将整个文件的内容都清空掉。如果文件指针的偏移值为5的话,truncate就会将文件大小设置为5,也就是只保留原文件里的前5个字节的数据,其他的数据都会丢失。

    如果提供了size参数,则会将文件大小设置为size指定的大小,如果size的值大于文件的原始尺寸的话,就相当于给文件扩容,文件中的原有数据会保持不变,多出来的字节可能会用0来填充。

    此外,truncate操作不会改变文件指针的位置。与truncate相关的底层C函数为file_truncate(定义在Objects/fileobject.c文件中),有兴趣的读者可以阅读其源代码来加深理解。

    下面是一个简单的例子:

fo = open('test.txt', 'r+')
print fo.read(5)
print 'file position:', fo.tell()
fo.truncate()
print 'test.txt is truncated'
print 'file position:', fo.tell()


    truncate方法需要可写模式,因此,上面在打开文件时,就用到了 r+ 访问模式。这段代码的执行结果如下:

black@slack:~/test$ cat test.txt
hello world
welcome to black world
I'm Black
black@slack:~/test$ python test.py
hello
file position: 5
test.txt is truncated
file position: 5
black@slack:~/test$ od -t c test.txt
0000000   h   e   l   l   o
0000005
black@slack:~/test$ 


    代码通过read(5)从文件中读取了5个字节的数据,同时将文件指针的位置调整到5,这样truncate方法在执行时,就会将文件大小设置为5了,也就是只保留了前5个字节的数据。

    我们还可以使用truncate对文件大小进行扩容操作,例如下面这段代码:

fo = open('test.txt', 'r+')
fo.truncate(10)
print 'expand test.txt size to 10 bytes'


    这段代码的执行结果如下:

black@slack:~/test$ od -t c test.txt
0000000   h   e   l   l   o
0000005
black@slack:~/test$ python test.py
expand test.txt size to 10 bytes
black@slack:~/test$ od -t c test.txt
0000000   h   e   l   l   o  \0  \0  \0  \0  \0
0000012
black@slack:~/test$ ls -l
total 8
-rw-r--r-- 1 black users 86 Nov 15 15:05 test.py
-rw-rw-rw- 1 black users 10 Nov 15 15:05 test.txt
black@slack:~/test$ 


    可以看到,truncate方法将test.txt文件的大小扩充到了10个字节,多出来的5个字节都用0来填充。

文件对象的writelines方法:

    writelines方法的语法格式如下:

fileObject.writelines(sequence)

    参数sequence可以是任何可迭代出字符串数据的对象,最典型的就是由字符串构成的列表。writelines方法会将对象中的每个字符串成员依次写入到文件中,该方法不会自动添加换行符,需要换行的话,需要在字符串中包含换行符。

    下面是一个简单的例子:

fo = open('test.txt', 'w')
fo.writelines(['hello world ', 'welcome to Black World\n'])
fo.writelines(("I'm Black,", " I'm from Black World\n"))


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
black@slack:~/test$ cat test.txt
hello world welcome to Black World
I'm Black, I'm from Black World
black@slack:~/test$ 


结束语:

    限于篇幅,本章先到这里,其他的内容将放到以后的章节中。

    OK,就到这里,休息,休息一下 o(∩_∩)o~~

    伟大的艺术品不必追随潮流,他本身就能引领潮流。

——  《乔布斯传》
 
上下篇

下一篇: Python基本的I/O操作 (三)

上一篇: Python基本的I/O操作 (一)

相关文章

Python的calendar模块

Python相关的数学运算函数

Python随机数函数

Python词典类型

Python的数字类型及相关的类型转换函数

Python循环语句