Kodomo

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

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

Файлы, работа со строками

Файлы

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

Объект типа файл возвращает метод open(). Обязательный параметр - путь к файлу, который может быть абсолютным или относительным (заданным из корневой директории или из текущей, соответственно).

   1 >>> f = open('workfile', 'w')

Дополнительный параметр – режим открытия файла, по умолчанию файл открывается на чтение в текстовом режиме. Возможные значения параметра:

'r'

открытие на чтение

'w'

открытие на запись, создается файл, если файл с таким названием существует, он замещается новым

'x'

открытие на запись, создается файл, если файл с таким названием существует, создается исключение

'a'

открытие на дозапись

't'

открытие в текстовом режиме

'b'

открытие в двоичном режиме

w+

открытие на чтение и запись, если файл с таким названием существует, он переписывается

и др.

Параметры можно комбинировать, так для открытия на чтение бинарного файла необходимо указать значения параметра 'rb’. Для закрытия файла используется метод close(). Все файлы обязательно закрывать, поскольку иначе могут возникать ошибки при повторных попытках доступа к ним.

   1 >>> f.close()

Файл закроется автоматически при использовании конструкции with open .. as.

   1 >>> with open('workfile') as f:
   2         f.write("Hello word!")

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

   1 >>>contents=f.read()

Или читать по строкам:

   1 >>>for line in f:
   2 print line

Для записи в файл строки можно использовать метод write().

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

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

   1 >>> for i in range(3):
   2     print 'Spring'
   3 Spring
   4 Spring
   5 Spring
   6 >>> import sys
   7 >>> for i in range(3):
   8   sys.stdout.write('Spring')
   9 SpringSpringSpring

Записать последовательность строчек можно с помощью метода writelines.

   1 >>>file.writelines([‘one’, ‘two’, ‘three’])

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

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

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

   1 >>>string=”This is string
   2 >>>string=”-’This is string!’”
   3 >>>string=’’’The spring is nice.
   4 The sun is shining bright. ’’’

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

   1 >>> string = “The sun is\
   2 shining bright.”

Строковые литералы склеиваются. Обратный слэш также используется для специальных символов в python - \t, \n, …. При необходимости этот слэш может экранироваться еще одним обратным слэшом

   1 >>>string="\\string"

или применением метода r:

   1 >>>string=r”\string

Подобно спискам для строк можно делать срезы их символов.

   1 >>> string="qwerty"
   2 >>> string[0:2]
   3 'qw'
   4 >>> string[:2]
   5 'qw'
   6 >>> string[0:4:2]
   7 'qe'

Строки можно склеивать с помощью оператора +, сравнивать с помощью операторов сравнения (==, !=, <=, >=, < , > ), мультиплицировать с помощью оператора *.

   1 >>> string1="I love"
   2 >>> string2="spring"
   3 >>> string1  + " " + string2
   4 'I love spring'
   5 
   6 if string2 == 'spring':
   7     print("Yes!")
   8 else:
   9     print("No!")
  10 
  11 >>> string2 * 3
  12 'springspringspring'

Нестроковые типы переменных могут быть переведены в строку методов str:

   1 >>>a=69
   2 >>> type(a)
   3 <type 'int'>
   4 b=str(a)
   5 >>> type(b)
   6 <type 'str'>

Вхождение подстроки в строку можно проверять с помощью операторов in и not in.

   1 >>> "li" in "likes"
   2 True
   3 >>> "li" not in "likes"
   4 False

Методы для работы со строками:

Методы rstrip, lstrip, strip –удаление всех вхождений элемента справа, слева или с обоих концов строки. По умолчанию удаляет пробельные символы.

   1 >>> "    I love spring    ".strip()
   2 'I love spring'
   3 >>> "    I love spring    ".rstrip()
   4 '    I love spring'
   5 >>> "    I love spring    ".lstrip()
   6 'I love spring    '
   7 >>> "I love spring????!!!???".strip("!?")
   8 'I love spring'

Метод split - разбивает строку на подстроки по указанному разделителю. По умолчанию – по пробельным символам.

   1 >>> "    I love spring    ".split("love")
   2 ['    I ', ' spring    ']
   3 >>> "    I love spring    ".split()
   4 ['I', 'love', 'spring']

Методы partition и rpartition возвращают кортеж из трех элементов. Подстроку до разделителя, разделитель и строку после разделителя. Соответственно слева и справа по строке.

   1 >>> "Apples, oranges, bananas".partition(",")
   2 ('Apples', ',', ' oranges, bananas')
   3 >>> "Apples, oranges, bananas".rpartition(",")
   4 ('Apples, oranges', ',', ' bananas')

Метод join позволяет объединять строки из списка или строчные символы в новую строку с указанным разделителем.

   1 >>> ", ".join(['Apples', 'oranges', 'bananas'])
   2 'Apples, oranges, bananas'
   3 >>> ", ".join([2, 3, 4])
   4 Traceback (most recent call last):
   5   File "<pyshell#71>", line 1, in <module>
   6     ", ".join([2, 3, 4])
   7 TypeError: sequence item 0: expected string, int found
   8 
   9 >>> ", ".join("apple")
  10 'a, p, p, l, e'

Метод replace заменяет в строке одну подстроку на другую. В качестве параметра можно передать максимальное количество производимых замен.

   1 >>> "I love spring".replace("spring", "bananas")
   2 'I love bananas'
   3 >>> "I love spring, spring, spring".replace("spring", "bananas", 2)
   4 'I love bananas, bananas, spring'

Метод translate позволяет сделать множественную замену. В примере каждый символ “?” и “,” заменяется на “!”

   1 >>> from string import maketrans
   2 >>> table=maketrans('?,', '!!')
   3 >>> "I love spring, spring, spring?".translate(table)
   4 'I love spring! spring! spring!'

Методы find и rfind возвращают соответственно первый и последний индексы подстроки в строке.

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

Методы startswith и endswith удобны для сравнения префикса и суффикса строки со строкой.

   1 >>> "likes".startswith("li")
   2 True
   3 >>> "likes".endswith(("m", "s"))
   4 True

Пример 1:

   1 #input_file format:
   2 
   3 chr15:20012542;20012741
   4 chr15:20012564;20012763
   5 chr15:20018877;20019076
   6 chr15:20019782;20019981
   7 chr14:20019909;20020108
   8 chr14:20019909;20020108
   9 chr14:20013241;20020415
  10 chr14:20012344;20027989
  11 
  12 
  13 #output_file format
  14 15      20012542        20012741
  15 15      20012564        20012763
  16 15      20018877        20019076
  17 15      20019782        20019981
  18 14      20019909        20020108
  19 14      20013241        20020415
  20 14      20012344        20027989

Программа:

   1 file = open("data_file.txt", "r")
   2 output_file = open("output_file.bed", "w")
   3 
   4 for line in file:
   5     chrom, pos = line.strip().split(":")
   6     start, end = pos.split(";")
   7     chrom = chrom.replace("chr", "")
   8 
   9     new_string = "\t".join([chrom, start, end])
  10     output_file.write(new_string + "\n")
  11 
  12 file.close()
  13 output_file.close()

Пример 2:

   1 #input_file format
   2 >fasta_id
   3 ATTTGGCGTCAATCGATCGATGCAAAAAAAAAAATGTCGTACGATCGATCGATCGTACGTACGTAGCTAGCTAGTGTTCGGCGATCATCAGC
   4 ATTTGGCGTCAATCGATCTCGATCGATCGTACGTACGTAGCTAGCTAGTGTTCGGCGATCATCAGCGTACGTACGTAGCTAGCTAGTGTTCG
   5 ACGATCGATCGATCGTACGTACGTAGCTAGCTAGTGTTCGGCGATCATCAGATTTGGCGTCAATCGATCGATGCAAAAAAAAAAATGTCGTC
   6 
   7 
   8 #output_file format
   9 >fasta_id
  10 UAAACCGCAGUUAGCUAGCUACGUUUUUUUUUUUACAGCAUGCUAGCUAGCUAGCAUGCAUGCAUCGAUCGAUCACAAGCCGCUAGUAGUCG
  11 UAAACCGCAGUUAGCUAGAGCUAGCUAGCAUGCAUGCAUCGAUCGAUCACAAGCCGCUAGUAGUCGCAUGCAUGCAUCGAUCGAUCACAAGC
  12 UGCUAGCUAGCUAGCAUGCAUGCAUCGAUCGAUCACAAGCCGCUAGUAGUCUAAACCGCAGUUAGCUAGCUACGUUUUUUUUUUUACAGCAG

Программа:

   1 from string import maketrans
   2 
   3 table = maketrans('ATGC', 'UACG')
   4 new_strings = []
   5 with open("data_fasta_file.txt") as file:
   6     with open("output_file.fasta", "w") as output_file:
   7         for line in file:
   8             if line.startswith(">"):
   9                  new_strings.append(line)
  10             else:
  11                 new_strings.append(line.translate(table))
  12 
  13         output_file.writelines(new_strings)

Словари

Итак, как мы узнали, файлы можно подставлять в цикл for, чтобы перебирать их строки. Переменные некоторых типов, например числовые – 3, перечисляемыми не являются, они состоят только из одной переменной. Но некоторые другие типы переменных также как и файлы состоят из отдельных элементов, которые можно перебрать по одному. Типы переменных, которые состоят из нескольких других переменных, называются перечисляемыми (или итерируемыми). Самым простым примером такого типа является уже известный вам тип «список» (list): список [0, 1, 2, 3] состоит из 4 отдельных элементов.

Список хорош, чтобы хранить один набор переменных. Рассмотрим теперь следующую задачу: преподаватели курса python на ФББ решили написать свою собственную программу для ведения электронного журнала. Для этого им нужно хранить список базу данных, содержащую ФИО студентов и их баллы за выполнение заданий. Можно записать все имена в один список, а баллы в другой:

>>> students_names = [‘Vasya’, ‘Dima’, ‘Olya’]

>>> students_scores = [ [2,1],
[], # Dima is not a great student :(
[3,2,4,3] ]

# (т.к. у студента может быть больше одного балла, то мы храним оценки в виде списка списков)

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

Если есть необходимость хранить пары значений (в нашем случае это пара «Имя» – «Баллы»), то для таких задач в python есть специальный тип, который называется словарь (dict). Для задания словаря используются фигурные скобки:

>>> students_scores_dict = { 'Vasya' : [2,1],
'Dima' : [],
'Olya' : [3,2,4,3] }

Также можно использовать два других способа задать словарь:

>>> a = {'one': 1, 'two': 2, 'three': 3}  # We already know this
>>> b = dict(one=1, two=2, three=3)  # This is the same
>>> c = dict([('one', 1), ('two', 2), ('three', 3)])  # And this also
>>> a == b == c
True

В нашем примере имена называются ключами, а баллы студентов – значениями. Чтобы получить элемент словаря, нужно обратиться к нему по ключу:

>>> students_scores_dict['Vasya']
[2,1]

Для добавления новой пары «ключ»-«значение» используется следующая конструкция:

>>> students_scores_dict['Zhenya'] = [1,2,3]  # New student entered FBB
>>> students_scores_dict
{'Vasya': [2, 1], 'Dima': [], 'Olya': [3, 2, 4, 3], 'Zhenya': [1, 2, 3]}

Важно!

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

>>> students_scores_dict['Zhenya'] = [2,1]  # Trying to add one more student with the same key
>>> students_scores_dict  # But getting Zhenya overwrited
{'Vasya': [2, 1], 'Dima': [], 'Olya': [3, 2, 4, 3], 'Zhenya': [2, 1]}

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

Следующие методы словарей используются для работы с ними (верно только для python 2):

>>> len(students_scores_dict)  # Length of dict
4
>>> students_scores_dict.keys()  # Get list of keys
['Vasya', 'Dima', 'Olya', 'Zhenya']
>>> students_scores_dict.values()  # Get list of values
[[2, 1], [], [3, 2, 4, 3], [1, 2, 3]]
>>> students_scores_dict.items()  # Get list of tuples with pairs key-value
[('Vasya', [2, 1]), ('Dima', []), ('Olya', [3, 2, 4, 3]), ('Zhenya', [1, 2, 3])]
>>> if 'Zhenya' in students_scores_dict:  # Check if key is in dict
...     print(1)
... else:
...     print(0)
1
>>> if [1,2,3] in students_scores_dict:  # But it does not work for value
...     print(1)
... else:
...     print(0)
0

* Особенности python 3: в третьей версии питона методы .keys(), .values() и .items() словарей возвращают не списки, а объекты специального типа. Они относятся к упомянутым в начале конспекта перечисляемым типам, поэтому в обеих версиях языка можно использовать конструкции вида:

>>> for name in students_scores_dict.keys():
...     print(name)
Vasya
Dima
Olya
Zhenya

Однако получить ключи, значения или их пары в python 3, в виде списка как это было в python 2, просто так не получится, нужно использовать конструкцию вида:

>>> list( students_scores_dict.keys() )
['Vasya', 'Dima', 'Olya', 'Zhenya']

Во второй версии языка аналогами являются методы .viewkeys(), .viewvalues() и .viewitems().

Перебор по .items() можно вести двумя способами:

>>> for data in students_scores_dict.items():
...     name, scores = data

#Или сразу:

>>> for name, scores in students_scores_dict.items():

Важно!

Списки хранят переменные по порядку. В отличии от них, словари не хранят порядок своих переменных. Иными словами, new_dict.keys() от только что созданного словаря вернет вам ключи НЕ В ТОМ порядке, в котором вы их записали:

>>> new_dict = {'Three': 1, 'simple': 2, 'words': 3}
>>> new_dict
{'simple': 2, 'Three': 1, 'words': 3}
>>> new_dict.keys()
['simple', 'Three', 'words']
>>> new_dict.values()
[2, 1, 3]

Однако, порядок, в котором возвращают ключи и значения методы .keys() и .values() (и .items()) – один и тот же (в примере и в том и в другом случае сначала идет вторая из записанных пар, потом первая, потом третья).

Важно!

Ключом словаря могут быть только неизменяемые (**) объекты. Это значит, что ключом могут быть: числа, строки, кортежи (и все они одновременно); но не могут быть, например, списки и другие словари. Значениями может быть все, что угодно, и даже другие словари:

(**) на самом деле – хэшируемые; если вам интересно, что значит это слово – обращайтесь за пояснениями к преподавателю или к Google’у

>>> new_dict = {1:1, 'key':'value'}  # Keys could be variables of different types
>>> new_dict
{1: 1, 'key': 'value'}
>>> new_dict[ [1,2] ] = 'world!'  # But only not changeable – and list is changeable
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    new_dict[ [1,2] ] = 'world!'
TypeError: unhashable type: 'list'
>>> new_dict[ (1,2) ] = {0:'new dictionary'}  # However, tuple is fine
>>> new_dict
{(1, 2): {12: 'new dictionary'}, 1: 1, 'key': 'value'}
>>> new_dict[(1,2)][12]
'new dictionary'

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

Для списка проверка наличия элемента в списке требует время, пропорциональное длине списка. Это значит, что проверка >>> if a in list_b: займет в 100 раз больше времени для списка list_b из 100 000 элементов, чем для списка из 1000 элементов. И если вы работаете с (не такими уж и) большими объемами данных, то ваша программа может начать работать очень медленно, если вы используете списки. Однако проверка наличия ключа в словаре >>> if a in dict_b: не обладает такой проблемой. Более того, можно примерно сказать, что время поиска ключа в словаре происходит за одно и то же время, независимо от того, как много у вас элементов в списке – 1, 1000 или 100 000! Это очень полезно, и даже жизненно важно, когда вы работаете с большими объемами информации, и вы бы хотели, чтобы ваша программа закончила считать до того, как погаснет солнце.

(**) как по-вашему, в чем отличие (в python 2 для определенности) между >>> if a in dict_b: и >>> if a in dict_b.keys():? Если хочется проверить наличие в словаре по значению – можно использовать конструкцию >>> if a in dict_b.values(): - будет ли она работать в зависимости от длины словаря, или нет?

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

>>> new_dict = {'a':4, 'b':3, 'c':2, 'd':1}
>>> new_dict.items()  # Not sorted
[('a', 4), ('c', 2), ('b', 3), ('d', 1)]
>>> sorted(new_dict.items())  # Sorted by key
[('a', 4), ('b', 3), ('c', 2), ('d', 1)]
>>> sorted(new_dict.items(), key=lambda x:x[1])  # Magic trick to sort by value
[('d', 1), ('c', 2), ('b', 3), ('a', 4)]