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