Kodomo

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

Учебная страница курса биоинформатики,
год поступления 2011

Репозитории; Файлы и Строки

Репозитории

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

Существует множество разных программ для организации репозиториев (как правило, несовместимых друг с другом, хотя зачастую существуют ещё третьи программы, чтобы как-то их подружить): CVS, SVN, git, mercurial, ...

Мы будем пользоваться mercurial, притом частично интерфейсом командной строки, частично tortoise (это то, что делает инсталлятор по умолчанию).

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

Основные понятия в меркуриале:

репозиторий

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

список контролируемых файлов
меркуриал разрешает хранить в директории репозитория файлы, история изменения которых не отслеживается. Например, не имеет смысла отслеживать историю изменения файлов, которые автоматически генерируются из наших программ за несколько секунд; ещё мы можем решить, что очень большие бинарные файлы не хочется хранить в репозитории из экономии места (для бинарных файлов у меркуриала могут быть менее удачные алгоритмы хранения версий, и это может привести к тому, что меркуриал на каждую версию будет хранить просто копию этого файла). По умолчанию меркуриал не принимает сам решения, за какими файлами он следит, то есть не следит ни за чем.
история
список версий файлов. История содержит версии, общие для всего проекта, а не для отдельного файла. Зато из каждой версии можно узнать, что было в конкретном файле в этой версии.
версия
сохранённое содержимое файлов в какой-то момент.

Чтобы начать работать с репозиторием, нужно зайти в (уже созданную) директорию (в которой могут лежать уже созданные файлы) и сказать hg init. Вообще, все команды меркуриалу нужно говорить, находясь в репозитории (например, через FAR).

Далее нам нужно сказать меркуриалу, что мы хотим следить за версиями каких-то файлов. Добавляются файлы командой hg add (добавляет все файлы в директории) или hg add файл (добавляет один или несколько файлов). Если какой-то файл нам надоел, и мы больше не хотим следить за его историей, мы можем его удалить: hg rm файл. При этом прежняя история этого файла сохранится, но меркуриал не будет больше считать его частью репозитория.

Чтобы сказать меркуриалу, что нынешнее состояние файлов хорошее – например, вот ровно сейчас программа работает, не дышать, – нужно сказать: hg commit. Меркуриал при этом откроет notepad, в котором предложит вам описать изменения. По умолчанию меркуриал сохраняет состояние всех файлов, но мы можем сохранять их состояние по одному: hg commit файл.

Теперь мы можем посмотреть на историю изменений в репозитории: hg log. (Tortoisehg предоставляет более красивое представление, отыщите его в контекстной менюшке в "моём компьютере")

Ещё мы можем сравнить содержимое двух разных версий: hg diff 1 2. Или узнать, что мы успели сделать с момента последнего коммита: hg diff.

Чтобы разобраться, какие файлы сохранены в репозитории, какие нет, есть команда: hg status.

Мы можем вернуться к одной из прежних версий: hg update -r 1 заменяет в рабочей директории все файлы на их содержимое из версии номер 1.

Если вы дома пишете программу на своём компьютере, потом хотите перенести её в класс на флэшке (? зачем на флэшке – этого я понять не могу, но потребность весьма частая), вам полезна команда hg push путь-к-директории, которая содержимое вашего репозитория отправляет в репозиторий, который (уже) лежит в путь-к-директории. (Например, потому, что вы его туда скопировали вместе со всем проектом). И обратная для неё команда hg pull путь-к-директории вытягивает из репозитория путь-к-директории изменения в текущий репозиторий. Для тех, кто не любит возиться с флэшками, меркуриал умеет пользоваться SSH, для этого в качестве пути к репозиторию нужно дать ему что-то в духе: ssh://логин@kodomo.fbb.msu.ru/Term4/.../.../... Единственное, после любой из этих команд нужно сказать hg update в том репозитории, где что-то менялось в истории. (Почему – это вопрос того, как использовать репозиторий для работы в команде – это тема, которую мы оставляем за скобками).

Самая главная команда меркуриала: hg help.

Не бойтесь пробовать! Меркуриал достаточно навязчиво предупредит вас, если вы попытаетесь сделать какое-нибудь необратимое действие.

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

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

В питоне (как и во многих языках программирования), основная идея работы с файлом такая:

Мы будем рассматривать, как работать в питоне с текстовыми файлами (например, файлы .txt, исходники питонских программ, html, fasta, genbank). Работа с бинарными файлами (например, вордовские файлы, или картинки) принципиально ничем не отличается от текстовых, однако требует гораздо более сложного знания устройства формата файла. Как правило, для работы с бинарными файлами проще всего найти готовую библиотку, которая будет представлять его содержимое удобным для нас образом в питоне. Таких библиотек для питона создано множество для разных форматов файлов, однако работа с ними стоит за рамками основного курса (многие из них сопровождаются хорошей документацией с примерами).

Открытие файла.

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

   1 file = open('file.txt')

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

   1 file = open('file.txt', "w")

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

Что делать, если мы хотим дописать в файл? Для этого существуют еще один режим работы файла 'a' (от 'append'):

   1 file = open('file.txt', "a") 

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

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

Так эти строки будут выглядять изнутри питона. Если имена файлов приходят из аргументов командной строки, то добавлять лишние бэкслеши не надо. (В командной строке будет H:\Task3\Python – то, как FAR пишет пути, если ему щёлкнуть Ctrl-Enter по файлу).

Дабы не писать лишние бэкслеши, в питоне есть ещё один тип строк, о котором мы вам не рассказывали: если перед строкой стоит буква r, то тогда бэкслеши в теле строки ничего не значат для самого питона:

   1 f = open(r"..\Task2\README")

Чтение по строкам

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

   1 for line in file:
   2         print line

Если мы распечатаем каждую строку таким образом, то в итоге на экране мы увидим все строки через пробел (пустую строку). Это произойдет из-за того, что в файле в конце каждой строки стоит символ '\n', а print автоматически прибавляет этот символ к концу печатаемого объекта. О решении этой проблемы поговорим чуть позже (см. Работа со строками и всякие полезные вещи при работе с файлами).

Чтение по байтам.

   1 bytes=file.read(1)

метод .read(1) возвращает строку bytes, в которой будет находиться 1 байт, прочитанный из файла.

Чтение целиком

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

   1 contents=file.read()

Запись в файл.

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

дописывает одну строку в конец файла (если мы не применяем усилий, курсор в файле, в который мы пишем, всегда оказывается в конце).

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

Как закрывать файл

Чтобы закрыть файл, нужно на объекте файла вызвать метод close:

   1 file = open('hello.txt', 'w')
   2 file.write("Hello, world!\n")
   3 file.close()

Однако, в питоне есть более удобный способ, чтобы питон сам закрыл за нами файл. Для этого в питоне есть конструкция with: питон закроет файл в тот момент, когда мы из неё выходим (то есть возвращаем старый отступ):

   1 with open('hello.txt', 'w') as file:
   2     file.write("Hello, world!\n")

Стандартные потоки ввода-вывода

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

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

В питоне каждый из этих потоков выглядит как обычный файл. Имена этим файлам: sys.stdin, sys.stdout, sys.stderr. (Разумеется, чтобы ими пользоваться, нужно сначала проимпортировать модуль sys).

Работа со строками.

Итак, вернемся к чтению по строкам.

   1 for line in file:
   2         print line

Как уже было сказано, у нас возникает проблема с удвоением '\n'. Избавиться от этого лишнего пробельного символа можно с помощью метода strip().

.strip(): обрезание символов с концов строки

strip отрезает с начала и конца строки те символы, которые указаны в его аргументах (т.е. в скобках). Пустые скобки означают пробельные символы

   1 >>> a=" \t 1 2 "
   2 >>> a.strip()
   3 '1 2'

Важно! Строки нельзя изменить, поэтому все методы редактирования строк просто возвращают новую отредактированную строку, а старую не меняют.

То есть:

   1 >>> a=" \t 1 2 "
   2 >>> a.strip()
   3 '1 2'
   4 >>> a
   5 " \t 1 2 "

Если мы напишем:

   1 >>> a=a.strip()
   2 >>> a
   3 '1 2'

то a будет указывать теперь на новую строку (изменит адрес), однако исходная строка останется прежней.

Еще примеры со strip:

   1 >>> '123abc231'.strip('12')
   2 '3abc23'

Как уже было сказано, strip обрезает с начала и конца строки все символы, которые указаны в его аргументах. Он работает только посимвольно! Поэтому с его помощью нельзя вырезать из строки '123abc321' число '12', чтобы осталось '3abc3'.

Вернемся к нашей проблеме. Метод strip() можно использовать, однако он удалит еще пробелы и в начале, что уже не есть хорошо. Чтобы избежать этого, надо указать в аргументах символ '\n', который обычно присутствует только в конце. Для полной уверенности можно использовать разновидность метода strip – rstrip, который вырезает символы с правого конца (аналогично, существует метод lstrip для левого). Тогда:

   1 for line in file:
   2     print line.rstrip('\n')

.split(): разбиение строки по заданным разделителям

Разбить строку на элементы списка можно с помощью метода .split()

   1 >>> " \t 1 2 ".split()
   2 ['1', '2']  

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

   1 >>> '1,_2,_3,4,_5'.split(',_') # в аргументе split указываем разделитель на элементы
   2 ['1', '2', '3,4', '5']
   3 >>> a='1,_2,_3,4,_5'.split(',')
   4 ['1', '_2', '_3', '4', '_5']

split("строка") разбивает по строке, а не по символам, из которых она состоит! (Не путайте со strip)

split() при разбиении выкидывает пробелы в начале и в конце строки (смотри пример выше), а split(" ") нет!

   1 >>> " \t 1 2 ".split(' ')
   2 ['', '\t', '1', '2', '']

Если в строке встретились два разделителя подряд, то в выходном списке окажется пустой элемент.

split можно попросить искать в строке разделитель не более, чем n раз: s.split(separator, n). Нетрудно догадаться, что такой вызов вернёт список длины не более, чем n+1. Например:

   1 >>> ',,1,2,,3'.split(',', 2)
   2 ['', '', '1,2,,3']
   3 >>> '1'.split(',', 2)
   4 ['1']

Поэтому существует и двойственный метод rsplit, который делает то же самое, что и split, но если ему дают второй аргумент, то он ищет не более, чем n последних разделителей.

.join(): операция, обратная к split:

   1 >>> line = 'a\t b \tc\t'
   2 >>> fields = line.split("\t")
   3 >>> fields
   4 ['a', ' b ', 'c', '']
   5 >>> "\t".join(fields)
   6 'a\t b \tc\t'

Несколько странным может показаться, что join нужно вызывать как метод строки, которой мы объединяем элементы списка, а не как метод списка, который мы склеиваем. Этому есть псевдоразумные заумные объяснения, с которыми некоторые соглашаются, некоторые нет. Оставим это на совести Гвидо, и просто запомним, что с join нужно не путать, как его вызывать.

.replace()

Замена всех вхождений подстроки в строке на другую подстроку. Особых комментариев не требует.

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

.lower(), .upper(): приведение строки к одному регистру

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

   1 >>> 'Ku-Ku'.lower()
   2 'ku-ku'
   3 >>> 'Ku-Ku'.upper()
   4 'KU-KU'
   5 >>> 'ku-KU'.capitalize()
   6 'Ku-ku'

.find(): поиск подстроки в строке

Возвращает индекс первого вхождения подстроки в строку:

   1 >>> 'I like limes'.find('li')
   2 2

Можно также искать только в заданной подстроке исходной строки:

   1 >>> 'I like limes'.find('li', 4) # ищем начиная с буквы с номером 4 (то есть пятой буквы)
   2 7
   3 >>> 'I like limes'[4:].find('li') # ищем начиная с буквы с номером 4 (то есть пятой буквы) -- но если мы делаем это своими руками, нужно не забыть прибавить обратно к номеру позиции находки откушенную нами часть строки
   4 3

Если ничего не нашлось, возвращается -1:

   1 >>> 'I like limes'.find('li', 4, 7)
   2 -1

startswith

Возвращает True, если строка начинается с данной подстроки:

   1 >>> 'I like limes'.startswith('I like')
   2 True

Так же, как и с find(), можно ограничивать подстроку в исходной строке:

   1 >>> 'I like limes'.startswith('like', 2)
   2 True

Слайсы: string[start:end]

Со строками в питоне можно работать так же, как и со списками:

   1 >>> "abcbd"[1:-1]
   2 'bcb'
   3 >>> len("abcbd")
   4 5
   5 >>> "abcbd"[1::2]
   6 "bb"
   7 >>> "abcbd"[1]
   8 "b"

Только вот элементами этого списка являются снова строки (хотя некоторым людям казалось бы, что строка – это список всё-таки из букв). Если задуматься, это довольно странная идея (например, x[0][0][0][0] говорит нам либо о том, что x – это очень сложная конструкция из вложенных друг в друга списков, либо строка, из которой мы просто очень извращённым способом берём первую букву), однако, она довольно популярна, и используется во многих относительно молодых языках программирования (например, javascript).

Параметры командной строки

Когда мы вызываем программу через командную строку, мы можем также указывать ее параметры (если они есть).

Например, вспомним команду ls. Ее можно вызвать с различными параметрами.

Все три записи выше равнозначны между собой.

Если параметр написан через знак дефиса, то он называется именованным, иначе – позиционным.

В переменной sys.argv содержится список строк – все то, что было написано в командной строке (разделенное по пробелам).

Например, если мы напишем маленькую программку test_argv.py:

   1 import sys
   2 print sys.argv

И запустим ее с какими-нибудь параметрами, то получится так:

$ python test_argv.py file.txt -r -w 5
['test_argv.py', 'file.txt', '-r', '-w', '5']