Kodomo

Пользователь

Внутри файла

О документации питона

Напоминаю в очередной раз, как добывать документацию:

Подсказка из зала: Гууугл!!!

Ну, да, и гугл тоже.

Тут есть такая тонкость: когда вы ищете гуглом, вы читаете про разные способы решения той же задачи (что хорошо, покуда она простая), а когда вы читаете стандартную документацию, вы читаете про разные (соседние) возможности питона (а это расширяет ваш кругозор – у вас рано или поздно может быть начнут появляться новые идеи о том, что в своей жизни можно питоном упростить – или усложнить).

Самодокументация

Однако, вернёмся к help().

Если вы помните, когда я только рассказывал, что такое питонский модуль (я называл этот рассказ примерно "как вы будете складывать функции в файл и тестировать их" – но речь шла о модулях), я приводил пример, что мы создаём файл hello.py, запихиваем туда что-нибудь, например:

   1 def hello(who):
   2     print("Hello, %s!" % who)

И показывал, что если мы после этого модуль загрузим, то питон уже будет считать, что какие-то хелпы для него есть, хотя и пустые:

   1 >>> import hello
   2 >>> help(hello)
   3 ... траляля ...

Давайте теперь научимся делать так, чтобы эти хелпы были не пустыми.

В первом приближении вы это уже умеете делать:

   1 """A module with facilities to greet bypassers.
   2 """
   3 
   4 def hello(who):
   5     print("Hello, %s!" % who)

Сохраняем, запускаем, смотрим1:

   1 >>> import hello
   2 >>> help(hello)

Теперь непустой оказалась строка NAME в хелпах про наш модуль.

В питоне есть стандарт (зовётся PEP-0257), который говорит, что и как писать строки документации. В частности, в нём говорится, что строка должна начинаться с заглавной буквы (если только она начинается не с имени какой-нибудь переменной или функции, в котором случае регистр букв менять – зло), без пробела, быть цельным предложением и завершаться точкой. Ещё там написано, что если хочется больше одной строки документации, то тогда нужно после общего заголовка отступить одну пустую строку и после неё писать основной текст:

   1 """A module to greet the bypassers.
   2 
   3 The only function worth mentioning in this module is hello().
   4 
   5 If you want to override the default greeting message, you have to
   6 reimplement the function yourself.
   7 """
   8 
   9 def hello(who):
  10     print("Hello, %s!" % who)

И снова глядим, что получится:

   1 >>> import hello
   2 >>> help(hello)

Теперь видим, что первая строка оказалась в NAME, дальше идёт (на мой вкус, довольно лишний) раздел FILES без интересных для нас сведений, дальше идёт DESCRIPTION, где и оказывается весь наш текст.

Не описанной осталась сама функция hello. Давайте, поправим и это:

   1 """A module to greet the bypassers.
   2 
   3 The only function worth mentioning in this module is hello().
   4 
   5 If you want to override the default greeting message, you have to
   6 reimplement the function yourself.
   7 """
   8 
   9 def hello(who):
  10     """Print greetings to who.
  11 
  12     Usage:
  13 
  14     >>> hello("world")
  15     Hello, world!
  16     """
  17     print("Hello, %s!" % who)

Если тело функции начинается со строковой константы (т.е. с ", ', """, или '''), то содержимое этой строковой константы и есть хелпы. Хелпы нужно писать с тем же уровнем отступа, что и тело функции – в строке окажутся лишние пробелы, но питон это учитывает и будет их отрезать.

Проверяем:

   1 >>> import hello
   2 >>> help(hello)

Теперь в разделе FUNCTIONS у нас после нашей функции hello оказался текст нашего хелпа.

В конечном итоге, наша программа раздулась фактически в 8 раз (по количеству строк)! Я бы сказал, что это ужасно, если бы всё это раздувание не состояло _(исключительно)_ из добавления пустой строки и текстов хелпов! У хорошей питонской программы хелпов должно быть чуть ли не больше, чем кода. Питон для этого достаточно выразительный язык.

Вся эта система официально в питоне называется "docstring", я это слово на русский вольно перевожу как "самодокументация".

Разумеется, начиная с того момента, как я про эту штуку вам рассказал, я требую, чтобы она была во всех ваших решениях! (Сам я такие штуки пишу добровольно – уж больно они полезные).

Doctest

Вы обратили внимание, что в качестве примера использования функции я привёл тот формат, в котором мы описываем диалог с питоном – буде то на доске, здесь в конспектах, или же в решениях задач первого занятия?

Это неспроста!

Мы можем попросить питон проверять достоверность таких диалогов! А таким образом, мы можем тестировать правильность работы нашей программы!

   1 """A module to greet the bypassers.
   2 
   3 The only function worth mentioning in this module is hello().
   4 
   5 If you want to override the default greeting message, you have to
   6 reimplement the function yourself.
   7 """
   8 import doctest
   9 
  10 def hello(who):
  11     """Print greetings to who.
  12 
  13     Usage:
  14 
  15     >>> hello("world")
  16     Hello, world!
  17     """
  18     print("Hello, %s!" % who)
  19 
  20 if __name__ == "__main__":
  21     doctest.testmod()

Я добавил три строки: import doctest и концовку программы.

Если мы теперь запустим эту программу, она ничего не напечатает.

Внесём в неё ошибку. Пусть, в строке с print будет print("ello, %s" % who). Запустим нашу программу снова, мы увидим:

...$ python hello.py 
**********************************************************************
File "hello.py", line 15, in __main__.hello
Failed example:
    hello("world")
Expected:
    Hello, world!
Got:
    ello, world!
**********************************************************************
1 items had failures:
   1 of   1 in __main__.hello
***Test Failed*** 1 failures.

Знакомо? Так и есть, я использовал именно doctest, чтобы тестировать ваши решения 3 – 5 заданий. Я его использовал чуть-чуть другим боком, а именно, хранил тесты в отдельных текстовых файлах. Вы всё это можете посмотреть у меня в репозитории – и воспользоваться, чтобы приписать doctest к своим функциям.

Объясню смысл шаманской конструкции if __name__ == "__main__" (но не принцип её работы). Когда мы запускаем наш hello.py как питонскую программу (например, говорим: python hello.py), это условие выполняется. Когда мы загружаем наш hello.py как модуль (например, говорим: import hello – в этот момент питон ведь на самом деле тоже просто исполняет программу), это условие не выполняется. Таким образом, мы можем просто запустить программу, чтобы она проверила себя, но когда мы её используем в других случаях, тесты зазря не гоняются и уж тем более зазря не валятся (там уж поздно пить боржом).

Перескажу предыдущий абзац более простыми словами: в рамках моего курса писать эти три строки о-бя-за-тель-но! В тексте задания есть две заготовки – на случай, когда вы пишете модуль (т.е. просто файл – свалку функций), и на случай, когда вы пишете полноценный скрипт (который использует optparse и все дела).

Работа с файлами

Наконец, переходим к главной теме сегодняшнего занятия: как работать с содержимым файлов.

Для начала, маленькое предисловие. В питоне (как и в подавляющем большинстве языков программирования) с файлами можно делать много разных операций: читать его построчно, читать его целиком, писать в него, двигать по нему курсор позиции, с которой мы будем читать или в которую мы будем писать... И довольно много чего ещё, о чём вам бы знать не захотелось (с файлами можно делать много нелицеприятного). Поэтому – и именно поэтому – в питоне каждый файл, с которым мы работаем, обозначается объектом. (А объект обозначается переменной).

Соответственно, прежде, чем с файлом начинать работать, нам нужно создать питонское представление об этом файле – создать объект, который представляет именно конкретный файл. В питоне эта операция называется "открыть файл". Делается это так:

   1 file = open(filename)

Такой файл можно только читать. Питон будет запрещать для него операции записи. Если нам нужно в файл писать, то мы пишем так:

   1 file = open(filename, "w")

Тут сразу предупреждаю: в тот момент, когда питон открывает файл на запись, если такой файл уже существовал, то он будет стёрт! Это свойство даже не столько питона, сколько большинства операционных систем. Его нужно всегда учитывать: нельзя указывать одинаковым входной и выходной файл для программы. В большинстве случаев это приведёт не к тому, чего вы хотели, а к тому, что входные данные у вас будут уничтожены. (Что бывает очень печально, когда в них вложено много труда и вы для них не сделали резервной копии).

Так как такое поведение встречается не только в питоне, а почти повсеместно, то я не предлагаю вам способов обхода этой беды, а наоборот предлагаю вам набить на этом шишку сейчас, пока это относительно безболезненно.

filename – это обычная строка. Примеры:

   1 open("hello.txt") # открыть файл hello.txt в текущей директории
   2 
   3 # UNIX only:
   4 open("../task2/README") # открыть файл README, директорией выше в директории task2
   5 open("_darcs/prefs/author") # открыть файл author в поддиректории prefs в директории _darcs
   6 open("/bin/ls") # открыть файл ls в директории bin, лежащей в корневой директории /
   7 
   8 # Windows only:
   9 open("..\\Task2\\README")
  10 open("_darcs\\prefs\\author")
  11 open("H:\\Task3\\Python\\ls.py")

Так эти строки будут выглядять изнутри питона. В задачах я не даю примеров, где бы вы писали имена файлов внутри питона – имена файлов вам всегда приходят из аргументов командной строки. (В командной строке двойных бэкслешей не будет, там будет H:\Task3\Python – то, как FAR пишет пути, если ему щёлкнуть Ctrl-Enter по файлу). Этот подход я считаю наиболее правильным, покуда он применим. Пусть пользователь сам разбирается, в какую сторону нужно писать слэши! Если вам нужно делать манипуляции над именем файла (дописывать имя файла к директории и т.п.), то повторяю мысль предыдущего занятия: для этого нужно использовать функции из os.path. По крайней мере, если вам кажется полезным использовать вашу программу и в UNIX, и в Windows (и, вдруг, на MacOS).

Теперь, как с файлом работать:

   1 file.read()

читает целиком2 содержимое файла и возвращает в виде одной длинной строки.

   1 file.write("Hello, file!\n")

дописывает одну строку в конец файла. Обратите внимания: в отличие от print, эта функция переноса строки в конец не дописывает, нам приходится вставлять его туда самостоятельно!

Самый полезный на мой вкус способ работы с файлом:

   1 for line in file:
   2     print(line)

если мы подставляем файл туда, где питон ожидал бы список, то питон будет интерпретировать файл как список строк файла – включая переносы в концах строк.

StdIO

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

Расскажу про них с исторической точки зрения. В значительной мере, основные операционные системы, которые используются сейчас (Windows, UNIX, MacOS) являются потомками UNIX. Изначально при создании UNIX была такая идея: для каждой задачи мы пишем маленькую программу, которая решает только эту задачу (но решает её хорошо), и мы делаем простой язык склеивания таких программ вместе (такой язык называется шелл: изначально был Bourne Shell, он же sh, сейчас мы пользуемся bash: Bourne Again Shell). Чтобы программы склеивать, нужно было договориться о стандартном способе обмена данными между ними, которым можно было бы управлять в какой-то мере, не влезая внутрь программы. Придумали рассматривать каждую программу как некий универсальный переработчик, который имеет два разъёма: вход и выход – и придумали способы объединять вход одной программы к выходу другой. Эти вход и выход обозначили "стандартных вход" (stdin) и "стандартный выход" (stdout). И ещё придумали, что если мы у програмы никуда не привязываем выход, то, что из неё высыпается, сыпется на экран. А если не привязан вход, то она присасывается к клавиатуре и ловит всё, что вы набираете. На то время этого было достаточно.

Сейчас сколь-нибудь активно этот подход используется только в UNIX (напр., в Linux). В других ОС появились другие способы взаимодействия программ (в UNIX они тоже просочились, разумеется), да и ситуация, когда программы стали штукой платной, и ни одна большая корпорация не хочет лить воду на чью-либо мельницу, кроме своей, не способствует тому, чтобы идея добиваться решения задачи, склеивая кусочки чужих решений, процветала. Ну да разговор не об этом.

С точки зрения питона.

Стандартный вывод – это то, куда пишет print. Это такой файл. К нему можно дотянуться через sys.stdout.

Стандартный ввод – это то, чем мы пока что не пользовались, ибо нечего! Но сейчас появились задачи, для которых с ним хорошо бы уметь работать. Это тоже такой файл. К нему можно дотянуться, как вы догадались, черзе sys.stdin.

Про стандартный поток ошибок умолчу для простоты. (Его зовут sys.stderr, если кто не догадался).

Полезные приёмы работы со строками

Покажу на примерах.

Пример 1. Мы хотим делать что-нибудь с PDB-файлом (см. материалы задания о том, как устроен формат PDB). Первым делом нам нужно определить тип очередной строки. Он хранится в PDB в первых 6 символах. Я бы делал это так:

   1 for line in file:
   2     type = line[:6].strip().lower()
   3     ...

Функция (точнее, метод) str.strip отбрасывает от строки пробелы по краям, str.lower заменяет заглавные буквы на строчные (чтобы AToM мы распознали так же, как ATOM – ежели вдруг где такая беда случится).

Пример 2. Самый простой способ работать с таблицами. В Excel есть один из форматов, в который можно сохранять таблицы: CSV (Comma Separated Values). Это текстовый файл, в котором столбцы разделяются табуляциями. Вообще-то, для работы с такими файлами (в них бывают некоторые ньюансы) есть встроенный модуль в питоне, который, кажется, так и называется. Но мне всегда удавалось обходиться без него, вот так:

   1 for line in file:
   2     fields = line.split("\t")

str.split(разделитель) возвращает список строк, на которые строка делится разделителем. Нет разделителя в строке – результатом будет список длины 1. Есть два разделителя подряд – будет в списке и пустая строка. Так удобнее.

Обратная к split операция зовётся join, и выглядит странно:

   1 for line in file:
   2     fields = line.split("\t")
   3     line = "\t".join(fields)

Последняя полезная функция, которую я хочу вам упомянуть, зовётся replace – она заменяет все вхождения одного текста на другой текст в строке. Примеры:

   1 >>> "abcbd".replace("b", "x")
   2 "axcxd"
   3 >>> "abcbd".replace("b", "")
   4 "acd"

Типы

Пока что мы в питоне общались с типами:

Проверить, что что-то имеет какой-то тип, можно так:

   1 if isinstance(x, list):
   2     print("x is list")

Не злоупотребляйте проверками! Даже когда такая проверка вам нужна, трижды подумайте, в какую сторону вы хотите жёстко привязать тип (например, если мы ожидаем число или список, то лучше проверять, является ли пришедшая штука списком – списки едины, а чисел бывает несколько разных типов).

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

   1 >>> float(1)
   2 1.0
   3 >>> int(1)
   4 1
   5 >>> int(1.0)
   6 1
   7 >>> int("1")
   8 1
   9 >>> list("hello")
  10 ["h", "e", "l", "l", "o"]

Говорю всё это здесь исключительно для полноты картины и ради двух функций работы со строками: float(s) разбирает из строки число с плавающей точкой, int(s) – целое число.

  1. Я предполагаю, что в этом месте вы откроете рядом IDLE или текстовый редактор с командной строкой питона и будете копировать туда мои примеры и смотреть, что в действительности выдаёт питон (1)

  2. начиная с того места, где сейчас стоит в файле курсор -- сразу после open курсор стоит на начале файла (2)