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

Ознакомить пользователя с выводом программы можно различными способами — данные могут быть выведены в читаемом виде или записаны в файл для последующего использования. Часть возможностей будет обсуждена в этой главе.

7.1. Удобное форматирование вывода

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

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

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

Если вам не нужны вычурные выходные данные, а просто требуется быстрое отображение некоторых переменных для отладки, вы можете преобразовать любое значение в строку функциями 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...
>>> # Функция 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'))"

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

7.1.1. Форматирующие строковые литералы

Форматированные строковые литералы (также называемые 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. Метод 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.

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

>>> 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() см. в разделе Синтаксис 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(). Эти методы не выводят ничего, они лишь возвращают новую строку. Если строка на входе чересчур длинная, то они не усекают её, а возвращают без изменения; это испортит вашу столбцовую разбивку, но обычно это лучше, чем альтернатива, которая бы наврала о значении. (Если вам действительно хочется сделать обрезку, то всегда можно добавить операцию слайса, например: 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. Форматирование строк в старом стиле

Оператор % (по модулю) также может использоваться для форматирования строк. Учитывая '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() возвращает файловый объект и чаще всего используется с двумя аргументами: open(filename, mode).

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

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

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

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

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

>>> with open('workfile') 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()
'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, whence). Положение вычисляется на основе добавления offset к точке отсчета; точка отсчета выбирается аргументом whence. Значение 0 параметра whence отмеряет смещение от начала файла, 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() или ноль. Любые другие значения offset производят неопределенное поведение.

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

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

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

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

Примечание

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

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

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

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

json.dump(x, f)

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

x = json.load(f)

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