Вспоминаем Python!

Часть 2

Циклы

Цикл for

In [2]:
for i in range(100): # делать 100 раз, i примет значения от 0 до 99
    if i % 17 == 0:
        print(i)
0
17
34
51
68
85

Цикл while

Мы с вами разобрали циклы for и while. Цикл for является наиболее часто используемым в питоне, однако иногда он все же неудобен.

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

Для такого рода проблем существует цикл while.

Помните, while не замена for

Вот пример некорректного использования while, тут правильно использовать for:

In [1]:
a = 0
while a < 5:
    print (a)
    a += 1
0
1
2
3
4

А, например, если вы ведете диалог с пользователем и выполняете какое-то действие, пока он не введет "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

break и continue

Два важных соуса к циклам for и while - это операторы break и continue

break позволяет досрочно выйти из цикла

In [116]:
# Проверка того, что число простое
a = input()
a = int(a)

if a > 1:
    is_prime = True
    for i in range(2, a): # итерироваться, начиная с 2 и заканчивая a - 1
        if a % i == 0: # если число делится на что-то из этого без остатка, 
            # то оно не простое
            is_prime = False
            break  # можем досрочно завершить цикл, 
            # у нас нет смысла что-либо проверять дальше
else:
    is_prime = False
    
print(is_prime)
11
True

continue позволяет досрочно перейти к следующей итерации цикла

In [118]:
for i in range(5):
    if i == 2:
        continue
    print(i)
0
1
3
4

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

In [44]:
for i in range(0, 5):
    if i % 13 == 0:
        pass # insert fix here
    print(i)
0
1
2
3
4

Что такое коллекция?

Какие виды коллекций в Python вы помните?

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

Итерация (лат. iteratio — «повторение») — повторение какого-либо действия.

Итерация в программировании — один шаг цикла / одно исполнение заданных команд.

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

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

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

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

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

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

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

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

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

In [66]:
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 [67]:
a = [1, 3, 4, 100, 34]
for i in a:
    if i % 2 == 0:
        break
i
Out[67]:
4

Проблема в том, что если элемента не найдено, то i примет значение последнего элемента в списке.

In [68]:
a = [1, 3, 7, 11]
for i in a:
    if i % 2 == 0:
        break
i
Out[68]:
11

Еще одна проблема, что если i не было ранее объявлено, а мы решили пройтись по пустому списку, то i останется необъявленным и мы получим ошибку:

In [69]:
del i # remove variable i
a = []
for i in []:
    if i % 2 == 0:
        break
i
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-69-b6ded53dcdda> in <module>()
      4     if i % 2 == 0:
      5         break
----> 6 i

NameError: name 'i' is not defined

Обе проблемы решаются использованием else в for-цикле!

Обратите внимание, что else расположено на одном уровне с for и относится именно к нему!

else в этом случае выполняется, только если выход из for не был осуществлен при помощи break (то есть мы прошли по всем значениям в контейнере)

In [70]:
a = [1, 3, 7, 11]
for i in a:
    if i % 2 == 0:
        break
else:
    i = None 
    print("No even elements")
print (i)
No even elements
None

Обе проблемы решаются использованием else в for-цикле!

Обратите внимание, что else расположено на одном уровне с for и относится именно к нему!

else в этом случае выполняется, только если выход из for не был осуществлен при помощи break (то есть мы прошли по всем значениям в контейнере)

In [71]:
a = []
for i in a:
    if i % 2 == 0:
        break
else:
    i = None
    print("No even elements")
print (i)
No even elements
None

Если искомый элемент все же есть, то все работает хорошо!)

In [72]:
a = [1, 3, 7, 12]
for i in a:
    if i % 2 == 0:
        break
else:
    i = None
    print("No even elements")
print (i)
12

Проверка списка на пустоту

Работает не только для списка, а для всех коллекций/контейнеров

In [14]:
if not a: # if a is empty
    print("a is empty")

b = [1]
if b: # if b is not empty
    print("b is not empty")
b is not empty

Вспомните, как копировать список

Подсказка: есть 3 способа!

Копирование вложенных объектов

Для вложенных списков и просто изменяемых объектов в изменяемых объектов - функция deepcopy модуля copy

In [97]:
from copy import deepcopy
In [97]:
lst1 = ['a','b',['ab','ba']]
lst2 = deepcopy(lst1)

lst2[2][1] = "d"
lst2[0] = "c";
print(lst1)
print(lst2)
['a', 'b', ['ab', 'ba']]
['c', 'b', ['ab', 'd']]

deep_copy

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

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

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

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

In [98]:
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 [11]:
a = [1, 4]
b = [1, 2, 7]
c = a + b
print(a, b, c)
[1, 4] [1, 2, 7] [1, 4, 1, 2, 7]
In [12]:
a[1] = 5
print(a, b, c)
[1, 5] [1, 2, 7] [1, 4, 1, 2, 7]
In [100]:
lst = [1, 4]
lst.extend([1, 2, 7])
lst
Out[100]:
[1, 4, 1, 2, 7]

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

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

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

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

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

In [10]:
lst = [-5, 0, 1, 1, 6, 7]
In [103]:
lst.index(0)
Out[103]:
1

Методы строк

Для строк существует множество методов.

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

.strip - удалить перенос строки и прочие "белые" символы по краям строки.

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

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

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

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

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

In [106]:
a = "Hello, world"
a.find("world")
Out[106]:
7
In [107]:
a.find("wild")
Out[107]:
-1
In [17]:
a = "hello, hello, hello"
f_ind = a.find("hello")
print(f_ind)
print(a.find("hello", f_ind  + 1))
0
7

Метод позволит вам искать в строке, начиная с определенной позиции, это поможет вам в домашнем задании :^)

In [16]:
?a.find

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

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

Но если вам нужно искать в специфичном месте строки, то есть специальные методы!

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

In [13]:
a = "hello, hello, hello"
In [111]:
print(a.startswith("hello"))
print(a.startswith("ello"))
True
False

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

In [ ]:
a = "hello, hello, hello"
In [112]:
print(a.endswith("hello"))
print(a.endswith("he"))
True
False

.join - объединить строкой-разделителем несколько других строк в контейнере

In [1]:
", ".join(["1", "2", "apple"])
Out[1]:
'1, 2, apple'
In [2]:
a = list("ATGATAGATAG")
a
Out[2]:
['A', 'T', 'G', 'A', 'T', 'A', 'G', 'A', 'T', 'A', 'G']
In [3]:
"G".join(a)
Out[3]:
'AGTGGGAGTGAGGGAGTGAGG'

Работаем со словарями

Как создать словарь?

In [7]:
my_dict = {'Cats': 10,
           'Rats': 20,
           'Mice': 52}

Что будет, если мы запросим значение словаря по ключу 'Dogs'?

In [8]:
my_dict['Dogs']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-8-cd4e80ef3946> in <module>
----> 1 my_dict['Dogs']

KeyError: 'Dogs'

Можно использовать метод .get(key, default_value=None) для того, чтобы в случае отсутствия ключа в словаре получать какое-то значение по умолчанию.

In [9]:
print(my_dict.get("Dogs"))
None
In [10]:
my_dict.get("Cats", 5)
Out[10]:
10

А как просто проверить, есть ли такой ключ в словаре?

Проверка наличия ключа в словаре

Как и для других коллекций, для проверки наличия ключа в словаре используется слово in

In [11]:
if "Dogs" in my_dict:
    print("Oh my!")
else:
    print("Nice!")
Nice!

Проход по ключам словаря

Учтите, что методы словаря, "возвращающие" хранимое в нем, возвращают не-совсем-списки, с этим объектом нельзя работать так же, как со списком

Пройтись по ключам словаря (итерировать) можно двумя способами

In [12]:
my_dict.keys()
Out[12]:
dict_keys(['Cats', 'Rats', 'Mice'])

Способ 1:

In [13]:
for key in my_dict.keys():
    print (key)
Cats
Rats
Mice

Способ 2:

In [14]:
for key in my_dict:
    print (key)
Cats
Rats
Mice

Проход по значениям словаря

In [15]:
my_dict.values()
Out[15]:
dict_values([10, 20, 52])
In [16]:
for value in my_dict.values():
    print (value)
10
20
52

Проход по ключам и значениям словаря

In [17]:
for key in my_dict.keys():
    value = my_dict[key]
    print(key, value)
Cats 10
Rats 20
Mice 52

В чем тут недостаток?

In [18]:
my_dict.items()
Out[18]:
dict_items([('Cats', 10), ('Rats', 20), ('Mice', 52)])
In [19]:
for key, value in my_dict.items():
    print(key, value)
Cats 10
Rats 20
Mice 52
In [20]:
list(my_dict.keys())
Out[20]:
['Cats', 'Rats', 'Mice']
In [21]:
list(my_dict.values())
Out[21]:
[10, 20, 52]
In [22]:
list(my_dict.items())
Out[22]:
[('Cats', 10), ('Rats', 20), ('Mice', 52)]

Удаление элементов из словаря

In [139]:
a = {"A": 0,
     "T": 1, 
     "G": 2, 
     "C": 3, 
     "G": 4, 
     "U": 5}
In [140]:
a
Out[140]:
{'A': 0, 'C': 3, 'G': 4, 'T': 1, 'U': 5}
In [140]:
a
Out[140]:
{'A': 0, 'C': 3, 'G': 4, 'T': 1, 'U': 5}
In [141]:
del a['U']
a
Out[141]:
{'A': 0, 'C': 3, 'G': 4, 'T': 1}

Все хорошо? Попробуем еще раз!

In [142]:
del a['U']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-142-a67e9e64d21f> in <module>()
----> 1 del a['U']

KeyError: 'U'

Можно использовать метод .pop - он правильнее

In [143]:
a = {"A" : 0, "T" : 1, "G": 2, "C" : 3, "G" : 4, "U" : 5}
a.pop('U')
a
Out[143]:
{'A': 0, 'C': 3, 'G': 4, 'T': 1}
In [144]:
a.pop('U')
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-144-f443700df4c8> in <module>()
----> 1 a.pop('U')

KeyError: 'U'
In [145]:
a.pop('U', None)

Будет ли работать такой код?

In [ ]:
a = {"A": 0, 
     "T": 1,
     "G": 2, 
     "C": 3, 
     "G": 4,
     "U": 5}

for key in a.keys():
    if key == "C":
        a.pop(key)

Будет ли работать такой код?

In [4]:
a = {"A": 0, 
     "T": 1,
     "G": 2, 
     "C": 3, 
     "G": 4,
     "U": 5}

for key in a.keys():
    if key == "C":
        a.pop(key)
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-4-ae436969eca3> in <module>
      6      "U": 5}
      7 
----> 8 for key in a.keys():
      9     if key == "C":
     10         a.pop(key)

RuntimeError: dictionary changed size during iteration

Как обойти проблему?

Можно обойти проблему так:

In [147]:
a = {"A": 0, "TC": 1, 
     "G": 2, "CG": 3, 
     "GC": 4, "U": 5}

keys = list(a.keys())
for key in keys:
    if "C" in key:
        a.pop(key)
a
Out[147]:
{'A': 0, 'G': 2, 'U': 5}

Порядок ключей в словаре

До версии Python3.5 включительно никто не гарантировал вам никакого порядка ключей в словаре. То есть то, как они вам выдавались функцией keys и т.д не зависело ни от порядка вставки элементов, не от результата их сравнения напрямую.

Однако с версии Python3.6 в наиболее распространненой реализации (CPython), а с версии Python3.7 - в любой, порядок ключей в словаре соответствует порядку их вставки в него. Если ключ уже существовал в словаре и вы его перезаписали, то порядок ключей не изменится.

Преимущества словарей

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

Недостатки словарей

Словари "кушают" много памяти. Все операции на словарях быстры в среднем — отдельная операция может длиться очень долго.

Кортежи

Если очень хочется сделать ключом словаря список, то нужно использовать не list, а другой тип данных — кортеж tuple.

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

In [148]:
ta = (1, 2, 5, 4)
ta[0]
Out[148]:
1

Как сделать кортеж длиной 1?

In [5]:
a = (1)
print(a)
1
In [6]:
a = (1, )
print(a)
(1,)

Кортеж во многом похож на список, но главным отличием является то, что кортеж неизменяем.

In [150]:
ta[0] = 5
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-150-387a39ba50ab> in <module>()
----> 1 ta[0] = 5

TypeError: 'tuple' object does not support item assignment

Можно ли использовать список как ключ словаря?

In [151]:
dt = {}

dt[(1, 2)] = "Hello"
dt[(2, 1)] = "world"
dt
Out[151]:
{(1, 2): 'Hello', (2, 1): 'world'}
In [152]:
dt[(1, 2)]
Out[152]:
'Hello'

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

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

Множество

В некоторых случаях удобно пользоваться типом данных "множество" set.

Это именно множество в математическом смысле этого слова, то есть его элементы уникальны и не могут повторяться (в отличие от списка и кортежа).

In [153]:
s = {-10, 20, 45}
s.add(348)
s.add(-100000)
s.add(-10)
s
Out[153]:
{-100000, -10, 20, 45, 348}
In [154]:
my_set1 = {1, 7, 9} # first way to create set
my_set2 = set([0, -4, 10, 1]) # second way
In [155]:
lst = [1, 10, -5, -5, 20]
my_set = set(lst)
my_set
Out[155]:
{-5, 1, 10, 20}
In [156]:
for elem in my_set:
    print(elem)
1
10
-5
20

Как задать пустое множество?

In [7]:
a = {}
In [8]:
type(a)
Out[8]:
dict
In [9]:
a = set(); type(a)
Out[9]:
set

Множества используются в типичном решении того, как оставить в списке только уникальные элементы

In [157]:
lst = [1, 10, -5, -5, 20, -10, 20, 0, 0, 0]
my_set = set(lst)
unique_lst = list(my_set)
unique_lst
Out[157]:
[0, 1, 10, 20, -10, -5]

На множествах определены операции объединения, пересечения и разности.

In [158]:
my_set1 & my_set2  # intersection
Out[158]:
{1}
In [159]:
my_set1 | my_set2  # join
Out[159]:
{-4, 0, 1, 7, 9, 10}
In [160]:
my_set1 - my_set2  # difference 
Out[160]:
{7, 9}
In [161]:
my_set1.symmetric_difference(my_set2)
Out[161]:
{-4, 0, 7, 9, 10}

Проверку на наличие элемента в множестве можно провести при помощи уже известного вам ключевого слова in

In [162]:
5 in my_set1
Out[162]:
False
In [167]:
nucleotides = {"A", "G", "T", "U", "C"}

n = input()

if not (n in nucleotides):
    print ("error")
A
In [171]:
nucleotides = {"A", "G", "T", "U", "C"}

n = input()

if n not in nucleotides:
    print ("error")
a
error

Множество — изменяемый тип (не может быть ключом словаря, но может быть значением словаря, что можно применить в задании 6).

Изменяемость нужна, чтобы использовать удобные в некоторых ситуациях методы .add, .pop и .remove (см. help(set.add) и т.п.).

In [172]:
print (my_set1.pop())
my_set1
5
Out[172]:
{7, 9}
In [173]:
my_set1.add(5)
my_set1
Out[173]:
{5, 7, 9}

Порядок элементов в множестве

Порядок элементов в множестве по-прежнему не гарантирован и не зависит ни от чего. У вас результат запуска может отличаться, к примеру)

In [174]:
s = {-10, 20, 45}
s.add(348)
s.add(-100000)
s
Out[174]:
{-100000, -10, 20, 45, 348}

frozenset

Неизменяемым аналогом set является frozenset. В отличии от обычного множества его можно использовать в качестве ключа в словаря.

In [175]:
my_frozen = frozenset([4, 5, 6])
my_frozen
Out[175]:
frozenset({4, 5, 6})
In [176]:
my_frozen.add(4)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-176-06075b4ac502> in <module>()
----> 1 my_frozen.add(4)

AttributeError: 'frozenset' object has no attribute 'add'

Модуль сollections!

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

Counter

In [177]:
from collections import Counter

nucleotides = ["A", "T", "G", "C"]
seq = "ATAATATATATGAGGCGGCGCGCGCG"
cnt = Counter(seq)
print(cnt)
for n in nucleotides:
    print (cnt[n])
Counter({'G': 9, 'A': 7, 'T': 5, 'C': 5})
7
5
9
5

defaultdict

In [180]:
from collections import defaultdict

dl_dict = defaultdict(list) 
# для каждого ключа, которого нет в словаре, но мы его запросили - создать по-умолчанию пустой список
print(dl_dict)
print(dl_dict[1])
print(dl_dict)
dl_dict[1].append("A")
dl_dict[1].append("A")
dl_dict[2].append("A")
dl_dict[100].append("C")
print ("dict: ", dl_dict, sep="   ")
print ("dict[1]", dl_dict[1], sep="   ")
print ("dict[20]", dl_dict[20], sep="   ")
print ("dict", dl_dict, sep="  ")
defaultdict(<class 'list'>, {})
[]
defaultdict(<class 'list'>, {1: []})
dict:    defaultdict(<class 'list'>, {1: ['A', 'A'], 2: ['A'], 100: ['C']})
dict[1]   ['A', 'A']
dict[20]   []
dict  defaultdict(<class 'list'>, {1: ['A', 'A'], 2: ['A'], 100: ['C'], 20: []})