7. Ввод и вывод

Есть несколько способов представить вывод программы. Данные могут быть напечатаны в удобочитаемом для человека виде или записаны в файл для дальнейшего использования. В этой главе рассматриваются разные возможности ввода и вывода.

7.1. Продвинутое форматирование вывода

До сих пор мы встречались с двумя способами вывода значений: инструкциями выражений и функцией print(). (Третий способ — использование метода write() объектов файлов; на стандартный выходной файл можно ссылаться как на sys.stdout. См. Справочник по библиотеке для получения дополнительной информации об этом.)

Часто вам захочется иметь больше контроля над выводом, а не просто выводить значения, разделённые пробелами. Существует несколько способов форматировать вывод.

  • Чтобы использовать форматированные строковые литералы, поставьте f или F перед открывающей одинарной или тройной кавычкой у строкового литерала. Внутри такой строки можно писать выражение Python между символами { и }. Оно может содержать переменные и другие литералы.

    >>> year = 2016
    >>> event = 'Референдум'
    >>> f'Результаты {event} {year}'
    'Результаты Референдум 2016'
    
  • Метод строк str.format() требует больше ручной работы. Вы по-прежнему будете использовать { и }, для обозначения места подстановки значения, и сможете предоставить подробные директивы форматирования, но вам также необходимо будет предоставить сами данные для форматирования. В следующем блоке кода приведены два примера форматирования переменных:

    >>> yes_votes = 42_572_654
    >>> total_votes = 85_705_149
    >>> percentage = yes_votes / total_votes
    >>> '{:-9} голосов ЗА {:2.2%}'.format(yes_votes, percentage)
    ' 42572654 голосов ЗА 49.67%'
    

    Обратите внимание, что yes_votes дополняется пробелами и знаком минус только для отрицательных чисел. В примере также выводится percentage, умноженный на 100, с двумя десятичными знаками, за которыми следует знак процента (подробности см. в Format specification mini-language).

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

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

Функция str() предназначена для получения представлений значений, удобных для чтения человеком, тогда как repr() предназначена для создания представлений, которые понимает интерпретатор (или которые вызовут SyntaxError, если эквивалентного синтаксиса нет). Для объектов без специального представления для людей, функция str() вернёт то же значение, что repr(). Многие значения, такие как числа или структуры вроде списков и словарей, имеют одинаковое представление в обоих вариантах. У строк, однако, есть два различных представления.

Несколько примеров:

>>> s = 'Привет, мир.'
>>> str(s)
'Привет, мир.'
>>> repr(s)
"'Привет, мир.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'Значение x равно ' + repr(x) + ', и y равно ' + repr(y) + '...'
>>> print(s)
Значение x равно 32.5, и y равно 40000...
>>> # repr() для строки добавляет кавычки и обратные слэши
>>> hello = 'привет, мир\n'
>>> hellos = repr(hello)
>>> print(hellos)
'привет, мир\n'
>>> # Аргументов repr() может быть любой объект Python:
>>> repr((x, y, ('спам', 'яйца')))
"(32.5, 40000, ('спам', 'яйца'))"

Модуль string поддерживает простой подход к шаблонам на основе регулярных выражений через string.Template. Это даёт ещё один способ подставлять значения в строки, используя заполнители вроде $x и заменяя их значениями из словаря. Этот синтаксис прост в использовании, хотя он предлагает гораздо меньше контроля над форматированием.

7.1.1. Форматированные строковые литералы

Форматированные строковые литералы (также называемые f-строками) позволяют включать значения любых выражений Python в строку, предваряя её f или F и записывая выражение как {expression}.

За выражением может следовать необязательный спецификатор формата. Он позволяет точнее контролировать, как именно значение будет отформатировано. Например, так можно округлить число π до трёх десятичных цифр в дробной части:

>>> import math
>>> print(f'Значение π приблизительно равно {math.pi:.3f}.')
Значение π приблизительно равно 3.142.

Передача целое числа после ':' задаёт минимальную ширину этого поля в символах. Это полезно для выравнивания столбцов.

>>> table = {'Шоэрд': 4127, 'Джек': 4098, 'Дкаб': 7678}
>>> for name, phone in table.items():
...     print(f'{name:10} ==> {phone:10d}')
...
Шоэрд      ==>       4127
Джек       ==>       4098
Дкаб       ==>       7678

Другие модификаторы преобразуют значение перед форматированием. '!a' применяет ascii(), '!s' применяет str(), а '!r' применяет repr() к значению:

>>> animals = 'угри'
>>> print(f'Весь мой экраноплан захватили {animals}.')
Весь мой экраноплан захватили угри.
>>> print(f'Весь мой экраноплан захватили {animals!r}.')
Весь мой экраноплан захватили 'угри'.

Спецификатор = может быть использован для расширения выражения до текста самого выражения, знака равенства и представления вычисленного выражения.

>>> bugs = 'roaches'
>>> count = 13
>>> area = 'living room'
>>> print(f'Debugging {bugs=} {count=} {area=}')
Debugging bugs='roaches' count=13 area='living room'

См. самодокументирующиеся выражения для получения дополнительной информации о спецификаторе =. Для ссылки на эти спецификации формата см. руководство по Format specification mini-language.

7.1.2. Строковой метод format()

Простое использование метода str.format() выглядит так:

>>> print('Мы — те {}, кто говорит "{}!"'.format('рыцари', 'Нет'))
Мы — те рыцари, кто говорит "Нет!"

Фигурные скобки и символы внутри них (называемые форматными полями) заменяются объектами, которые были переданы в метод str.format(). Число в скобках может использоваться для ссылки на позицию объекта, переданного в метод str.format().

>>> print('{0} и {1}'.format('спам', 'яйца'))
спам и яйца
>>> print('{1} и {0}'.format('спам', 'яйца'))
яйца и спам

Если в метод str.format() переданы именованные аргументы, их значения можно использовать по имени.

>>> print('Этот {food} просто {adjective}.'.format(
...       food='спам', adjective='абсолютно ужасен'))
Этот спам просто абсолютно ужасен.

Позиционные и именованные аргументы могут быть использованы вместе произвольным образом:

>>> print('В истории оказались {0}, {1} и {other}.'.format('Билл', 'Мэнфред',
...                                                    other='Георг'))
В истории оказались Билл, Мэнфред и Георг.

Если у вас очень длинная форматная строка, которую не хочется разбивать, можно ссылаться на переменные, используемые в форматировании, по имени вместо позиции. Для этого достаточно передать словарь и использовать квадратные скобки '[]' для доступа по ключам.

>>> table = {'Шоэрд': 4127, 'Джэк': 4098, 'Дкаб': 8637678}
>>> print('Джэк: {0[Джэк]:d}; Шоэрд: {0[Шоэрд]:d}; '
...       'Дкаб: {0[Дкаб]:d}'.format(table))
Джэк: 4098; Шоэрд: 4127; Дкаб: 8637678

Это также можно сделать, передавая словарь table в качестве именованных аргументов с помощью нотации **.

>>> table = {'Шоэрд': 4127, 'Джэк': 4098, 'Дкаб': 8637678}
>>> print('Джэк: {Джэк:d}; Шоэрд: {Шоэрд:d}; Дкаб: {Дкаб:d}'.format(**table))
Джэк: 4098; Шоэрд: 4127; Дкаб: 8637678

Особенно удобно это сочетать со встроенной функцией vars(), которая возвращает словарь, содержащий все локальные переменные:

>>> table = {k: str(v) for k, v in vars().items()}
>>> message = " ".join([f'{k}: ' + '{' + k +'};' for k in table.keys()])
>>> print(message.format(**table))
__name__: __main__; __doc__: None; __package__: None; __loader__: ...

В качестве примера следующие строки выводят аккуратно выровненные колонки с целыми числами, их квадратами и кубами:

>>> for x in range(1, 11):
...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

Для полного обзова форматиорвания строк с помощью str.format() смотрите Format string syntax.

7.1.3. Ручное форматирование строк

Ту же таблицу из квадратов и кубов можно было отформатировать вручную:

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # Обратите внимание на использование 'end' в предыдущей строке
...     print(repr(x*x*x).rjust(4))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

(Заметьте, что пробелы между столбцами были добавлены функцией print() как разделители между аргументами.)

Метод строк str.rjust() выравнивает строку по правому краю до заданной ширины, добавляя пробелы в начало. Существуют аналогичные методы str.ljust() и str.center(). Эти методы ничего не выводят, а возвращают новую строку. Если данная строка слишком длинная, они не обрежут её, а вернут как есть. Это нарушит выравнивание колонок, но обычно это лучше, чем искажать значение. (Если вам хочется обрезать строку, всегда можно использовать срез: x.ljust(n)[:n].)

Есть ещё один метод, str.zfill(), который заполняет числовую строку слева нулями. Он учитывает знаки плюс и минус:

>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'

7.1.4. Старое форматирование строк

Оператор % (по модулю) также можно использовать для форматирования строк. В выражении format % values (где format — это строка), спецификаторы преобразования % в format заменяются нулём или несколькими элементами values. Эта операция широко известна как строковая интерполяция. Например:

>>> import math
>>> print('Значение π приблизительно равно %5.3f.' % math.pi)
Значение π приблизительно равно 3.142.

Больше информации можно найти в разделе printf-style String Formatting.

7.2. Чтение и запись файлов

Функция open() возвращает файловый объект и чаще всего вызывается с двумя позиционными аргументами и одним именованным: open(filename, mode, encoding=None)

>>> f = open('workfile', 'w', encoding="utf-8")

Первый аргумент — строка, содержащая имя файла. Второй аргумент — строка, содержащая несколько символов, которые описывают, каким образом файл будет использован. mode может быть 'r' для чтения (от английского read), 'w' для записи (от английского write) (существующий файл с этим именем будет удалён), и 'a' для добавления информации в конец файла (от английского append). r+ открывает файл для чтения и записи одновременно. Аргумент mode необязательный, если режим не указан, он считается равным 'r'.

Обычно файлы открываются в текстовом режиме, что означает, что вы читаете и записываете строки из файла, которые закодированы в определенной кодировке. Если кодировка не указана, то значение по умолчанию зависит от платформы (см. open()). Поскольку UTF-8 является современным стандартом де-факто, рекомендуется использовать encoding="utf-8", если вы не уверены, что вам нужно использовать другую кодировку. Добавление 'b' к атрибуту mode открывает файл в бинарном режиме. Данные в бинарном режиме читаются и записываются в виде объектов bytes. Вы не можете указывать кодировку при открытии файла в бинарном режиме.

В текстовом режиме поведение по умолчанию — преобразовывать специфичные для платформы окончания строк (\n на Unix, \r\n на Windows) в просто \n. При записи в текстовом режиме происходит обратное — \n превращаются в специфичные для платформы окончания строк. Такие изменения подходят для текстовых файлов, но испортят двоичные данные, например, JPEG или EXE. Будьте осторожны и используйте только двоичный режим при чтении и записи таких файлов.

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

>>> with open('workfile', encoding="utf-8") as f:
...     read_data = f.read()

>>> # Мы можем проверить, что файл был автоматически закрыт.
>>> f.closed
True

Если вы не используете конструкцию with, вам придётся вызвать f.close(), чтобы закрыть файл и вернуть системные ресурсы, которые он использовал.

Предупреждение

Вызов f.write() без использования конструкции with или без вызова f.close() может привести к тому, что данные, переданные в f.write(), будут записаны на диск не полностью, даже если программа завершится успешно.

После того, как файловый объект закрыт — либо с помощью инструкции with, либо с помощью вызова f.close() — любая попытка использовать его автоматически завершится ошибкой.

>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

7.2.1. Методы файловых объектов

Оставшиеся примеры в данном разделе предполагают, что файловый объект f уже был создан.

Чтобы прочитать содержимое файла, можно вызвать f.read(size), что прочтёт некоторое количество данных и вернёт их в виде строки (в текстовом режими) или объекта байтов (в двоичном режиме). size — необязательный числовой аргумент. Если size опущен или отрицательный, всё содержимое файла будет прочитано и возвращено. К этому надо относиться осторожно, особенно если файл большой и может не поместиться в оперативную память. В противном случае не более size символов (в текстовом режиме) или size байтов (в двоичном режиме) будет прочитано и возвращено. Если достигнут конец файла, f.read() вернёт пустую строку (''). ::)

>>> f.read()
'Это целый файл.\n'
>>> f.read()
''

Метод f.readline() считывает одну строку из файла; символ перевода строки (\n) остаётся в конце строки и отсутствует только у последней строки, если она не кончается символом перевода строки. Это делает возвращаемое значение однозначным: если метод f.readline() вернул пустую строку, достигнут конец файла, а пустая строка, содержащая только символ перевода строки, представлена как '\n'.

>>> f.readline()
'Это первая строка файла.\n'
>>> f.readline()
'Вторая строка файла\n'
>>> f.readline()
''

Для чтения строк из файла можно пройтись циклом по файловому объекту. Это эффективно с точки зрения памяти, быстро и делает код проще:

>>> for line in f:
...     print(line, end='')
...
Это первая строка файла.
Вторая строка файла

Если вы хотите прочитать все строки файла в список, можете использовать list(f) или f.readlines().

Метод f.write(string) записывает содержимое string в файл и возвращает количество записанных символов.

>>> f.write('Это такой тест\n')
15

Другие типы объектов нуждаются в преобразовании — либо в строку (в текстовом режиме), либо в объект байтов (в двоичном режиме) — перед записью:

>>> value = ('the answer', 42)
>>> s = str(value)  # преобразование кортежа в строку
>>> f.write(s)
18

Метод f.tell() возвращает целое число, указывающее текущую позицию в файле — в байтах от начала файла в бинарном режиме и в виде внутреннего условного числа в текстовом режиме.

Чтобы изменить позицию внутри файлового объекта, используйте метод f.seek(offset, whence). Новая позиция вычисляется с помощью добавления offset к точке отсчёта, задаваемой аргументом whence. Значение 0 выберет точкой отсчёта начало файла, 1 — текущее положение, а 2 — конец файла. Аргумент whence может быть опущен, в таком случае его значение будет 0 и точкой отсчёта будет начало файла.

>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)      # Переход к 6-му байту в файле
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2)  # Переход к 3-му байту с конца
13
>>> f.read(1)
b'd'

В текстовых файлах (открытых без символа b в строке режима) разрешены только смещения относительно начала файла (за исключением смещения к самому концу с помощью seek(0, 2)), причём в качестве значения offset допускаются только числа, которые вернул метод f.tell() или 0. Любое другое значение для offset приведёт к неопределённому поведению.

Файловые объекты имеют ещё несколько дополнительных методов, таких как isatty() и truncate(), которые используются реже. За полным руководством по файловым объектам обращайтесь к Справочнику по библиотеке.

7.2.2. Сохранение структурированных данных с json

Строки могут быть легко записаны в файл и считаны из него. Числа требуют немного больше усилий, так как метод read() возвращает только строки, которые должны быть переданы в функцию, такую как int(), которая принимает строку, например, '123', и возвращает её числовое значение 123. Когда вы хотите сохранить более сложные типы данных, такие как вложенные списки и словари, парсинг и сериализация вручную становятся сложными.

Вместо того, чтобы заставлять пользователей постоянно писать и отлаживать код для сохранения сложных типов данных в файлах, Python позволяет использовать популярный формат обмена данными, называемый JSON (JavaScript Object Notation). Стандартный модуль под названием json может принимать иерархические структуры данных Python и преобразовывать их в строковые представления; этот процесс называется сериализацией. Восстановление данных из строкового представления называется десериализацией. Между сериализацией и десериализацией строка, представляющая объект, могла быть сохранена в файле или в другом хранилище данных либо отправлена по сетевому соединению на какой-либо удаленный компьютер.

Примечание

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

Если у вас есть объект x, вы можете получить его строковое представление в формате JSON с помощью простой строки кода:

>>> import json
>>> x = [1, 'простой', 'список']
>>> json.dumps(x)
'[1, "простой", "список"]'

Другой вариант функции dumps()dump() — сериализует объект прямо в текстовый файл. Так, если f — объект текстового файла, открытый для записи, можно сделать следующее:

json.dump(x, f)

Чтобы снова декодировать объект, если f — это объект бинарного файла или текстового файла, который был открыт для чтения, выполните:

x = json.load(f)

Примечание

Файлы JSON должны быть закодированы в UTF-8. Используйте encoding="utf-8" при открытии файла JSON в виде текстового файла как для чтения, так и для записи.

Такой простой способ сериализации работает со списками и словарями, но чтобы сериализовывать экземпляры произвольных классов в формате JSON, требуется чуть больше работы. Справка по модулю json расскажет, как это сделать.

См. также

pickle — модуль консервирования объектов

В отличие от JSON, pickle — это протокол, который позволяет сериализовать объекты Python любой сложности. Он специфичен для Python и не может быть использован для взаимодействия с приложениями, написанными на других языках. Ещё он небезопасен по умолчанию: десериализация данных в формате pickle из недоверенного источника может привести к выполнению произвольного кода, если данные были подготовлены опытным злоумышленником.