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
    >>> total_votes = 85_705_149
    >>> percentage = yes_votes / total_votes
    >>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
    ' 42572654 YES votes  49.67%'
    

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

  • Наконец, вы можете самостоятельно выполнять все операции со строками, используя операции нарезки и конкатенации строк для создания любого макета, который вы только можете себе представить. У типа 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'.

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

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

Дополнительные сведения о спецификаторе self-documenting expressions см. в разделе =. Дополнительные сведения о спецификаторах формата см. в справочном руководстве для Мини-язык спецификации формата.

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(), которая возвращает словарь, содержащий все локальные переменные:

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

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. При открытии файла в двоичном режиме нельзя указывать кодировку.

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

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