blogs

43 IO编程_操作文件和目录

如果我们要操作文件、目录,可以在命令行下面输入操作系统提供的各种命令来完成。比如dir、cp等命令。

如果要在Python程序中执行这些目录和文件的操作怎么办?其实操作系统提供的命令只是简单地调用了操作系统提供的接口函数,Python内置的os模块也可以直接调用操作系统提供的接口函数。

打开Python交互式命令行,我们来看看如何使用os模块的基本功能:

import os
os.name  # 操作系统类型
'posix'

如果是posix,说明系统是Linux、Unix或Mac OS X,如果是nt,就是Windows系统。

要获取详细的系统信息,可以调用uname()函数:

os.uname()
posix.uname_result(sysname='Linux', nodename='adminitrc-B7079F77CV10HR', release='4.10.0-28-generic', version='#32~16.04.2-Ubuntu SMP Thu Jul 20 10:19:48 UTC 2017', machine='x86_64')

注意uname()函数在Windows上不提供,也就是说,os模块的某些函数是跟操作系统相关的。

环境变量

在操作系统中定义的环境变量,全部保存在os.environ这个变量中,可以直接查看:

os.environ
environ{'LC_PAPER': 'ko_KR.UTF-8',
        'LC_ADDRESS': 'ko_KR.UTF-8',
        'XDG_SESSION_ID': '222',
        'LC_MONETARY': 'ko_KR.UTF-8',
        'TERM': 'xterm-color',
        'SHELL': '/bin/bash',
        'SSH_CLIENT': '114.70.194.70 63968 22',
        'CONDA_SHLVL': '2',
        'CONDA_PROMPT_MODIFIER': '(keras) ',
        'LC_NUMERIC': 'ko_KR.UTF-8',
        'OLDPWD': '/home/frank',
        'SSH_TTY': '/dev/pts/8',
        'USER': 'frank',
        'LS_COLORS': 'rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:',
        'LD_LIBRARY_PATH': '/usr/local/cuda-8.0/lib64:',
        'LC_TELEPHONE': 'ko_KR.UTF-8',
        'CONDA_EXE': '/home/frank/anaconda3/bin/conda',
        'CONDA_PREFIX_1': '/home/frank/anaconda3',
        'MAIL': '/var/mail/frank',
        'PATH': '/home/frank/anaconda3/envs/keras/bin:/home/frank/anaconda3/envs/keras/bin:/home/frank/bin:/home/frank/.local/bin:/home/frank/anaconda3/bin:/usr/local/cuda-8.0/bin:/opt/anaconda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin',
        'QT_QPA_PLATFORMTHEME': 'appmenu-qt5',
        'CONDA_PREFIX': '/home/frank/anaconda3/envs/keras',
        'LC_IDENTIFICATION': 'ko_KR.UTF-8',
        'PWD': '/home/frank/work',
        'LANG': 'en_US.UTF-8',
        'LC_MEASUREMENT': 'ko_KR.UTF-8',
        'SHLVL': '1',
        'HOME': '/home/frank',
        'CONDA_PYTHON_EXE': '/home/frank/anaconda3/bin/python',
        'LOGNAME': 'frank',
        'XDG_DATA_DIRS': '/usr/local/share:/usr/share:/var/lib/snapd/desktop',
        'SSH_CONNECTION': '114.70.194.70 63968 114.70.193.193 22',
        'CONDA_DEFAULT_ENV': 'keras',
        'LESSOPEN': '| /usr/bin/lesspipe %s',
        'XDG_RUNTIME_DIR': '/run/user/1010',
        'DISPLAY': 'localhost:10.0',
        'LESSCLOSE': '/usr/bin/lesspipe %s %s',
        'LC_TIME': 'ko_KR.UTF-8',
        'LC_NAME': 'ko_KR.UTF-8',
        '_': '/home/frank/anaconda3/envs/keras/bin/jupyter',
        'JPY_PARENT_PID': '7094',
        'CLICOLOR': '1',
        'PAGER': 'cat',
        'GIT_PAGER': 'cat',
        'MPLBACKEND': 'module://ipykernel.pylab.backend_inline'}

要获取某个环境变量的值,可以调用os.environ.get(‘key’):

os.environ.get('PATH')
'/home/frank/anaconda3/envs/keras/bin:/home/frank/anaconda3/envs/keras/bin:/home/frank/bin:/home/frank/.local/bin:/home/frank/anaconda3/bin:/usr/local/cuda-8.0/bin:/opt/anaconda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin'
os.environ.get('x', 'default')
'default'

操作文件和目录

操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中,这一点要注意一下。查看、创建和删除目录可以这么调用:

# 查看当前目录的绝对路径:
 os.path.abspath('.')
'/home/frank/work/Learn-Python'
os.listdir('.')
['.ipynb_checkpoints',
 '42 IO编程_StringIO和BytesIO.ipynb',
 '37 错误、调试和测试_错误处理.ipynb',
 '31 面向对象高级编程使用_slots__.ipynb',
 '07-10 函数',
 '38 错误、调试和测试_调试.ipynb',
 '26-30 面向对象编程',
 '32 面向对象高级编程使用_使用@property.ipynb',
 '00-06 Python基础',
 '39 错误、调试和测试_单元测试.ipynb',
 '__pycache__',
 '35 面向对象高级编程使用_使用枚举类.ipynb',
 '40 错误、调试和测试_文档测试.ipynb',
 '34 面向对象高级编程使用_定制类.ipynb',
 '42 IO编程_操作文件和目录.ipynb',
 '24-25 模块',
 '36 面向对象高级编程使用_使用元类.ipynb',
 'hello.py',
 'testdir',
 '16-23 函数式编程',
 'test',
 '41 IO编程_文件读写.ipynb',
 '33 面向对象高级编程使用_多重继承.ipynb',
 '11-15 高级特性']
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
os.path.join('/home/frank/work/Learn-Python', 'testdir')
'/Users/michael/testdir'
# 然后创建一个目录:
os.mkdir('/home/frank/work/Learn-Python/testdir')
os.path.abspath('.')
'/home/frank/work/Learn-Python'
os.rmdir('/home/frank/work/Learn-Python/testdir')

把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符。在Linux/Unix/Mac下,os.path.join()返回这样的字符串:

part-1/part-2

而Windows下会返回这样的字符串:

part-1\part-2

同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:

d = os.path.join('/home/frank/work/Learn-Python', 'testdir')
os.mkdir(d)
d1 = os.path.abspath(d)
print(d1)
/home/frank/work/Learn-Python/testdir
d2 = os.path.split('/home/frank/work/Learn-Python/testdir')
print(d2)
('/home/frank/work/Learn-Python', 'testdir')

os.path.splitext()可以直接让你得到文件扩展名,很多时候非常方便:

os.path.splitext('/home/frank/work/Learn-Python/testdir/test.txt')
('/home/frank/work/Learn-Python/testdir/test', '.txt')

这些合并、拆分路径的函数并不要求目录和文件要真实存在,它们只对字符串进行操作。

文件操作使用下面的函数。假定当前目录下有一个test.txt文件:

with open('./testdir/test.txt', 'w') as f:
    f.write('This is a test line!')
# 对文件重命名:
os.rename('./testdir/test.txt', './testdir/test.py')
# 删掉文件:
os.remove('./testdir/test.py')

但是复制文件的函数居然在os模块中不存在!原因是复制文件并非由操作系统提供的系统调用。理论上讲,我们通过上一节的读写文件可以完成文件复制,只不过要多写很多代码。

幸运的是shutil模块提供了copyfile()的函数,你还可以在shutil模块中找到很多实用函数,它们可以看做是os模块的补充。

最后看看如何利用Python的特性来过滤文件。比如我们要列出当前目录下的所有目录,只需要一行代码:

os.listdir('.')
['.ipynb_checkpoints',
 '42 IO编程_StringIO和BytesIO.ipynb',
 '37 错误、调试和测试_错误处理.ipynb',
 '31 面向对象高级编程使用_slots__.ipynb',
 '07-10 函数',
 '38 错误、调试和测试_调试.ipynb',
 '26-30 面向对象编程',
 '32 面向对象高级编程使用_使用@property.ipynb',
 '00-06 Python基础',
 '39 错误、调试和测试_单元测试.ipynb',
 '__pycache__',
 '35 面向对象高级编程使用_使用枚举类.ipynb',
 '40 错误、调试和测试_文档测试.ipynb',
 '34 面向对象高级编程使用_定制类.ipynb',
 'Untitled.ipynb',
 '24-25 模块',
 '36 面向对象高级编程使用_使用元类.ipynb',
 'hello.py',
 'testdir',
 '16-23 函数式编程',
 'test',
 '41 IO编程_文件读写.ipynb',
 '33 面向对象高级编程使用_多重继承.ipynb',
 '11-15 高级特性']
 [x for x in os.listdir('.') if os.path.isdir(x)]
['.ipynb_checkpoints',
 '07-10 函数',
 '26-30 面向对象编程',
 '00-06 Python基础',
 '__pycache__',
 '24-25 模块',
 'testdir',
 '16-23 函数式编程',
 'test',
 '11-15 高级特性']

要列出所有的.py文件,也只需一行代码:

 [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['hello.py']

是不是非常简洁?

小结

Python的os模块封装了操作系统的目录和文件操作,要注意这些函数有的在os模块中,有的在os.path模块中。

练习

利用os模块编写一个能实现dir -l输出的程序。

编写一个程序,能在当前目录以及当前目录的所有子目录下查找文件名包含指定字符串的文件,并打印出相对路径。