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

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

7.1. Более сложное форматирование вывода

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

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

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

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

    >>> yes_votes = 42_572_654
    >>> no_votes = 43_132_495
    >>> percentage = yes_votes / (yes_votes + no_votes)
    >>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
    ' 42572654 YES votes  49.67%'
    
  • Наконец, вы можете самостоятельно выполнять все операции со строками, используя операции нарезки и конкатенации строк для создания любого макета, который вы только можете себе представить. Тип string имеет несколько методов, которые выполняют полезные операции для заполнения строк до заданной ширины столбца.

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

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

Некоторые примеры:

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print(s)
The value of x is 32.5, and y is 40000...
>>> # The repr() of a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> # The argument to repr() may be any Python object:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

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

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

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

За выражением может следовать необязательный спецификатор формата. Это позволяет лучше контролировать форматирование значения. В следующем примере pi округляется до трех знаков после запятой:

>>> import math
>>> print(f'The value of pi is approximately {math.pi:.3f}.')
The value of pi is approximately 3.142.

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

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...     print(f'{name:10} ==> {phone:10d}')
...
Sjoerd     ==>       4127
Jack       ==>       4098
Dcab       ==>       7678

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

>>> animals = 'eels'
>>> print(f'My hovercraft is full of {animals}.')
My hovercraft is full of eels.
>>> print(f'My hovercraft is full of {animals!r}.')
My hovercraft is full of 'eels'.

Для получения информации об этих спецификациях формата см. справочное руководство для Мини-язык спецификации формата.

7.1.2. Метод String format()

Базовое использование метода str.format() выглядит следующим образом:

>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"

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

>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam

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

>>> print('This {food} is {adjective}.'.format(
...       food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.

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

>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
...                                                    other='Georg'))
The story of Bill, Manfred, and Georg.

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

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
...       'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

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

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

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

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

>>> 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() смотрите в разделе Синтаксис строки форматирования.

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

Вот та же таблица квадратов и кубов, отформатированная вручную:

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # Note use of 'end' on previous line
...     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(). Эти методы ничего не записывают, они просто возвращают новую строку. Если входная строка слишком длинная, они не усекают ее, а возвращают без изменений; это испортит расположение столбцов, но это обычно лучше, чем альтернатива, которая заключается в том, что значение будет ложным. (Если вам действительно нужно усечение, вы всегда можете добавить операцию slice, как в 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. Старое форматирование строк

Оператор % (modulo) также может быть использован для форматирования строк. Если задано 'string' % values, экземпляры % в string заменяются на ноль или более элементов values. Эта операция известна как интерполяция строк. Например:

>>> import math
>>> print('The value of pi is approximately %5.3f.' % math.pi)
The value of pi is approximately 3.142.

Более подробную информацию можно найти в разделе Форматирование строк в стиле printf.

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

open() возвращает file object, и чаще всего используется с двумя позиционными аргументами и одним аргументом ключевого слова: open(filename, mode, encoding=None)

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

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

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

В текстовом режиме при чтении по умолчанию преобразуются специфические для платформы окончания строк (\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()

>>> # We can check that the file has been automatically closed.
>>> 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()
'This is the entire file.\n'
>>> f.read()
''

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

>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''

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

>>> for line in f:
...     print(line, end='')
...
This is the first line of the file.
Second line of the file

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

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

>>> f.write('This is a test\n')
15

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

>>> value = ('the answer', 42)
>>> s = str(value)  # convert the tuple to string
>>> f.write(s)
18

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

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

>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)      # Go to the 6th byte in the file
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2)  # Go to the 3rd byte before the end
13
>>> f.read(1)
b'd'

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

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

7.2.2. Сохранение структурированных данных с помощью json

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

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

Примечание

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

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

>>> import json
>>> x = [1, 'simple', 'list']
>>> json.dumps(x)
'[1, "simple", "list"]'

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

json.dump(x, f)

Для повторного декодирования объекта, если f является объектом binary file или text file, который был открыт для чтения:

x = json.load(f)

Примечание

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

Эта простая техника сериализации может работать со списками и словарями, но сериализация произвольных экземпляров классов в JSON требует немного дополнительных усилий. Ссылка на модуль json содержит объяснение этого.

См.также

pickle - модуль pickle

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

Вернуться на верх