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

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

7.1. Особенности форматирования вывода

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

Чаще вам захочется больше контроля над форматированием вашего вывода, чем простое печатание разделенных пробелом значений. Есть два способа форматировать ваш вывод; первый способ - чтобы все строки обрабатывали сами себя; используя нарезание строк и операторы конкатенации, вы можете создать любую разметку, какое можете представить. Строковый тип имеет несколько методов, которые выполняют полезные операции для обивки строк данной шириной столбца; скоро они будут обсуждаться. Второй способ - использовать formatted string literals (docs.python.org/3/reference/lexical_analysis.html#f-strings) или метод str.format().

Модуль string (docs.python.org/3/library/string.html#module-string) содержит класс Template (docs.python.org/3/library/string.html#string.Template), который предлагает еще один способ подстановки значений в строки.

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

Функция str() подразумевалась для возврата представлений значений, которые достаточно читабельны, тогда как repr() подразумевалась для генерации представлений, которые могут быть прочитаны интерпретатором (или приведут к SyntaxError (docs.python.org/3/library/exceptions.html#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...
>>> # repr() строки добавляет строковые кавычки и бэкслэши:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> # Аргумент для repr() может быть любым объектом Python:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

Вот два способа вывести таблицу квадратов и кубов:

>>> 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
 
>>> 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

(Заметьте, что в первом примере один пробел между каждым столбцом был добавлен с помощью 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'

Основное использование метода 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

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

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

Позиционные и keyword (ключевые слова - прим. пер.) аргументы могут быть произвольно скомбинированы:

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

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

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

Необязательное ':' и спецификатор формата могут следовать за именем поля. Это позволяет получить больший контроль над форматированием значения. Следующий пример округляет Pi до трех знаков после точки.

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

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

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

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

>>> 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 = {'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(), которая возвращает словарь, включающий все локальные переменные.

Для полного обзора строкового форматирования с помощью str.format() см. Format String Syntax (docs.python.org/3/library/string.html#formatstrings).

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

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

>>> 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() возвращает файловый объект (docs.python.org/3/glossary.html#term-file-object) и чаще всего используется с двумя аргументами: open(filename, mode).

>>> f = open('workfile', 'w')

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

 Обычно файл открывается в текстовом режиме, это означает, что вы считываете из файла и записываете в файл строки. Файл кодируется в определенной кодировке. Если кодировка не указана, то по умолчанию она зависит от платформы (см. open()). Добавление 'b' к режиму откроет файл в режиме binary (бинарном - прим. пер.): теперь данные читаются и записываются из байтовых объектов. Этот режим следует использовать для всех файлов, которые не содержат текст.

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

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

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

Считывая содержимое файла, вызовите f.read(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)  # конвертирование кортежа в строку
>>> f.write(s)
18

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

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

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

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

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

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

Хорошая практика - использовать ключевое слово with (docs.python.org/3/reference/compound_stmts.html#with), когда имеете дело с объектами-файлами. У этого есть преимущество, что файл правильно закроется после завершения его набора, даже если по пути возникнет исключение. Это также намного короче, чем писать эквивалентный try(docs.python.org/3/reference/compound_stmts.html#try)-finally(docs.python.org/3/reference/compound_stmts.html#finally) блок:

>>> with open('workfile', 'r') as f:
...     read_data = f.read()
>>> f.closed
True

Файловые объекты имеют несколько дополнительных методов, таких как isstty() и trancate(), которые используются менее часто; обратитесь к справке по библиотеке для полного экскурса по объектам-файлам.

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

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

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

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

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

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

Другой вариант функции dumps() (docs.python.org/3/library/json.html#json.dumps), называемый dump() (docs.python.org/3/library/json.html#json.dump), просто сериализует объект в текстовый файл (docs.python.org/3/glossary.html#term-text-file). Так если f - текстовый файловый объект, открытый на запись, мы можем сделать это:

json.dump(x, f)

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

x = json.load(f)

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

См. также: pickle (docs.python.org/3/library/pickle.html#module-pickle) - модуль pickle

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

Создано

Обновлено