7.8. shutil — 高阶文件操作

未匹配的标注

目的:高阶文件操作

shutil 模块提供了高阶的文件操作,例如复制和归档。

复制文件

copyfile() 复制源文件的内容到目标位置,如果没有创建新文件的权限将会引发 IOError

shutil_copyfile.py

import glob
import shutil

print('BEFORE:', glob.glob('shutil_copyfile.*'))

shutil.copyfile('shutil_copyfile.py', 'shutil_copyfile.py.copy')

print('AFTER:', glob.glob('shutil_copyfile.*'))

因为 copyfile() 不管文件的类型如何,都会打开进行读取,一些特殊的文件(例如 Unix 设备)是不能使用它进行复制的。

$ python3 shutil_copyfile.py

BEFORE: ['shutil_copyfile.py']
AFTER: ['shutil_copyfile.py', 'shutil_copyfile.py.copy']

 copyfile()  使用低级的方法 copyfileobj() 实现。然而传入 copyfile() 的参数是文件名称,而传入 copyfileobj() 是打开的文件描述符。可选的第三个参数是用来设置读取块的大小。

shutil_copyfileobj.py

import io
import os
import shutil
import sys

class VerboseStringIO(io.StringIO):

    def read(self, n=-1):
        next = io.StringIO.read(self, n)
        print('read({}) got {} bytes'.format(n, len(next)))
        return next

lorem_ipsum = '''Lorem ipsum dolor sit amet, consectetuer
adipiscing elit.  Vestibulum aliquam mollis dolor. Donec
vulputate nunc ut diam. Ut rutrum mi vel sem. Vestibulum
ante ipsum.'''

print('Default:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output)

print()

print('All at once:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output, -1)

print()

print('Blocks of 256:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output, 256)

默认行为是使用大块读取。使用 -1 一次性读取所有输入,或者使用一个正整数设定一个具体的区块大小。这个例子中使用了不同的块大小展示效果。

$ python3 shutil_copyfileobj.py

Default:
read(16384) got 166 bytes
read(16384) got 0 bytes

All at once:
read(-1) got 166 bytes
read(-1) got 0 bytes

Blocks of 256:
read(256) got 166 bytes
read(256) got 0 bytes

copy() 方法会像 Unix 命令行工具 cp 那样解释输出文件名称。如果目标是一个目录而不是一个文件,那么将会在这个目录中创建一个与源文件同名的新文件。

shutil_copy.py

import glob
import os
import shutil

os.mkdir('example')
print('BEFORE:', glob.glob('example/*'))

shutil.copy('shutil_copy.py', 'example')

print('AFTER :', glob.glob('example/*'))

文件的权限将会随着内容一起复制。

$ python3 shutil_copy.py

BEFORE: []
AFTER : ['example/shutil_copy.py']

copy2() 类似 copy() ,但是在复制时会包含元数据中的访问和修改时间。

shutil_copy2.py

import os
import shutil
import time

def show_file_info(filename):
    stat_info = os.stat(filename)
    print('  Mode    :', oct(stat_info.st_mode))
    print('  Created :', time.ctime(stat_info.st_ctime))
    print('  Accessed:', time.ctime(stat_info.st_atime))
    print('  Modified:', time.ctime(stat_info.st_mtime))

os.mkdir('example')
print('SOURCE:')
show_file_info('shutil_copy2.py')

shutil.copy2('shutil_copy2.py', 'example')

print('DEST:')
show_file_info('example/shutil_copy2.py')

新文件拥有旧版本的所有特征。

$ python3 shutil_copy2.py

SOURCE:
  Mode    : 0o100644
  Created : Wed Dec 28 19:03:12 2016
  Accessed: Wed Dec 28 19:03:49 2016
  Modified: Wed Dec 28 19:03:12 2016
DEST:
  Mode    : 0o100644
  Created : Wed Dec 28 19:03:49 2016
  Accessed: Wed Dec 28 19:03:49 2016
  Modified: Wed Dec 28 19:03:12 2016

复制文件元数据

默认情况下,Unix 上创建一个新文件的时候,它的权限依赖于当前用户的 umask。为了将文件权限从一个复制到另一个,请使用 copymode()

shutil_copymode.py

import os
import shutil
import subprocess

with open('file_to_change.txt', 'wt') as f:
    f.write('content')
os.chmod('file_to_change.txt', 0o444)

print('BEFORE:', oct(os.stat('file_to_change.txt').st_mode))

shutil.copymode('shutil_copymode.py', 'file_to_change.txt')

print('AFTER :', oct(os.stat('file_to_change.txt').st_mode))

这个脚本创建了一个文件并修改了它的权限,然后使用 copymode() 将当前脚本的权限复制给了它。

$ python3 shutil_copymode.py

BEFORE: 0o100444
AFTER : 0o100644

为了复制文件其他的元数据,可以使用 copystat()

shutil_copystat.py

import os
import shutil
import time

def show_file_info(filename):
    stat_info = os.stat(filename)
    print('  Mode    :', oct(stat_info.st_mode))
    print('  Created :', time.ctime(stat_info.st_ctime))
    print('  Accessed:', time.ctime(stat_info.st_atime))
    print('  Modified:', time.ctime(stat_info.st_mtime))

with open('file_to_change.txt', 'wt') as f:
    f.write('content')
os.chmod('file_to_change.txt', 0o444)

print('BEFORE:')
show_file_info('file_to_change.txt')

shutil.copystat('shutil_copystat.py', 'file_to_change.txt')

print('AFTER:')
show_file_info('file_to_change.txt')

只有与文件相关的权限和日期才会被 copystat() 复制。

$ python3 shutil_copystat.py

BEFORE:
  Mode    : 0o100444
  Created : Wed Dec 28 19:03:49 2016
  Accessed: Wed Dec 28 19:03:49 2016
  Modified: Wed Dec 28 19:03:49 2016
AFTER:
  Mode    : 0o100644
  Created : Wed Dec 28 19:03:49 2016
  Accessed: Wed Dec 28 19:03:49 2016
  Modified: Wed Dec 28 19:03:46 2016

处理目录树

shutil 模块提供了三个方法处理目录树。为了将目录从一个地方复制到另一个,请使用 copytree() 。它递归源目录内容,将每个文件复制到目标目录,目标目录事先必须存在。

shutil_copytree.py

import glob
import pprint
import shutil

print('BEFORE:')
pprint.pprint(glob.glob('/tmp/example/*'))

shutil.copytree('../shutil', '/tmp/example')

print('\nAFTER:')
pprint.pprint(glob.glob('/tmp/example/*'))

symlinks 参数控制符号链接是被赋值为链接还是文件。默认情况下时将内容复制到新的文件。如果这个参数是 true ,将会在目标目录树中创建新的符号链接。

$ python3 shutil_copytree.py

BEFORE:
[]

AFTER:
['/tmp/example/example',
 '/tmp/example/example.out',
 '/tmp/example/file_to_change.txt',
 '/tmp/example/index.rst',
 '/tmp/example/shutil_copy.py',
 '/tmp/example/shutil_copy2.py',
 '/tmp/example/shutil_copyfile.py',
 '/tmp/example/shutil_copyfile.py.copy',
 '/tmp/example/shutil_copyfileobj.py',
 '/tmp/example/shutil_copymode.py',
 '/tmp/example/shutil_copystat.py',
 '/tmp/example/shutil_copytree.py',
 '/tmp/example/shutil_copytree_verbose.py',
 '/tmp/example/shutil_disk_usage.py',
 '/tmp/example/shutil_get_archive_formats.py',
 '/tmp/example/shutil_get_unpack_formats.py',
 '/tmp/example/shutil_make_archive.py',
 '/tmp/example/shutil_move.py',
 '/tmp/example/shutil_rmtree.py',
 '/tmp/example/shutil_unpack_archive.py',
 '/tmp/example/shutil_which.py',
 '/tmp/example/shutil_which_regular_file.py']

copytree() 方法使用两个回调参数控制它的行为。ignore 参数在每个目录或者子目录以及目录中内容被复制时调用,它返回一个应该被复制的内容列表。copy_function 参数用于在文件实际复制时调用。

shutil_copytree_verbose.py

import glob
import pprint
import shutil

def verbose_copy(src, dst):
    print('copying\n {!r}\n to {!r}'.format(src, dst))
    return shutil.copy2(src, dst)

print('BEFORE:')
pprint.pprint(glob.glob('/tmp/example/*'))
print()

shutil.copytree(
    '../shutil', '/tmp/example',
    copy_function=verbose_copy,
    ignore=shutil.ignore_patterns('*.py'),
)

print('\nAFTER:')
pprint.pprint(glob.glob('/tmp/example/*'))

这个例子中,ignore_patterns() 用于去创建一个忽略方法跳过 Python 源文件。verbose_copy() 复制的文件名称然后调用 copy2() 复制,它是默认的复制方法。

$ python3 shutil_copytree_verbose.py

BEFORE:
[]

copying
 '../shutil/example.out'
 to '/tmp/example/example.out'
copying
 '../shutil/file_to_change.txt'
 to '/tmp/example/file_to_change.txt'
copying
 '../shutil/index.rst'
 to '/tmp/example/index.rst'

AFTER:
['/tmp/example/example',
 '/tmp/example/example.out',
 '/tmp/example/file_to_change.txt',
 '/tmp/example/index.rst']

如果需要移除一个目录以及它的内容,请使用 rmtree()

shutil_rmtree.py

import glob
import pprint
import shutil

print('BEFORE:')
pprint.pprint(glob.glob('/tmp/example/*'))

shutil.rmtree('/tmp/example')

print('\nAFTER:')
pprint.pprint(glob.glob('/tmp/example/*'))

错误默认情况下引发为异常,但是如果第二个参数为 true 将被忽略,也可以通过第三个参数提供一个错误处理方法。

$ python3 shutil_rmtree.py

BEFORE:
['/tmp/example/example',
 '/tmp/example/example.out',
 '/tmp/example/file_to_change.txt',
 '/tmp/example/index.rst']

AFTER:
[]

可以使用 move() 将一个文件或者目录从一个地方移到另一个地方。

shutil_move.py

import glob
import shutil

with open('example.txt', 'wt') as f:
    f.write('contents')

print('BEFORE: ', glob.glob('example*'))

shutil.move('example.txt', 'example.out')

print('AFTER : ', glob.glob('example*'))

原理类似于 Unix 命令 mv 。如果源文件和目标文件都存在,源文件将会被重命名。否则源文件被复制到目的地然后被删除。

$ python3 shutil_move.py

BEFORE:  ['example.txt']
AFTER :  ['example.out']

查找文件

which() 方法会按照一个搜索路径查找文件。典型的使用场景是在环境变量 PATH 定义的路径中查找可执行程序的位置。

shutil_which.py

import shutil

print(shutil.which('virtualenv'))
print(shutil.which('tox'))
print(shutil.which('no-such-program'))

如果没有找到文件,which() 返回 None

$ python3 shutil_which.py

/Users/dhellmann/Library/Python/3.5/bin/virtualenv
/Users/dhellmann/Library/Python/3.5/bin/tox
None

which() 方法接收参数依据文件权限以及搜索路径进行过滤。path 参数默认是 os.environ('PATH'),但是可以是任何由 os.pathsep 分隔的字符串。mode 参数应该是一个匹配文件权限的位掩码。默认情况下查找可执行文件,但是下列的例子使用了可读掩码以及一个搜索路径去查找配置文件。

shutil_which_regular_file.py

import os
import shutil

path = os.pathsep.join([
    '.',
    os.path.expanduser('~/pymotw'),
])

mode = os.F_OK | os.R_OK

filename = shutil.which(
    'config.ini',
    mode=mode,
    path=path,
)

print(filename)

以这种方式查找文件可能会存在冲突,因为在查找文件和使用文件之间,文件可以为删除或者它的权限会被修改。

$ touch config.ini
$ python3 shutil_which_regular_file.py

./config.ini

压缩

Python 标准库包含了许多模块用于管理压缩文件,例如 tarfilezipfileshutil 模块中也有几个高阶方法用于解压缩文件。get_archive_formats() 返回一个当前系统支持的格式名称以及描述列表。

shutil_get_archive_formats.py

import shutil

for format, description in shutil.get_archive_formats():
    print('{:<5}: {}'.format(format, description))

支持的格式依赖于哪些模块和底层库可用,所以这个例子的输出可能依赖于你的系统。

$ python3 shutil_get_archive_formats.py

bztar: bzip2'ed tar-file
gztar: gzip'ed tar-file
tar  : uncompressed tar file
xztar: xz'ed tar-file
zip  : ZIP file

可以使用 make_archive() 创建一个新的压缩文件。其输入旨在最好地支持压缩整个目录以及所有内容。默认情况下,它使用当前工作目录,以便所有文件和子目录出现在压缩文件的顶层。

shutil_make_archive.py

import logging
import shutil
import sys
import tarfile

logging.basicConfig(
    format='%(message)s',
    stream=sys.stdout,
    level=logging.DEBUG,
)
logger = logging.getLogger('pymotw')

print('Creating archive:')
shutil.make_archive(
    'example', 'gztar',
    root_dir='..',
    base_dir='shutil',
    logger=logger,
)

print('\nArchive contents:')
with tarfile.open('example.tar.gz', 'r') as t:
    for n in t.getnames():
        print(n)

这个例子开始于 shutil 示例源目录,然后移动到当前目录上级添加 shutil 目录到 gzip 格式压缩文件中。logging 模块被配置用于打印 make_archive() 产生的消息。

$ python3 shutil_make_archive.py

Creating archive:
changing into '..'
Creating tar archive
changing back to '...'

Archive contents:
shutil
shutil/config.ini
shutil/example.out
shutil/file_to_change.txt
shutil/index.rst
shutil/shutil_copy.py
shutil/shutil_copy2.py
shutil/shutil_copyfile.py
shutil/shutil_copyfileobj.py
shutil/shutil_copymode.py
shutil/shutil_copystat.py
shutil/shutil_copytree.py
shutil/shutil_copytree_verbose.py
shutil/shutil_disk_usage.py
shutil/shutil_get_archive_formats.py
shutil/shutil_get_unpack_formats.py
shutil/shutil_make_archive.py
shutil/shutil_move.py
shutil/shutil_rmtree.py
shutil/shutil_unpack_archive.py
shutil/shutil_which.py
shutil/shutil_which_regular_file.py

shutil 模块维护了一个可在当前系统上解压缩的格式注册表,通过 get_unpack_formats() 访问。

shutil_get_unpack_formats.py

import shutil

for format, exts, description in shutil.get_unpack_formats():
    print('{:<5}: {}, names ending in {}'.format(
        format, description, exts))

这个注册表不同于创建压缩文件的注册表,因为它还包括用于每种格式的常用文件扩展名,以便解压方法根据文件扩展名猜测要使用的格式。

$ python3 shutil_get_unpack_formats.py

bztar: bzip2'ed tar-file, names ending in ['.tar.bz2', '.tbz2']
gztar: gzip'ed tar-file, names ending in ['.tar.gz', '.tgz']
tar  : uncompressed tar file, names ending in ['.tar']
xztar: xz'ed tar-file, names ending in ['.tar.xz', '.txz']
zip  : ZIP file, names ending in ['.zip']

使用 unpack_archive() 解压文件,传入压缩文件名以及可选的解压目录,默认是当前目录。

shutil_unpack_archive.py

import pathlib
import shutil
import sys
import tempfile

with tempfile.TemporaryDirectory() as d:
    print('Unpacking archive:')
    shutil.unpack_archive(
        'example.tar.gz',
        extract_dir=d,
    )

    print('\nCreated:')
    prefix_len = len(d) + 1
    for extracted in pathlib.Path(d).rglob('*'):
        print(str(extracted)[prefix_len:])

这个例子中, unpack_archive() 能够根据文件名后缀 tar.gz 判断压缩格式,并且该值与解压缩注册表中的 gztar 相关联。

$ python3 shutil_unpack_archive.py

Unpacking archive:

Created:
shutil
shutil/config.ini
shutil/example.out
shutil/file_to_change.txt
shutil/index.rst
shutil/shutil_copy.py
shutil/shutil_copy2.py
shutil/shutil_copyfile.py
shutil/shutil_copyfileobj.py
shutil/shutil_copymode.py
shutil/shutil_copystat.py
shutil/shutil_copytree.py
shutil/shutil_copytree_verbose.py
shutil/shutil_disk_usage.py
shutil/shutil_get_archive_formats.py
shutil/shutil_get_unpack_formats.py
shutil/shutil_make_archive.py
shutil/shutil_move.py
shutil/shutil_rmtree.py
shutil/shutil_unpack_archive.py
shutil/shutil_which.py
shutil/shutil_which_regular_file.py

文件系统空间

在执行一个长期运行并且消耗空间的操作之前检查当前系统可用空间会是非常有用的。disk_usage() 返回一个元组表示系统总空间,当前使用总量以及剩余总量。

shutil_disk_usage.py

import shutil

total_b, used_b, free_b = shutil.disk_usage('.')

gib = 2 ** 30  # GiB == gibibyte
gb = 10 ** 9   # GB == gigabyte

print('Total: {:6.2f} GB  {:6.2f} GiB'.format(
    total_b / gb, total_b / gib))
print('Used : {:6.2f} GB  {:6.2f} GiB'.format(
    used_b / gb, used_b / gib))
print('Free : {:6.2f} GB  {:6.2f} GiB'.format(
    free_b / gb, free_b / gib))

disk_usage() 返回的是字节数,所以这个例子程序将他们转换成更可读的并打印。

$ python3 shutil_disk_usage.py

Total: 499.42 GB  465.12 GiB
Used : 246.68 GB  229.73 GiB
Free : 252.48 GB  235.14 GiB

推荐阅读

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/pymotw/shutil-h...

译文地址:https://learnku.com/docs/pymotw/shutil-h...

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~