Kodomo

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

Логические выражения. Главные форматы файлов.

Напоминание

Для понимания материала ОБЯЗАТЕЛЬНО каждый пример пытаться исполнить, понять, почему он работает или не работает, и попробовать в нём что-то поменять, и посмотреть, что получится.

Программирование – это практическая дисциплина, и без успешных попыток что-то сделать руками понимания теории не появляется.

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

Разбор задач

1. Являются ли питонские программы текстовыми файлами? (Ответ: да/нет + доказательство).

Ответ: являются.

Например, мы можем открыть блокнот, написать в нём текст программы, сохранить в файл, и этот файл без изменений запустить питоном.

Или так:

   1 import codecs
   2 file = codecs.open("example.py", "w")
   3 file.write("print 'Hello, world!'")
   4 file.close()

После того, как мы запустим эту программу, у нас возникнет файл example.py, который мы, в свою очередь, снова можем запустить питоном.

2. Напишите программу, которая читает содержимое файла "in.txt" и без изменений пишет его в файл "out.txt"

Наиболее прямолинейное решение (которое работает для только небольших файлов – не больше нескольких гигабайт) состоит из двух частей:

  1. сначала нужно прочитать файл и положить его текст в переменную
  2. затем нужно открыть выходной файл за запись, и положить текст из переменной в него

   1 import codecs
   2 infile = codecs.open("in.txt")
   3 text = infile.read() # получили в переменной text содержимое файла in.txt
   4 outfile = codecs.open("out.txt", "w")
   5 outfile.write(text) # записали содержимое переменной text в файл out.txt
   6 outfile.close() # нужно обязательно не забыть закрыть за собой файл, иначе есть шансы, что он так и останется пустым

3. Напишите программу, которая пишет в файл "words.txt" текст песенки про 99 бутылок

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

Вроде просто, но вылезает много мелких различий:

Для простоты я пишу программу про сильно урезанную версию стишка:

   1 import codecs
   2 file = codecs.open("words.txt", "w")
   3 for n_bottles in reversed(range(100)):
   4     file.write(str(n_bottles) + " of beer on the wall\n")
   5 file.close()

4. Напишите программу, которая считает число строк, букв и слов в файле "words.txt", и пишет его на экран

Это задание ничем не отличается от прошлого раза, кроме того, что текст, в котором нужно посчитать число строк, букв и слов, получается из файла.

Если в прошлый раз было:

   1 text = """
   2 ...
   3 """
   4 
   5 print len(text), "characters"
   6 print len(text.split()), "words"
   7 print len(text.split('\n')), "lines"

то в этот раз вместо первой строки у нас получается:

   1 import codecs
   2 file = codecs.open("words.txt")
   3 text = file.read()

Логические выражения или как питон отличает истину от лжи

Если у нас есть два числа в питоне, мы можем их сравнить:

   1 answer = 1 > 2
   2 print answer

Переводится на русский этот странный кусок кода так: положить в переменную answer ответ на вопрос "правда ли, что 1 больше 2"? Мы-то знаем ответ на этот вопрос. Поэтому нас не удивит, что питон ответит: False (то есть неправда)1. Ответов на операцию сравнения в питоне может быть только два: True или False.

С числами всё просто: можно спрашивать, правда ли, что данное число больше (>), меньше (<), больше или равно (>=), меньше или равно (<=), равно (==) или отличается (!=)2

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

   1 x = 3
   2 y = 5
   3 print "Is x within [2,4) interval?", 2 <= x < 4
   4 
   5 if 2 <= y < 4:
   6     print "y is within [2,4) interval"
   7 else:
   8     print "y is not within [2,4) interval"

Ещё одна приятная новость состоит в том, что так же можно сравнивать и строки:

   1 answer = "hello" < "world"
   2 
   3 if answer:
   4     print "hello", "world"
   5 else:
   6     print "world", "hello"

А можете ли вы угадать ответ тут:

   1 answer = "a" > "aargval!"
   2 print answer

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

Например: что идёт в словаре раньше – ананас или антилопа? По первой букве оба слова идут в главу "А". По второй совпадают: "н". А по третьей букву у нас "а" и "т" – значит ананас раньше.

По этому принципу питон сравнивает между собой строки.

По такому же принципу питон сравнивает между собой и два списка.

Для строк в питоне есть множество полезных методов, проверяющих обладает ли строка некими свойствами:

Примеры:

   1 text = "abc"
   2 if text.isalpha():
   3     print "All", text, "are letters"
   4 if text.isupper():
   5     print "All letters in", text, "are upper case"
   6 if text.isdigit():
   7     print "All characters in", text, "are digits"

Попробуйте дописать этот фрагмент кода используя все известные вам проверки на разные типы символов, и посмотреть, как он реагирует на разные строки.

Для списков и строк есть ещё одна полезная операция сравнения: in проверяет наличие элемента в списке или наличие подстроки в строке, not in – отстутствие:

   1 if "needle" in "large haystack with a small needle":
   2     print "There IS needle in haystack!"
   3 else:
   4     print "There is NO needle in haystack..."
   5 
   6 if "needle" not in ["haystack", "without", "need", "le"]:
   7     print "There is NO needle in haystack..."
   8 else:
   9     print "There IS needle in haystack!"

Теперь мы можем четвёртую домашнюю задачу выполнить с точностью да занудства и посчитать именно число букв в тексте:

   1 n_letters = 0
   2 for character in text:
   3     if character.isalpha():
   4         n_letters = n_letters + 1

Есть ещё одна операция сравнения, с которой вы встретитесь в разных фрагментах кода в сети, которую я на ближайший год советую вам просто не использовать никогда: is и is not. Это НЕ сравнение на равенство. Это проверка на то, что слева и справа стоит один и тот же объект. И беда в том, что 1 is 1 имеет право быть верным не всегда – а это значит, что оно будет верно пока вы пишете и отлаживаете программу, но как только вы её захотите показать большому начальнику чтобы похвастаться, окажется, что оно не работает! Так что простое правило: в питоне есть сравнение is, мы его не используем. Даже не привожу примеров, чтобы у вас не было примеров плохого.

Кроме того, получившиеся логические ответы в питоне можно комбинировать:

   1 x = "hello"
   2 y = "HELLO"
   3 z = "HEL123"
   4 answer = (not x.isdigit()) and x.isupper()
   5 answer = (not y.isdigit()) and y.isupper()
   6 answer = (not z.isdigit()) and z.isupper()

Логическая операция and (И) проверяет, являются ли выполненными оба условия одновременно, or – хотя бы одно, not – что условие не выполнено.

Experts only

Кроме перечисленного, питон может воспринимать почти любой объект как логическое значение:

  • 0 как False, все остальные числа как True
  • [] как False, все остальные списки как True
  • ''как False, все остальные строки как True

и т.п. для остальных типов данных.

Это позволяет в некоторых случаях писать гораздо более понятную и надёжную проверку на пустоту.

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

Два слова про переносы строк

Печальная новость: исторически сложилось, что перенос строки в текстовых файлах может обозначаться двумя разными способами:

Ещё одна печальная новость состоит в том, что python, по идее, должен бы сам их различать и оставлять те, которые нужно, но почему-то в некоторых случаях он этого не делает.

Это очень неудобно, но есть и хорошие новости:

Отсюда мы можем выработать такую стратегию поведения:

count

У строк и у списков есть метод count:

   1 text = "a very long text with many long words"
   2 print "'long':", text.count("long"), "times"
   3 print "'y ':", text.count("y "), "times"

У строк метод count считает количество раз, которое подстрока встретилась в строке.

   1 words = text.split()
   2 list = words + [1, 2, 11, 22, 1]
   3 print list
   4 print "'long':", list.count("long"), "times"
   5 print "one:", list.count(1), "times"
   6 print "'y ':", list.count("y "), "times"

У списков метод count считает количество раз, которое данный элемент встретился в списке.

Это не очень полезные методы, так как обычно для статистики требуется считать несколько более сложные вещи, и есть более удачные способы это делать. Мы дойдём до этих способов несколько позже, а пока что поупражняемся с более простыми решениями.

Какие бывают текстовые форматы файлов

Обычно форматы файлов называют так же, как и соответствующее расширение файла. (Расширение файла – часть имени, идущая после точки). Важно понимать, что формат файла – это то, как устроены внутренности, а расширение файла – это просто имя файла3. "Если на клетке с тигром написано баран – не верь глазам своим!"

TXT

Наверное, самый полезный для нас формат – TXT.

Это очень сложный формат файлов, но мы его уже знаем: это текстовый файл, в котором хранится только читаемый и понятный человеку текст.

Полезно знать, что этот же формат называется "plain text", или "обычный текст", и многие программы, работающие с размеченным текстом, например, Word, умеют сохранять текст в TXT.

CSV

Второй из самых полезных для нас форматов – CSV. Он предназначен для хранения табличных данных. Название расшифровывается "comman separated values", то есть "значения, разделённые запятой".

Формат устроен так: каждая строка таблицы отображается как строка текстового файла. Строка делится на ячейки по разделителям – типично, запятым. Например:

Name,age
Johnson,42
Иванов,33

Обозначает таблицу:

Name age
Johnson 42
Иванов 33

Формально название формата требует, чтобы в качестве разделителя в нём использовались запятые, но на практике чаще всего используют запятые, точки с запятой или табуляции (символ табуляции в питонских строках обозначается "\t"). Пожалуй, что табуляции используются даже чаще остальных, в честь чего для них иногда используют название TSV – tab-separated values.

Формат CSV прекрасем тем, что все программы для работы с таблицами (включая MS Excel, Google Spreadsheets и Libreoffice) умеют сохранять таблицы в этом формате. Очевидно, в этом формате невозможно сохранить всяческие навороты вроде объединения строк или столбцов, раскраски, шрифтов и пр, поэтому если такие явления в вашей таблице встречаются, то вам либо сообщат об опасности сохранения в этом формате, либо (встречается реже, но больше удивляет) вам могут не предложить CSV в качестве возможных форматов для сохранения.

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

   1 import codecs
   2 separator = "," # а если в файле другой разделитель, можем поменять
   3 file = codecs.open("example.csv") # предполагаем, что codecs.open правильно угадает кодировку, если нет -- пропишем
   4 
   5 rows = []
   6 for line in file:
   7     line_cleared = line.rstrip() # убираем лишние \r\n
   8     cells = line_cleared.split(separator)
   9     rows.append(cells) # добавляем список ячеек данной строки в качестве одного элемента в список строк
  10 
  11 print rows

Одно маленькое замечание, которое изредка может стоить вам большого количества нервов: когда мы идём по строкам файла циклом for, то в переменную line на каждой итерации попадает вся строка целиком включая символы переноса строки. Поэтому я рекомендую взять себе в бездумную привычку первым делом при таком работе вызывать rstrip(), чтобы их отрезать – что и показано в примере.

Теперь мы можем, например, получить содержимое 3-й ячейки второй строки:

   1 row = rows[1]
   2 cell = row[2]
   3 print cell

Мы можем делать с таблицами, прочитанными таким образом всё, что мы умели делать со списками: считать длину, искать, есть ли в них интересующие нас данные, ходить по ним циклом, редактировать... Например:

   1 print "Number of rows:", len(rows)
   2 for row in rows:
   3     print "Number of columns:", len(row)

XML

Последний из самых важных для вас форматов файлов называется XML.

Работать с ним с помощью питона мы научимся нескоро – где-то в середине второго модуля – но иметь о нём представление нам необходимо для того, чтобы смотреть на него глазами и уметь из него находить нужные нам вещи.

Язык XML используется по существу для двух вещей:

Пример сложной структуры – это, например, НКРЯ:

Корпус – это метаданные + тексты. Каждый текст – это метаданные + предложения. Предложение состоит из слов, про каждое из которых указан морфологический разбор.

То есть основное отношение, которое описывается в формате XML: общее состоит из таких-то частей. Это отношение записывается в xml так:

   1 <whole>
   2 part1
   3 part2
   4 part3
   5 </whole>

Разметка <whole> и </whole> называется открывающий тэг и закрывающий тэг соответственно. Слово, описывающее тэг, обычно имеет какую-то смысловую нагрузку.

У тэгов могут дополнительно указываться атрибуты в виде пар ключ="значение" внутри открывающего тэга:

   1 <whole date="today" author="me">
   2 part1
   3 part2
   4 </whole>

Соответствено, чтобы получить эффект "корпус состоит из текстов, текст состоит из предложений", пары тэгов можно друг в друга вкладывать.

Пример из syntagrus:

   1 <?xml version="1.0" encoding="utf-8" standalone="no"?>
   2 <text DB_PATH="Y:\\Corpus\\Ready_Corpus\\2011\\Alpinizm.tgt" PARAMS="ETAPOPTION=RE; DOMAINS=COMMON-DOMAIN; LEVEL=$TRUE; WEAKPRED=$FALSE; PARAFR=$FALSE; STAT=N; SENTFEATS=STDSENT; SYNTMODE=STDSYNT; ALTTR=$TRUE; MWORDFORM=$FALSE; CORDESCRMODE=CORDESCROLDMODE; SYNTAXONLY=$FALSE; SOFTSENTBR=$FALSE; SOFTGEOMFILTR=$TRUE; EXPERMODE=EXPER_OFF; DISAMBMODE=DISAMB_OFF; MAXALTNUM=NORMALNUM; USECORPLF=$FALSE; SPEECHMODE=SPEECHMODENO; " ver="1.1">
   3 <inf>
   4 <annot>ЛМ</annot>
   5 <editor>TF</editor>
   6 <source>Википедия, 22 мая 2011</source>
   7 <title>Альпинизм</title>
   8 </inf>
   9 <body>
  10 <S ID="1">
  11 <W DOM="2" FEAT="S ЕД МУЖ ИМ НЕОД" ID="1" LEMMA="АЛЬПИНИЗМ" LINK="предик">Альпинизм</W> - 
  12 <W DOM="_root" FEAT="S ЕД МУЖ ИМ НЕОД" ID="2" LEMMA="ВИД">вид</W> 
  13 <W DOM="2" FEAT="S ЕД МУЖ РОД НЕОД" ID="3" LEMMA="СПОРТ" LINK="квазиагент">спорта</W> 
  14 <W DOM="3" FEAT="CONJ" ID="4" LEMMA="И" LINK="сочин">и</W> 
  15 <W DOM="6" FEAT="A ЕД МУЖ РОД" ID="5" LEMMA="АКТИВНЫЙ" LINK="опред">активного</W> 
  16 <W DOM="4" FEAT="S ЕД МУЖ РОД НЕОД" ID="6" LEMMA="ОТДЫХ" LINK="соч-союзн">отдыха</W>, 
  17 <W DOM="9" FEAT="S ЕД ЖЕН ТВОР НЕОД" ID="7" LEMMA="ЦЕЛЬ" LINK="1-компл">целью</W> 
  18 <W DOM="7" FEAT="S ЕД МУЖ РОД" ID="8" LEMMA="КОТОРЫЙ" LINK="квазиагент">которого</W> 
  19 <W DOM="2" FEAT="V НЕСОВ ИЗЪЯВ НЕПРОШ ЕД 3-Л" ID="9" LEMMA="ЯВЛЯТЬСЯ" LINK="релят">является</W> 
  20 <W DOM="9" FEAT="S ЕД СРЕД ИМ НЕОД" ID="10" LEMMA="ВОСХОЖДЕНИЕ" LINK="предик">восхождение</W> 
  21 <W DOM="10" FEAT="PR" ID="11" LEMMA="НА" LINK="2-компл">на</W> 
  22 <W DOM="11" FEAT="S МН ЖЕН ВИН НЕОД" ID="12" LEMMA="ВЕРШИНА" LINK="предл">вершины</W> 
  23 <W DOM="12" FEAT="S МН ЖЕН РОД НЕОД" ID="13" LEMMA="ГОРА" LINK="квазиагент">гор</W>. 
  24 </S>
  25 <S ID="2">
  26 <W DOM="2" FEAT="A ЕД ЖЕН ИМ" ID="1" LEMMA="СПОРТИВНЫЙ" LINK="опред">Спортивная</W> 
  27 <W DOM="4" FEAT="S ЕД ЖЕН ИМ НЕОД" ID="2" LEMMA="СУЩНОСТЬ" LINK="предик">сущность</W> 
  28 <W DOM="2" FEAT="S ЕД МУЖ РОД НЕОД" ID="3" LEMMA="АЛЬПИНИЗМ" LINK="квазиагент">альпинизма</W> 
  29 <W DOM="_root" FEAT="V НЕСОВ ИЗЪЯВ НЕПРОШ ЕД 3-Л" ID="4" LEMMA="СОСТОЯТЬ">состоит</W> 
  30 <W DOM="4" FEAT="PR" ID="5" LEMMA="В" LINK="1-компл">в</W> 
  31 <W DOM="5" FEAT="S ЕД СРЕД ПР НЕОД" ID="6" LEMMA="ПРЕОДОЛЕНИЕ" LINK="предл">преодолении</W> 
  32 <W DOM="6" FEAT="S МН СРЕД РОД НЕОД" ID="7" LEMMA="ПРЕПЯТСТВИЕ" LINK="1-компл">препятствий</W>, 
  33 <W DOM="7" FEAT="V НЕСОВ СТРАД ПРИЧ НЕПРОШ МН РОД" ID="8" LEMMA="СОЗДАВАТЬ" LINK="опред">создаваемых</W> 
  34 <W DOM="8" FEAT="S ЕД ЖЕН ТВОР НЕОД" ID="9" LEMMA="ПРИРОДА" LINK="агент">природой</W> 
  35 (<W DOM="7" FEAT="S ЕД ЖЕН ИМ НЕОД" ID="10" LEMMA="ВЫСОТА" LINK="примыкат">высота</W>, 
  36 <W DOM="10" FEAT="S ЕД МУЖ ИМ НЕОД" ID="11" LEMMA="РЕЛЬЕФ" LINK="сочин">рельеф</W>, 
  37 <W DOM="11" FEAT="S ЕД ЖЕН ИМ НЕОД" ID="12" LEMMA="ПОГОДА" LINK="сочин">погода</W>), 
  38 <W DOM="7" FEAT="PR" ID="13" LEMMA="НА" LINK="атриб">на</W> 
  39 <W DOM="13" FEAT="S ЕД МУЖ ПР НЕОД" ID="14" LEMMA="ПУТЬ" LINK="предл">пути</W> 
  40 <W DOM="14" FEAT="PR" ID="15" LEMMA="К" LINK="3-компл">к</W> 
  41 <W DOM="15" FEAT="S ЕД ЖЕН ДАТ НЕОД" ID="16" LEMMA="ВЕРШИНА" LINK="предл">вершине</W>. 
  42 </S>
  43 ...
  44 </body>
  45 </text>

Здесь мы видим ещё один тип тэгов: тэги <?...?> отвечают за настройку того, как интерпретировать этот документ, и нужны только компьютеру. Мы их смело можем не читать.

Стандарт XML не говорит нам трактовку конкретных тэгов, об этом нужно договариваться с авторами документа. (Впрочем, стандарт XML предполагает некоторые способы формализовать эту договорённость).

В нашем примере:

   1 <W DOM="4" FEAT="S ЕД МУЖ РОД НЕОД" ID="6" LEMMA="ОТДЫХ" LINK="соч-союзн">отдыха</W>, 

Тэг <w> обозначает разобранный токен. Про него в качестве атрибутов указывается:

Литература

  1. Собственно, алгебра логики -- это область математики, которая занимается выражениями, в которых участвуют значения "истина" и "ложь". И это большая область математики с кучей интересных выводов и полезных применений: начиная от ловли политиков на вранье и заканчивая расположением элементов на микросхеме. (1)

  2. Для проверки на неравенство есть синтаксис <>, которые те из вас, кто привык к паскалю, с удовольствием бы стали использовать. Но я это очень не рекоммендую делать, так как в третьем питоне такое сравнение уже не работает. (2)

  3. Увы, Windows этого не понимает, и принимает решение о том, как файл открывать исходя из того, как файл назван (3)