# 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 из недоверенного источника может привести к выполнению произвольного кода, если данные были подготовлены опытным злоумышленником.