Циклы. While

До данного момента мы с вами разобрали цикл for. Этот цикл является наиболее часто используемым в питоне, однако иногда он все же неудобен. Например, если вы не знаете, сколько раз вам необходимо будет совершить определенное действие. Для такого рода проблем существует цикл while.

while не замена for

In [3]:
# Это пример НЕПРАВИЛЬНОГО использования while, тут намного правильнее делать цикл for
a = 0
while a < 10:
    print (a)
    a += 1
0
1
2
3
4
5
6
7
8
9

Допустим, вы ведете диалог с пользователем и выполняете какое-то действие, пока он не введет "stop". Вот здесь без while обойтись сложно

In [4]:
answer = input("Input number: ")
while answer != "stop":
    answer = int(answer)
    if answer % 2 == 0:
        print("even")
    else:
        print("odd")
    answer = input("Input number: ")
Input number: 10
even
Input number: 10
even
Input number: stop

Коллекции

Коллекции - это какие-то наборы объектов разного или одного типа. 100 кошек, 10 собак, набор - [арбуз, тыква, голый землекоп], - все это является коллекцией.

В Python существует несколько разных типов, которые помогают вам хранить коллекции объектов в удобном для вас виде. Одним из базовых является список

Списки

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

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

In [5]:
my_lst1 = ['apple', 5, True]

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

In [6]:
my_lst2 = ['apple', ['tomato', 'fish'], 5]

Можно создать и пустой список, то есть список без элементов

In [7]:
empty_lst = []

Индексация

Как и строки, списки можно индексировать, как и в случае строк, индексация начинается с 0. Возможно брать отрицательные индексы

Пример индексации

In [8]:
my_lst2[1]
Out[8]:
['tomato', 'fish']
In [9]:
my_lst2[-1]
Out[9]:
5

Напоминание для строк:

Индексация строк

Срезы

Точно так же, как и для строк, можно брать срезы

In [10]:
my_lst2[0:-1]
Out[10]:
['apple', ['tomato', 'fish']]
In [11]:
my_lst2[-1:]
Out[11]:
[5]

Проход по элементам списка (итерация)

Можно пройтись по списку, просто по очереди обращаясь к каждому из его элементов по индексу. Для этого полезна функция len, которая, как и в случае строки, возвращает длину списка.

In [14]:
my_lst3 = [1, 7, -5, 0, 32]
In [16]:
for i in range(len(my_lst3)):
    print (my_lst3[i])
1
7
-5
0
32

Однако в случае списков (и вообще всех коллекций в Python), можно использовать цикл for напрямую!

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

In [17]:
for elem in my_lst3:
    print(elem)
1
7
-5
0
32

for можно применить и для строк. В этом случае вы будете итерироваться по символам строки

In [153]:
s = "Hello, world"
for c in s:
    print(c)
H
e
l
l
o
,
 
w
o
r
l
d

Операции над списками

Списки можно складывать (называется уже известным вам умным словом конкатенация)

In [18]:
a = [2,3,4]
b = [-5, 10]
a + b
Out[18]:
[2, 3, 4, -5, 10]
In [19]:
a - b # Я по-прежнему не могу понять, а чего кто-то ожидал:) 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-19-86c652a773c4> in <module>()
----> 1 a - b # Я по-прежнему не могу понять, а чего кто-то ожидал:)

TypeError: unsupported operand type(s) for -: 'list' and 'list'
In [20]:
a * b # аналогично
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-9af333135275> in <module>()
----> 1 a * b # аналогично

TypeError: can't multiply sequence by non-int of type 'list'

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

In [22]:
[0] * 5 # так делают
Out[22]:
[0, 0, 0, 0, 0]

Можно проверить, есть ли элемент в списке с помощью ключевого слова in

In [157]:
a = [1,3,10]
if 5 in a:
    print ("5 is in a")
else:
    print ("5 is not in a")
5 is not in a

Изменяемость списков

В случае со строками, если вы попробуете изменить какой-то символ, то выскочит ошибка

In [25]:
a = "Hello, world!"
a[-1] = "?"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-0c5d5d1e9b5d> in <module>()
      1 a = "Hello, world!"
----> 2 a[-1] = "?"

TypeError: 'str' object does not support item assignment

Приходится делать, например, так:

In [26]:
a = a[:-1] + "?"
a
Out[26]:
'Hello, world?'

В случае со списками все намного проще. Они являются изменяемыми объектами

In [27]:
a = [1, -3, 10, 6]
In [28]:
a[-1] = 5
a
Out[28]:
[1, -3, 10, 5]
In [29]:
a[0:1] = [0,1]
In [30]:
a
Out[30]:
[0, 1, -3, 10, 5]

Изменяемые объекты. Проблемы.

In [33]:
a = [10, 5, 4]
b = a
a[-2] = 7

Что лежит в b?

In [34]:
b[-2]
Out[34]:
7

TA2lrY

В чем же секрет? Если вспомнить аналогию про кошку и ящик, то все становится ясно

cat

В компьютере у вас есть великое множество коробок, в одну из которых вы положили кошку (в данном случае список). Но при этом вы решили называть эту коробку и a, и b. Python не создавал новой кошки в новой коробке. Просто у этой коробки теперь два имени. Потому, дразня кошку в переменной a, вы рискуете получить поцарапанные руки и в переменной b

In [142]:
colors = ['red', 'blue', 'green']
b = colors

colors

Чтобы бороться с таким поведением Python, вам придется заставить его скопировать список!

In [38]:
a = [10, 5, 4]
b = a[:] # берем срез всего списка - это его скопирует 
a[-2] = 7
print ("a", a)
print ("b", b)
a [10, 7, 4]
b [10, 5, 4]

Также можно использовать встроенную функцию Python - list

In [39]:
a = [10, 5, 4]
b = list(a) # берем срез всего списка - это его скопирует 
a[-2] = 7
print ("a", a)
print ("b", b)
a [10, 7, 4]
b [10, 5, 4]

Методы списка

Список, как и многие другие встроенные типы в Python, имеет набор функций, предназначенных для работы с ним и вызываемых с помощью конструкции вида lst.method_name

append - Добавление в список

Хочется уметь достраивать список в ходе работы программы. Например, можем создать список всех нечетных чисел до 20

In [41]:
lst = []
for i in range(20 + 1):
    if i % 2 == 1:
        lst.append(i)
        
print (lst)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

extend - хотим добавить в конец нашего списка другой

In [42]:
lst = [1, 4]
lst.extend([1,2,7])
lst
Out[42]:
[1, 4, 1, 2, 7]

count - подсчитать число вхождений элемента в список

In [43]:
lst = [1, 7, -5, 6, 0, 1]
lst.count(1)
Out[43]:
2

sort - отсортировать список на месте

In [44]:
lst.sort()
lst
Out[44]:
[-5, 0, 1, 1, 6, 7]

index - найти первый индекс значения в листе, если значения нет, то вернется ошибка

In [50]:
lst.index(0)
Out[50]:
1

Методы строк

Для строк, помимо разобранного на прошлом занятии метода split так же существует множество других методов. Обратите внимание, что, в отличии от списка, если эти методы подразумевают модификацию строки, то они возвращают НОВУЮ строку.

strip - удалить перенос строки и прочие "белые" символы по краям строки. Существуют аналоги - rstrip и lstrip, удаляющие эти символы только с одной стороны.

In [155]:
a = "   hello \n"
b = a.strip()
print (b)
print("START:", a, "END")
hello
START:    hello 
 END

replace - заменить все вхождения одной подстроки на другую

In [62]:
a = "hello, hello, hello"
b = a.replace("hello", "hell")
print(b)
hell, hell, hell

find - найти вхождение подстроки в строку. Если подстроки нет в строке, то вернуть -1

In [57]:
a = "Hello, world"
a.find("world")
Out[57]:
7
In [58]:
a.find("wild")
Out[58]:
-1
In [156]:
a = "hello, hello, hello"
f_ind = a.find("hello")
print (f_ind )
print (a.find("hello", f_ind  + 1)) # искать с определенной позиции, поможет вам в домашнем задании 
0
7

Чтобы проверить, содержится ли строка в другой строке, можно просто использовать ключевое слово in

In [70]:
print ("wo" in "world")
print ("he" in "world")
True
False

startswith - начинает ли строка на данную подстроку

In [63]:
print (a.startswith("hello"))
print (a.startswith("ello"))
True
False

endswith - заканчивается ли строка на данную подстроку

In [65]:
print (a.endswith("hello"))
print (a.endswith("he"))
True
False

join - объединить несколько строк через разделитель

In [68]:
", ".join(["1", "2", "apple"])
Out[68]:
'1, 2, apple'

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

Одна из основных деятельностей в биоинформатике - это чтение и запись файлов)

Для того, чтобы открыть новый файл на запись служит команда open(filename, "w")

In [117]:
outfile = open("test1.txt", "w")
In [118]:
outfile
Out[118]:
<_io.TextIOWrapper name='test1.txt' mode='w' encoding='UTF-8'>

Для записи в файл используется команда write

In [119]:
outfile.write("1 3\n") # необходимо указывать перенос строки явно
outfile.write("1 10\n")
Out[119]:
5

После работы с файлом его необходимо закрыть при помощи команды close

In [120]:
outfile.close()

Для того, чтобы открыть файл на чтение служит команда open(filename, "r")

In [125]:
infile = open("test1.txt", "r")
In [126]:
line = infile.readline() # прочитать одну строку из файла
line # возвращается с переносом строки
Out[126]:
'1 3\n'
In [127]:
line.strip() # чаще всего полезно избавиться
Out[127]:
'1 3'
In [128]:
print("Line: ", infile.readline().strip())
print("Line: ", infile.readline().strip())
Line:  1 10
Line:  

Когда файл дочитан до конца, readline возвращает пустую строку

Файл надо закрыть!

In [129]:
infile.close()

Казалось бы, можно организовать чтение всего файла следующим образом:

In [131]:
infile = open("test1.txt", "r")
line = infile.readline()
while line != "":
    print(line.strip())
    line = infile.readline()

infile.close()
1 3
1 10

Однако в Python есть два механизма, которые помогают упростить код, написанный выше. Во-первых, для чтения файла можно использовать цикл for

Если вам нужно обработать все строки из файла одинаковым способом, то этот способ наиболее предпочтителен - он не только компактнее и соответствует стилю Python, но он еще и быстрее.

In [150]:
infile = open("test1.txt", "r")
for line in infile:
    print(line.strip())

infile.close()
1 3
1 10

Один раз пройдя таким образом по файлу, при попытке пройти по нему второй раз, вы ничего не прочитаете нового - вы уже в конце файла

In [151]:
infile = open("test1.txt", "r")
for line in infile:
    print(line.strip())
    
for line in infile: #won't be executed
    print(line.strip())
infile.close()
1 3
1 10

Однако, можно заставить Python вернуться в начало файла командой seek

In [152]:
infile = open("test1.txt", "r")
for line in infile:
    print(line.strip())
    
infile.seek(0) # go to the start of the file
for line in infile: 
    print(line.strip())
infile.close()
1 3
1 10
1 3
1 10

Возвращаясь к структуре кода, даже так в коде осталось небольшое неудобство. А что если мы забудем закрыть файл? Чаще всего - ничего, но мы можем столкнуться с кучей проблемой и труднообнаружимых ошибок из-за этого. Для решения этого вопроса было придум специальный блок with, который позволяет сказать Python закрыть файл автоматически по окончанию работы с ним.

Согласитесь, выглядит красиво:?

In [134]:
with open("test1.txt", "r") as infile:
    for line in infile:
        print(line.strip())
1 3
1 10

В случае с записью файл нужно быть еще более внимательным к закрытию файла, так как Python пишет изменения в сам файл не сразу, потому, неправильно завершив программу, вы можете остаться с файлом, в котором ничего нет.

Для примера использования with для записи запишем в новый файл четные числа до 100

In [135]:
with open("test2.txt", "w") as outfile:
    for i in range(100 + 1):
        if i % 2 == 0:
            outfile.write(str(i) + "\n")

Аргументы командной строки (sys.argv)

Иногда удобно, чтобы скрипт мог принимать аргументы через командую строку, например

python my_script.py 5 10

Все аргументы, которые передаются python при запуске, хранятся в специальном списке sys.argv. Для работы с ним нужно сделать немного, пока, магии:)

In [143]:
import sys

# так как мы сейчас в Jupyter notebook, то первый аргумент это скрипт, его запускающий, два других - аргументы, которые ему передали 
sys.argv
Out[143]:
['/Users/dmitrypenzar/miniconda3/lib/python3.6/site-packages/ipykernel_launcher.py',
 '-f',
 '/Users/dmitrypenzar/Library/Jupyter/runtime/kernel-5469f680-6a16-402e-99cc-12cd51891c30.json']
In [144]:
sys.argv[0]
Out[144]:
'/Users/dmitrypenzar/miniconda3/lib/python3.6/site-packages/ipykernel_launcher.py'
In [145]:
sys.argv[1]
Out[145]:
'-f'
In [146]:
sys.argv[2]
Out[146]:
'/Users/dmitrypenzar/Library/Jupyter/runtime/kernel-5469f680-6a16-402e-99cc-12cd51891c30.json'