Рубрики
Без рубрики

shutil – высокоуровневые файловые операции

Автор оригинала: Doug Hellmann.

Цель:

Файловые операции высокого уровня.

Модуль 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.*'))

Поскольку функция открывает входной файл для чтения, независимо от его типа, специальные файлы (например, узлы устройств Unix) не могут быть скопированы как новые специальные файлы с помощью copyfile () .

$ 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, n1):
        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/*'))

Аргумент символические ссылки определяет, копируются ли символические ссылки как ссылки или как файлы. По умолчанию содержимое копируется в новые файлы. Если опция истинна, в целевом дереве создаются новые символические ссылки.

$ 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_functionverbose_copy,
    ignoreshutil.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/*'))

По умолчанию ошибки возникают как исключения, но их можно игнорировать, если второй аргумент истинен, а в третьем аргументе может быть указана специальная функция обработчика ошибок.

$ 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',
    modemode,
    pathpath,
)

print(filename)

По-прежнему существует состояние гонки при поиске читаемых файлов таким образом, потому что за время между поиском файла и фактической попыткой его использования файл может быть удален или его разрешения могут быть изменены.

$ touch config.ini
$ python3 shutil_which_regular_file.py

./config.ini

Архивы

Стандартная библиотека Python включает множество модулей для управления архивными файлами, такими как tarfile и zipfile. Также в shutil есть несколько функций более высокого уровня для создания и извлечения архивов. 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 () , чтобы создать новый архивный файл. Его входные данные предназначены для наилучшей поддержки рекурсивного архивирования всего каталога и всего его содержимого. По умолчанию он использует текущий рабочий каталог, так что все файлы и подкаталоги отображаются на верхнем уровне архива. Чтобы изменить это поведение, используйте аргумент root_dir для перехода к новой относительной позиции в файловой системе и аргумент base_dir , чтобы указать каталог для добавления в архив.

shutil_make_archive.py

import logging
import shutil
import sys
import tarfile

logging.basicConfig(
    format'%(message)s',
    streamsys.stdout,
    levellogging.DEBUG,
)
logger  logging.getLogger('pymotw')

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

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

Этот пример начинается в исходном каталоге примеров для shutil и перемещается на один уровень вверх в файловой системе, а затем добавляет каталог shutil в архив tar, сжатый с помощью gzip. Модуль ведения журнала настроен на отображение сообщений от 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_dird,
    )

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

В этом примере unpack_archive () может определять формат архива, потому что имя файла заканчивается на tar.gz , и это значение связано с gztar format в реестре формата распаковки.

$ 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>
gb  10 ** 9   # GB>

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

Смотрите также