Існує кілька способів презентації виводу програми; дані можуть виводитися для читання людиною чи записуватися у файл для використання у майбутньому. У цьому розділі буде розглянуто деякі можливості.
Можливості форматування виводу
Раніше ми познайомилися з двома способами запису величин:
твердження з виразами та твердження
print
. (Третій спосіб - це використання методу
write()
файлових об'єктів; на файл стандартного
виводу можна посилатися через sys.stdout
. Див.
"Опис бібліотеки" для подальшої інформації з цього приводу).
Часто потрібно більше контролю над форматуанням виводу, аніж простий
роздрук розділених комами величин. Існують два способи форматування
виводу. Перший - це самому приготувати ланцюжки для виводу: за
допомогою операцій членування та з'єднання можна отримати будь-яке
можливе форматування. Стандартний модуль string
має деякі корисні операції для заповнення ланцюжків до певної ширини
колонок, про що йтиметься незабаром. Другий спосіб - використання
оператора %
з ланцюжком в якості лівосторонього
параметра. Оператор %
інтерпретує лівосторонній аргумент
подібно до функції sprintf()
і застосовує
до нього правосторонній аргумент, повертаючи при цьому ланцюжок, що є
результатом операції форматування.
Тоді лишається таке питання: яким чином можна конвертувати різні
величини у ланцюжки? На щастя, Пітон має функції для конвертування
будь-яких величин у ланцюжки: repr()
та str()
.
Зворотні лапки (`</code>)
еквівалентні функції
repr()`, але їхне
вживання небажане.
Функція str()
створює
репрезентації, пристосовані для людського ока, тоді як
repr()
- репрезентації,
призначені для інтерпретатора (при цьому остання функція видасть
помилку SyntaxError
при застосуванні неправильного синтаксису). Для об'єктів, що не мають
певної репрезентації, призначеної для людського ока, str()
поверне таку саму репрезентацію, як і repr()
. Багато величин,
скажімо, числа чи такі
структури як списки чи словники, мають однакову репрезентацію при
застосуванні бідь-якої з цих функцій. Ланцюжки та числа з рухомою комою
мають дві різні репрезентації.
Окремі приклади:
>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> s1='привіт, світе\n' # кодування UTF-8
>>> str(s1)
'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd1\x96\xd1\x82, \xd1\x81\xd0\xb2\xd1\x96\xd1\x82\xd0\xb5\n'
>>> repr(s1)
"'\\xd0\\xbf\\xd1\\x80\\xd0\\xb8\\xd0\\xb2\\xd1\\x96\\xd1\\x82, \\xd1\\x81\\xd0\\xb2\\xd1\\x96\\xd1\\x82\\xd0\\xb5\\n'"
>>> print s1
привіт, світе
>>> str(0.1)
'0.1'
>>> repr(0.1)
'0.10000000000000001'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'Величина x дорівнює ' + repr(x) + ', а y - ' + repr(y) + '...'
>>> print s
Величина x дорівнює 32.5, а y - 40000...
>>> # Функція repr() додає лапки та зворотні скісні риски:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print hellos
'hello, world\n'
>>> # Аргументом функції repr() може бути будь-який об'єкт:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"
>>> # зворотні лапки зручні у діалоговому режимі:
... `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),
... # Зауважте кінцеву кому на попередньому рядку
... 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 '%2d %3d %4d' % (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
,
яка завжди додає пробіли між аргументами).
Цей приклад демонструє метод rjust()
ланцюжкових об'єктів, який вирівнює ланцюжки по правій стороні шляхом
додання пробілів з лівої сторони. Існують також подібні методи для
вирівнювання по центру (center()
) та по лівій
стороні (ljust()
). Ці методи не роблять жодної
операції виводу, а лише повертають новий ланцюжок. Якщо ланцюжок, що
вирівнюється, задовгий - то вони повертають його як є, без скорочення.
При цьому вигляд колонок буде спотворено, але здебільшого це краще, ніж
вивід хибної величини. (Якщо скорочення величини все-таки
потрібне, то цього можна завжди досягти за допомогою операції
членування: "x.ljust( n)[:n]
").
Існує також інший метод, zfill()
, що додає
зліва нулі до ланцюжка, що виражає число. Цей метод також розуміє знаки
плюс та мінус:
>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'
Використання оператора %
виглядає таким чином:
>>> import math
>>> print 'Величина числа Пі приблизно дорівнює %5.3f.' % math.pi
Величина числа Пі приблизно дорівнює 3.142.
Якщо треба сформатувати більш ніж один ланцюжок, то правий операнд повинен задаватися кортежем, як у цьому прикладі:
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
... print '%-10s ==> %10d' % (name, phone)
...
Jack ==> 4098
Dcab ==> 7678
Sjoerd ==> 4127
Більшість форматів працюють точнісінько як і в C і потребують
належного типу, але при передачі неправильного типу ви отримуєте
виняток, а не фатальну помилку (core dump). Формат %s
є найбільш гнучким: якщо відповідний аргумент не є ланцюжком, то
відбувається авоматична конверсія за допомогою вбудованої функції
str()
. Використання зірочки (*
) для
передачі ширини чи точності як окремого (цілочислового) аргумента також
можливе. Формати мови C %n
та %p
не
підтримуються.
Якщо потрібно сформатувати доволі довгий ланцюжок, який ви не хочете
розбивати на окремі підланцюжки, то набагато простіше посилатися на на
параметри за допомогою ключів, а не позицій. Це можна зробити у формі %(назва)формат
,
як це і показано нижче:
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print 'Jack: %(Jack)d; Sjoerd: %(Sjoerd)d; Dcab: %(Dcab)d' % table
Jack: 4098; Sjoerd: 4127; Dcab: 8637678
Це особливо корисно у комбінації з новою вмонтованою функцією vars()
, яка повертає словник, що
складається з усіх локальних змінних.
Зчитування і запис файлів
Функція open()
повертає файловий об'єкт і здебільшого вживається з двома
аргументами: "open(_назва_файла_, _режим_)
".
>>> f=open('/tmp/workfile', 'w')
>>> print f
<open file '/tmp/workfile', mode 'w' at 80a0960>
Перший аргумент - ланцюжок, що є назвою файла. Другий аргумент -
також ланцюжок, що містить кілька символів, котрі описують, яким саме
чином слід використовувати файл. Режим може бути 'r'
(read), коли файл відкривається лише для зчитування, 'w'
(write) - для запису (існуючий файл з цією назвою буде зтерто), 'a'
(append) - для додання (усі нові дані будуть автоматично додані в
кінець файла), а також 'r+'
, що відкриває файл для
зчитування та запису одночасно. Аргумент, що задає режим не є
обов'язковим, якщо його не вказано, то файл буде відкрито для
зчитування.
На системах Windows та Macintosh 'b'
, доданий до
режиму, вказує на те, що файл повинен бути відкрито у бінарному режимі.
Таким чином на цих системах маємо такі додаткові режими: 'rb'
,
'wb'
, and 'r+b'
. Windows розрізняє між
текстовими та бінарними файлами; символ нового рядка автоматично
змінюється при зчитуванні чи записі даних. Така позакулісна зміна
файлових даних годиться для роботи з файлами у кодуванні ASCII, але
вона зіпсує бінарні дані таких форматів як JPEG чи EXE. При
зчитуванні
чи записі таких файлів необхідно використовувати бінарний режим.
(Зауважте, що точна семантика текстового режиму на Macintosh залежить
від використовуваної бібліотеки мови C).
Методи файлових об'єктів
У заключній частині цього розділу припускається, що файловий об'єкт з
назвою f
вже було створено.
Зчитування змісту файла здійснюється за допомогою виклику f.read(_розмір_)
,
який зчитує певну кількість даних і повертає їх у формі ланцюжка. розмір
- це необов'язковий числовий аргумент. Якщо розмір пропущено
або його величина є негативною, то буде зчитано і повернуто увесь файл;
якщо ж файл удвічі більний за пам'ять вашого комп'ютера - то це ваша
особиста проблема. Якщо ж розмір вказано, то буде зчитано
кількість байтів, що дорівнює або є меншою за вказану величину.
Якщо дойдено до кінця файла, f.read()
поверне пустий
ланцюжок (""
).
>>> f.read()
'Це увесь файл.\n'
>>> f.read()
''
f.readline()
зчитує окремий рядок із файла; символ нового
рядка (\n
) лишається наприкінці ланцюжка (він може бути
відсутнім лише в отаньому рядку, якщо файл не закінчується цим
символом). Завдяки цьому повернена величина є однозначною; якщо f.readline()
повертає пустий ланцюжок, то це означає, що досягнуто кінця файла, тоді
як пустий рядок репрезентовано символом '\n'
, тобто
ланцюжком, що складається з символа нового рядка.
>>> f.readline()
'Це перший рядок файла.\n'
>>> f.readline()
'Другий рядок файла.\n'
>>> f.readline()
''
f.readlines()
повертає список, що складається з усіх
рядків файла. Якщо цей метод отримує необов'язковий параметр бажанийрозмір_, то буде зчитано
вказану кількість байтів, плюс стільки, скільки потрібно, щоб завершити
рядок. Це часто використовується для швидкого зчитування великих
файлів радок за рядком без завантаження цілого файла в пам'ять
комп'ютера. Лише цілісні рядки буде повернуто.
>>> f.readlines()
['Це перший рядок файла.\n', 'Другий рядок файла\n']
f.write(ланцюжок)
записує вміст ланцюжка в файл, повертаючи None
.
>>> f.write('Це - тест\n')
f.tell()
повертає ціле число, що вказує поточну позицію у
файлі, виміряну в байтах, починаючи від початку файла. Для зміни
позиції файлового об'єкта слід використовувати "f.seek(_скільки_, _звідки_)
".
Позиція обчислюється шляхом додання величини, вираженої
параметром скільки, до пункту відліку, що
визначається параметром звідки
і може мати такі величини: 0 (початок файла), 1 (поточна позиція), 2
(кінець файла). Параметр звідки
може бути пропущено, при цьому пошук позиції відбудеться відносно
початку файла.
>>> f=open('/tmp/workfile', 'r+')
>>> f.write('0123456789abcdef')
>>> f.seek(5) # Йдемо до 6-го байта у файлі
>>> f.read(1)
'5'
>>> f.seek(-3, 2) # Йдемо до 3-го байта з кінця
>>> f.read(1)
'd'
Після того, як роботу з файлом закінчено, слід викликати f.close()
для його закриття і звільнення системних ресурсів, що використовуються
відкритим файлом. Після виклику f.close()
спроба
використання файлового об'єкта призведе до помилки.
>>> f.close()
>>> f.read()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file
Файлові об'єкти мають такі додаткові методи, як isatty()
та truncate()
, що набагато рідше
використовуються. Див. "Опис бібліотеки" для докладнішої
інформації, що стосується файлових об'єктів.
Модуль pickle
Ланцюжки можуть легко записуватися у файл та зчитуватися з нього.
Певного зусилля потребують числа, тому що read()
повертає лише ланцюжки, які треба треба обробити за допомогою певної функції,
скажімо, int()
, котра отримує ланцюжок
(напр, "123") і повертає його числову величину (123). При записі більш
складних типів даних (списків, словників чи реалізацій класів)
ситуація значно ускладнюється.
Щоб не завдавати користувачам зайвих турбот, пов'язаних зі
створенням та налаштуванням коду для збереження складних типів даних,
Пітон має стандартний
модуль, що зветься pickle
.
Це чудовий модуль, що може взяти
будь-який об'єкт мови Пітон (і навіть певні форми коду!), і створити
його ланцюжкову репрезентацію. Цей процес зветься запаковуванням
(pickling, дослівно "маринування"). Реконструкція
об'єкта з
його ланцюжкової репрезентації зветься розпаковуванням (unpickling). Між цими двома
операціями ланцюжкова репрезентація об'єкта може бути збережена у
файлі чи переслана через мережу до віддаленої машини.
Якщо існує об'єкт x
, і файловий об'єкт f
,
відкритий для запису, то найпростіший спосіб запаковування об'єкта
потребує одного-єдиного рядка коду:
pickle.dump(x, f)
Для зворотньої дії, якщо f
- файловий об'єкт,
відкритий для зчитування:
x = pickle.load(f)
(Існують також можливості для запаковування кількох об'єктів одночасно,
для збереження результату не в файлі тощо. Докладнішу
інформацію можна отримати у документації модуля pickle
)
pickle
- це стандартний спосіб зберігання коду та його
подальшого використання іншими програмами чи іншим викликом цієї ж
програми. Технічний термін для цього явища - стійкий об'єкт (persistent
object). Чезе те що pickle
дуже широко використовується, численні
автори, що створюють розширення для Пітона, перевіряють чи нові типи
даних (напр., матриці) можуть нормально запаковуватися та
розпаковуватися.