Словари

Часто перед нами встает задача по какому-то, нечисленному, значению, найти соответствующее ему другое значение. Например, по ФИО человека номер его паспорта

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

In [2]:
my_dict = { "Dmitry" : 5,
            "Alexander" : 10,
            "Jupyter": 20}
my_dict
Out[2]:
{'Dmitry': 5, 'Alexander': 10, 'Jupyter': 20}

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

In [3]:
my_dict['Jupyter']
Out[3]:
20

В случае, если ключа с таким значением нет, получите ошибку:

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

KeyError: 'Dogs'

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

In [8]:
print (my_dict.get("Dogs")) # return None 
None
In [10]:
my_dict.get("Dogs", 5)
Out[10]:
5
In [5]:
my_dict.get("Jupyter", 5)
Out[5]:
20

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

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

In [11]:
if "Dogs" in my_dict:
    print("Hello")
else:
    print("World")
World

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

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

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

In [5]:
my_dict.keys()
Out[5]:
dict_keys(['Dmitry', 'Alexander', 'Jupyter'])
In [13]:
for key in my_dict.keys():
    print (key)
Dmitry
Alexander
Jupyter
In [14]:
for key in my_dict:
    print (key)
Dmitry
Alexander
Jupyter

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

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

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

In [7]:
for key in my_dict.keys():
    value = my_dict[key]
    print (key, value)
Dmitry 5
Alexander 10
Jupyter 20
In [8]:
my_dict.items()
Out[8]:
dict_items([('Dmitry', 5), ('Alexander', 10), ('Jupyter', 20)])
In [16]:
for key, value in my_dict.items():
    print (key, value)
Dmitry 5
Alexander 10
Jupyter 20
In [6]:
list(my_dict.keys())
Out[6]:
['Dmitry', 'Alexander', 'Jupyter']
In [7]:
list(my_dict.values())
Out[7]:
[5, 10, 20]
In [8]:
list(my_dict.items())
Out[8]:
[('Dmitry', 5), ('Alexander', 10), ('Jupyter', 20)]

Добавление элементов в словарь

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

In [10]:
d = dict() # another way to create dict
# d = {}
d[10] = "Hello"
d
Out[10]:
{10: 'Hello'}
In [11]:
d["hello"] = "world"
d
Out[11]:
{10: 'Hello', 'hello': 'world'}
In [13]:
my_key = "hi"
my_value = [1,2,3]
d[my_key] = my_value
d
Out[13]:
{10: 'Hello', 'hello': 'world', 'hi': [1, 2, 3]}
In [20]:
d[[1,2,3]] = 5  

# error, list is mutable (unhashable, to be precise)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-38846bdd94bd> in <module>()
----> 1 d[[1,2,3]] = 5  # error, list is mutable (unhashble, to be precise)

TypeError: unhashable type: 'list'
In [39]:
to_i_key = (1,2,3)
to_i_value = "art"
a = {'ac': 33, 'gw': 20, 'ap': 102, 'za': 321, 'bs': 10, "acccccc": 10}
a[to_i_key] = to_i_value
a
Out[39]:
{'ac': 33,
 'gw': 20,
 'ap': 102,
 'za': 321,
 'bs': 10,
 'acccccc': 10,
 (1, 2, 3): 'art'}

Дополнительная информация

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

Например, Counter легко может помочь в решении некоторых домашних работ:)

In [15]:
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

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

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

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

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

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

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

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

Кортежи

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

In [21]:
ta = (1, 2, 5, 4)
ta[0]
Out[21]:
1
In [16]:
a = (1)
print (a)

a = (1, )
print(a)
1
(1,)

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

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

TypeError: 'tuple' object does not support item assignment
In [18]:
dt = {}

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

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

Множество

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

In [42]:
s = {-10, 20, 45}
s.add(348)
s.add(-100000)
s
Out[42]:
{-100000, -10, 20, 45, 348}
In [44]:
s = {-10:0, 20:0, 45:0}
s[348] = 0
s[-100000] = 0
s
Out[44]:
{-100000: 0, -10: 0, 20: 0, 45: 0, 348: 0}
In [43]:
my_set1 = {1, 7, 9} # first way to create set
my_set2 = set([0, -4, 10, 1]) # second way
In [11]:
lst = [1, 10, -5, -5, 20]
my_set = set(lst)
my_set
Out[11]:
{-5, 1, 10, 20}

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

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

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

In [32]:
my_set1 & my_set2 # intersection
Out[32]:
{1}
In [33]:
my_set1 | my_set2 # join
Out[33]:
{-4, 0, 1, 7, 9, 10}
In [34]:
my_set1 - my_set2 # difference 
Out[34]:
{7, 9}
In [35]:
my_set1.symmetric_difference(my_set2)
Out[35]:
{-4, 0, 7, 9, 10}

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

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

n = input()

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

n = input()

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

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

In [38]:
print (my_set1.pop())
my_set1
1
Out[38]:
{7, 9}
In [39]:
my_set1.add(5)
my_set1
Out[39]:
{5, 7, 9}

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

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

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

Frozenset

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

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

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

Повторение пройденного. Файлы

Немного копипасты из моих же подсказок прошлого года:)

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

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

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

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

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

In [ ]:
# не запускайте этот код, если не готовы перезагружать тетрадь
opened_files = []

for i in range(1, 10000):
    fl = open(str(i), "w")
    opened_files.append(fl)

К сожалению, в IPython Notebook мы просто зависнем, однако в консоли, где мы его открыли (она и на Windows появляется), вылетит исключение zmq.error.ZMQError: Too many open files.

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

In [1]:
scr = '''
opened_files = []
for i in range(1, 10000):
    fl = open('Python_basics_Lecture3.ipynb', "r")
    opened_files.append(fl)
''' # another way to define multiline strings in Python


with open('script.py', 'w') as outfile:
    outfile.write(scr)
In [2]:
outfile
Out[2]:
<_io.TextIOWrapper name='script.py' mode='w' encoding='cp1251'>

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

In [8]:
!python script.py
Traceback (most recent call last):
  File "script.py", line 4, in <module>
OSError: [Errno 24] Too many open files: 'Python_basics_Lecture3.ipynb'

Теперь посмотрим на то, что будет, если мы пишем в файл, но не смогли его закрыть (забыли или случилось исключение)

In [7]:
fl = open('example.txt', "w")
for i in range(1000, -1000, -1):
    1 / i
    fl.write(str(i) + "\n")
fl.close()
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-7-d6c4a3385905> in <module>()
      1 fl = open('example.txt', "w")
      2 for i in range(1000, -1000, -1):
----> 3     1 / i
      4     fl.write(str(i) + "\n")
      5 fl.close()

ZeroDivisionError: division by zero

Открываем этот же файл на чтение

In [8]:
fl1 = open('example.txt', 'r')

И в нем ничего нет:)

In [9]:
fl1.readline() # no info....
Out[9]:
''

Теперь закроем файл (на запись)

In [10]:
fl.close()

Информация появилась

In [11]:
fl1.readline()
Out[11]:
'1000\n'

Функции

Функция - фрагмент программного кода, к которому можно обратиться из другого места программы

В Python функции задаются ключевым словом def.

In [23]:
def say_hello():
    print ("Hello")
    
say_hello()
Hello
In [3]:
def mod(a, b):
    return a % b

print (mod(3,4))
3
In [13]:
def wrong_mod(a, b):
    a % b

print (wrong_mod(3,4))
None

Функция может возвращать несколько значений, в этом случае она вернет их в качестве кортежа.

In [1]:
def s_pats(string, pats):
    ind = -1
    for p in pats:
        ind = string.find(p)
        if ind != -1:
            break
    return ind, p

res = s_pats("Hello, world", ["Hell", "world",
                              "happiness"])
print (type(res))
ind, p = res
print(ind, p)
<class 'tuple'>
0 Hell

Пример функции, реализующей факториал и вычисление n-го числа Фибоначчи

In [12]:
def factorial(n):
    acc = 1
    for i in range(2,n + 1):
        acc *= i
    return acc

def fibonacci(n):
    f0, f1 = 0, 1
    for i in range(n):
        f0, f1 = f1, f0 + f1
        # temp = f0
        # f0 = f1
        # f1 = f1 + temp
    return f0
In [13]:
print (factorial(5))
print (fibonacci(5))
120
5

Рекурсия

recursion

Python поддерживает механизм рекурсии - вызов функцией самой себя. Перепишем, например, предыдущие функции в рекурсивной форме

In [4]:
def factorial_rec(n):
    if n <= 1:
        return 1
    return n * factorial_rec(n - 1)

def fibonacci_rec(n):
    if n < 0:
        return 0
    
    if n == 0:
        return 0
    if n == 1:
        return 1
    
    return fibonacci_rec(n - 1) + fibonacci_rec(n - 2)
In [7]:
print (factorial_rec(5))
print (fibonacci_rec(5))
120
5

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

factorial

Замедление из-за использования рекурсии

Здесь это объясняется тем, что рекурсия - допронительные расходы для приостановки работы функции и возвращении в нее после.

In [9]:
%%timeit
factorial(1000)
402 µs ± 851 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [8]:
%%timeit
factorial_rec(1000)
739 µs ± 14.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Здесь же все еще хуже

In [11]:
%%timeit
fibonacci(20)
1.9 µs ± 1.93 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [10]:
%%timeit
fibonacci_rec(20)
6.01 ms ± 35.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

У fibonacci сразу две проблемы - то, что есть необходимость приостановки функций, и то, что мы повторяем многие вычисления множество раз. Например, вычисление второго числа Фибоначчи на картинке будет осуществлено 3 раза.

С этим можно бороться, но это не тема этой лекции

fibonacci

Бесконечная рекурсия

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

In [17]:
def factorial_rec_no_exit(n):
    return n * factorial_rec_no_exit(n - 1)

factorial_rec_no_exit(10) # good night, sweet prince
---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
<ipython-input-17-0e60f9985128> in <module>()
      2     return n * factorial_rec_no_exit(n - 1)
      3 
----> 4 factorial_rec_no_exit(10) # good night, sweet prince

<ipython-input-17-0e60f9985128> in factorial_rec_no_exit(n)
      1 def factorial_rec_no_exit(n):
----> 2     return n * factorial_rec_no_exit(n - 1)
      3 
      4 factorial_rec_no_exit(10) # good night, sweet prince

... last 1 frames repeated, from the frame below ...

<ipython-input-17-0e60f9985128> in factorial_rec_no_exit(n)
      1 def factorial_rec_no_exit(n):
----> 2     return n * factorial_rec_no_exit(n - 1)
      3 
      4 factorial_rec_no_exit(10) # good night, sweet prince

RecursionError: maximum recursion depth exceeded

Аргументы по-умолчанию

Достаточно часто встречается ситуация, когда большинство предпочтительных аргументов для функции известно (например, вы знаете, что ваш алгоритм лучше всего запускать с lambda = 0.73, а n_samples = 143). В таком случае, конечно, можно написать в комментариях к своей функции, но лучше воспользоваться аргументами по-умолчанию.

In [20]:
import random

def make_random_sequence(length=200,
                         alphabet="ATGC"):
    seq_lst = [] 
    for i in range(length):
        seq_lst.append(random.choice(alphabet))
    seq = "".join(seq_lst) 
    return seq
In [21]:
make_random_sequence()
Out[21]:
'CGACTCGAGGACGAGCCTCTGAAGTTTACTCGGGACGCGGCAAACACAACGGAAGGCTATGGCAGCCTGGAGCTCTTCATTTAACTGGAGGGGAATGCTATCAATCCTAGTAAGGAGCAATGGGTATCCCGGACATTCAAATTATTAACATAACGGCCTGTTCCCTCACATTTCGACTATATTCTCTACCTATAGGGTCC'
In [15]:
make_random_sequence(length=100)
Out[15]:
'CATCGAGTTGGCCCAAACATCTCTAGCTATGCCTGATGATCCAAATTTATCCAATGTTATAAGTGACCCGCATGCATTTTATCCCTGGTACTCTCCTAGA'
In [16]:
make_random_sequence(alphabet="AUGC", length=100)
Out[16]:
'UUAGACUGGAGAGGCCAGAGGCUAGUGUUCCCCCUGUGUCUAAGAGGCCCAGUCUCACCGAGAAAGGAAACGUUCCGCGGGUAGCCACUGUUCGACCUCU'

Также часто вы просто хотите задать поведение функции по-умолчанию/наиболее частое поведение.

In [29]:
def login(username="anonymous", password=None):
    """Some action"""
    pass

# we can call function in different ways
login("root", "ujdyzysqgfhjkm") 
login("guest")
login()
# Also you can specify the name of argument
login(password="nobody@mail.com") 

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

In [22]:
def write_random_fasta(out_file_path, 
                       name="random", 
                       length=200,
                       alphabet="ATGC"):
    out_file = open(out_file_path, "w") # it is better to use with-construction here
    seq = make_random_sequence(length=length, alphabet=alphabet)
    out_file.write(">{}\n".format(name))
    out_file.write("{}\n".format(seq))
    out_file.close()
In [23]:
write_random_fasta("random.fasta")
In [20]:
write_random_fasta()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-3d278c1e259e> in <module>()
----> 1 write_random_fasta()

TypeError: write_random_fasta() missing 1 required positional argument: 'out_file_path'
In [24]:
write_random_fasta("random2.fasta", length=10)
In [22]:
def add_to_list(el, lst = []):
    lst.append(el)
    return lst
In [23]:
print (add_to_list(5, [1,2,3])) # OK
print (add_to_list(5, [])) # OK
print (add_to_list(5)) # OK
print (add_to_list(5)) # WHAT???
[1, 2, 3, 5]
[5]
[5]
[5, 5]

surprise

Дело в том, что значение по-умолчанию создавалось один раз. И вначала оно равно пустому списку. Но в третьем случае мы добавляем в этот список элемент. И после возвращения из функции изменения не пропадают. Потому на следующем вызове элемент добавляется не к пустому списку, а к списку, содержащему один элемент. И так далее. Как это лечить? - не использовать в значениях по-умолчанию изменяемые объекты (списки, словари и т.д), а использовать None

In [24]:
def add_to_list_wsmf(el, lst = None):
    if lst == None:
        lst = []
    lst.append(el)
    return lst
In [ ]:
print (add_to_list_wsmf(5, [1,2,3])) # OK
print (add_to_list_wsmf(5, [])) # OK
print (add_to_list_wsmf(5)) # OK
print (add_to_list_wsmf(5)) # Still OK

Форматирование строк

Конкатенация

Грубый, но самый простой способ форматирования, в котором мы просто склеиваем несколько строк с помощью операции сложения:

In [2]:
name = "Дмитрий"
age = 25
print("Меня зовут " + name + ". Мне " + str(age) + " лет.")
Меня зовут Дмитрий. Мне 25 лет.

%-форматирование

Ранее самый популярный способ, который перешел в Python из языка С. Передавать значения в строку можно через кортежи

In [11]:
print("Меня зовут %s. Мне %d лет." % (name, age))
Меня зовут Дмитрий. Мне 25 лет.

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

In [12]:
print("Меня зовут %(name)s. Мне %(age)d лет." % {"name": name, "age": age})
Меня зовут Дмитрий. Мне 25 лет.

Форматирование с помощью метода format()

Этот способ появился в качестве замены %-форматированию. Он также поддерживает передачу значений по позиции и по имени.

In [4]:
print("Меня зовут {}. Мне {} лет.".format(name,
                                          age))
Меня зовут Дмитрий. Мне 25 лет.

Можно явно указывать позицию аргумента, который здесь надо вставить.

In [14]:
print("Меня зовут {0}. Мне {1} лет.".format(name,
                                            age)) 
Меня зовут Дмитрий. Мне 25 лет.

Можно использовать один и тот же аргумент несколько раз

In [15]:
print("Меня зовут {0}. Точно ли меня зовут {0}... Кажется, меня все же зовут {0}".format(name)) 
Меня зовут Дмитрий. Точно ли меня зовут Дмитрий... Кажется, меня все же зовут Дмитрий

f-strings

f-строки. Форматирование, которое появилось в Python 3.6 (PEP 498). Этот способ похож на форматирование с помощью метода format(), но гибче, читабельней и быстрей.

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

In [5]:
name = "Алексей"
age = 35
In [6]:
print(f"Меня зовут {name}. Мне {age} лет.")
Меня зовут Алексей. Мне 35 лет.

Их и рассмотрим подробнее (часть из их функционала есть и в других методах, тем не менее)

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

In [19]:
from math import pi 

print(f"Значение числа pi: {pi:.02f}")
Значение числа pi: 3.14

Они поддерживают базовые арифметические операции

In [4]:
x = 10
y = 5
print(f"{x} x {y} / 2 = {x * y / 2}")
10 x 5 / 2 = 25.0

Индексацию списков:

In [5]:
planets = ["Меркурий", "Венера", "Земля", "Марс"]
print(f"Мы живем на планете {planets[2]}")
Мы живем на планете Земля

А также к элементам словаря по ключу:

In [25]:
planet_radius = {"Земля":  6378000}
planet = "Земля"
print(f"Планета {planet}. Радиус {planet_radius[planet]/1000} км.")
Планета Земля. Радиус 6378.0 км.

Заметьте, что при обращении к словарю по ключу мы можем использовать переменные тоже) Можно и строки, конечно

In [26]:
planet_radius = {"Земля":  6378000}
print(f"Планета Земля. Радиус {planet_radius['Земля']/1000} км.")
Планета Земля. Радиус 6378.0 км.

Вы можете вызывать в f-строках методы объектов:

In [7]:
replic1 = "Вот они - смертные"
replic2 = "Все, что у них есть, - это совсем немного лет в этом мире. И они проводят драгоценные годы жизни за усложнением всего, к чему прикасаются. Очаровательно."
print(f"{replic1.upper()}, - продолжал Смерть. - {replic2.upper()}")
ВОТ ОНИ - СМЕРТНЫЕ, - продолжал Смерть. - ВСЕ, ЧТО У НИХ ЕСТЬ, - ЭТО СОВСЕМ НЕМНОГО ЛЕТ В ЭТОМ МИРЕ. И ОНИ ПРОВОДЯТ ДРАГОЦЕННЫЕ ГОДЫ ЖИЗНИ ЗА УСЛОЖНЕНИЕМ ВСЕГО, К ЧЕМУ ПРИКАСАЮТСЯ. ОЧАРОВАТЕЛЬНО.

Модули

Чужие модули

Вы уже встречались с модулями в Python.

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

In [48]:
import random 
In [50]:
random.randint(0, 100) # получить случайное число от 0 до 100 включительно
Out[50]:
82

Можно импортировать только нужную нам функцию

In [54]:
from random import choice
In [55]:
choice("ATGC") # выбрать случайную букву
Out[55]:
'T'
In [56]:
import random as rnd # импортировать модуль и использовать в программе для него другое имя
In [57]:
rnd.random() # получить случайно число от 0 до 1
Out[57]:
0.2898425980229784

Cвои модули

Вы можете написать модуль и сами. На самом деле, любой файл Python может использоваться как модуль

Допустим, мы написали код и хотим его использовать как модуль

In [1]:
def factorial(n):
    acc = 1
    for i in range(2,n + 1):
        acc *= i
    return acc

def fibonacci(n):
    f0, f1 = 0, 1
    for i in range(n):
        f0, f1 = f1, f0 + f1
        # temp = f0
        # f0 = f1
        # f1 = f1 + temp
    return f0

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

In [2]:
our_code = '''
def factorial(n):
    acc = 1
    for i in range(2,n + 1):
        acc *= i
    return acc

def fibonacci(n):
    f0, f1 = 0, 1
    for i in range(n):
        f0, f1 = f1, f0 + f1
        # temp = f0
        # f0 = f1
        # f1 = f1 + temp
    return f0
'''

with open("fibfact.py", "w") as outfile:
    outfile.write(our_code)

Теперь мы можем это использовать:

In [3]:
import fibfact
In [4]:
fibfact.factorial(5)
Out[4]:
120

Можем мы и использовать from

In [5]:
from fibfact import factorial
factorial(5)
Out[5]:
120

Можем и назначить модулю имя

In [6]:
import fibfact as fct
In [7]:
fct.fibonacci(5)
Out[7]:
5

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

In [8]:
def factorial(n):
    acc = 1
    for i in range(2,n + 1):
        acc *= i
    return acc

def fibonacci(n):
    f0, f1 = 0, 1
    for i in range(n):
        f0, f1 = f1, f0 + f1
        # temp = f0
        # f0 = f1
        # f1 = f1 + temp
    return f0

print (factorial(5))
print (fibonacci(10))
120
55
In [9]:
our_code = '''
def factorial(n):
    acc = 1
    for i in range(2,n + 1):
        acc *= i
    return acc

def fibonacci(n):
    f0, f1 = 0, 1
    for i in range(n):
        f0, f1 = f1, f0 + f1
        # temp = f0
        # f0 = f1
        # f1 = f1 + temp
    return f0
    
print (factorial(5))
print (fibonacci(10))
'''

with open("fibfact.py", "w") as outfile:
    outfile.write(our_code)

Если мы меняем модуль, а где-то он загружен, мы должны его перезагрузить (можете сами найти, как это делать), либо перезагрузить notebook (на верхней панели: Kernel->Restart)

In [1]:
import fibfact
120
55

То есть при импорте модуля этот код выполняется. Это нехорошо, т.к засоряет исполнение, кроме того, может привести к ошибкам при импорте модуля, например:

In [1]:
# Полученный скрипт будет принимать два аргумента из командной строки и для первого вычислять значение факториала, 
# а для второго - соответствующее число Фибоначчи
our_code = '''
import sys

def factorial(n):
    acc = 1
    for i in range(2,n + 1):
        acc *= i
    return acc

def fibonacci(n):
    f0, f1 = 0, 1
    for i in range(n):
        f0, f1 = f1, f0 + f1
        # temp = f0
        # f0 = f1
        # f1 = f1 + temp
    return f0
    
print (factorial(int(sys.argv[1])))
print (fibonacci(int(sys.argv[2])))
'''

with open("fibfact.py", "w") as outfile:
    outfile.write(our_code)

Надо перезагрузить notebook

Получаем ошибку, т.к sys.argv берется из jupyter-notebook. В общем сложно и неправильно все.

In [1]:
import fibfact
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-5bda95184b64> in <module>()
----> 1 import fibfact

~/Desktop/lec3/fibfact.py in <module>()
     17     return f0
     18 
---> 19 print (factorial(int(sys.argv[1])))
     20 print (fibonacci(int(sys.argv[2])))

ValueError: invalid literal for int() with base 10: '-f'
In [6]:
import sys
print (f"{sys.argv[1]}, {sys.argv[2]}") # вот эти аргументы он использовал..
-f, /Users/dmitrypenzar/Library/Jupyter/runtime/kernel-ac2e5513-19a1-40d5-a912-0733251c5cb2.json

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

Если наш файл запустили, как скрипт, то его имя - "__main__".

Если его использовали как модуль, то его имя - не "__main__", этого достаточно:)

In [ ]:
# Как выглядит код с подсветкой, запускать это не надо
import sys

def factorial(n):
    acc = 1
    for i in range(2,n + 1):
        acc *= i
    return acc

def fibonacci(n):
    f0, f1 = 0, 1
    for i in range(n):
        f0, f1 = f1, f0 + f1
        # temp = f0
        # f0 = f1
        # f1 = f1 + temp
    return f0
    
print(f"My name is {__name__}")
if __name__ == "__main__":
    print (factorial(sys.argv[1]))
    print (fibonacci(sys.argv[2]))
In [7]:
# Полученный файл при вызове как скрипт будет принимать два аргумента из командной строки и для первого вычислять значение факториала, 
# а для второго - соответствующее число Фибоначчи. Кроме того, он напечатает свое имя.
# При импортировании в качестве модуля - только напечатает свое имя
our_code = '''
import sys

def factorial(n):
    acc = 1
    for i in range(2,n + 1):
        acc *= i
    return acc

def fibonacci(n):
    f0, f1 = 0, 1
    for i in range(n):
        f0, f1 = f1, f0 + f1
        # temp = f0
        # f0 = f1
        # f1 = f1 + temp
    return f0
    
print(f"My name is {__name__}")
if __name__ == "__main__":
    print (factorial(int(sys.argv[1])))
    print (fibonacci(int(sys.argv[2])))
'''

with open("fibfact.py", "w") as outfile:
    outfile.write(our_code)

Надо перезагрузить notebook

Для импорта в качестве модуля работает

In [1]:
import fibfact
My name is fibfact

Работает и при запуске как самостоятельный скрипт

In [2]:
! python fibfact.py 5 5
My name is __main__
120
5

Исключения

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

В Python существует специальный механизм для отлова исключений - try: ... except: ...

In [57]:
1 / 0 
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-57-e542b24897df> in <module>()
----> 1 1 / 0

ZeroDivisionError: division by zero
In [58]:
try:
    1 / 0
except ZeroDivisionError:
    print ("HI")
HI

Порой нам необходимо отловить несколько исключений. В этом случае просто пишем несколько блоков except

In [1]:
int("1.1")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-8c4c1f183c39> in <module>()
----> 1 int("1.1")

ValueError: invalid literal for int() with base 10: '1.1'
In [3]:
a = input()
b = input()
try:
    a = int(a) # possible error
    b = int(b) # possible error
    print (a / b)
except ValueError:
    print ("a and b must be integers")
except ZeroDivisionError:
    print ("b must be different from 0")
10
0
b must be different from 0

Дополнительно про исключения

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

подтипы исключений

Так не будет работать так, как хотим

In [63]:
a = input()
b = input()
try:
    a = int(a) # possible error
    b = int(b) # possible error
    print (a / b)
    print (a[0])
except Exception: # Exception usually is the most general Exception, it's highly recommended not to use more general exceptions
    print ("Other exception")
except ValueError:
    print ("a and b must be integers")
except ZeroDivisionError:
    print ("b must be different from 0")
10
0
Other exception

А это уже правильная последовательность

In [65]:
a = input()
b = input()
try:
    a = int(a) # possible error
    b = int(b) # possible error
    print (a / b)
    print (a[0])
except ValueError:
    print ("a and b must be integers")
except ZeroDivisionError:
    print ("b must be different from 0")
except Exception: # Exception usually is the most general Exception, it's highly recommended not to use more general exceptions
    print ("Other exception")
10
5
2.0
Other exception

А что делать, если мы хотим что-то сделать только если исключения не произошло? Так не работает:

In [66]:
a = input()
b = input()
try:
    a = int(a) # possible error
    b = int(b) # possible error
    result = a / b
except ValueError:
    print ("a and b must be integers")
except ZeroDivisionError:
    print ("b must be different from 0")
except Exception: # Exception usually is the most general Exception, it's highly recommended not to use more general exceptions
    print ("Other exception")

print (result)
10
0
b must be different from 0
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-66-735ccb86ec50> in <module>()
     12     print ("Other exception")
     13 
---> 14 print (result)

NameError: name 'result' is not defined

Существуют еще ветвь else в try except, которая выполняется только если исключения не произошло

In [67]:
a = input()
b = input()
try:
    a = int(a) # possible error
    b = int(b) # possible error
    result = a / b
except ValueError:
    print ("a and b must be integers")
except ZeroDivisionError:
    print ("b must be different from 0")
except Exception: # Exception usually is the most general Exception, it's highly recommended not to use more general exceptions
    print ("Other exception")
else:
    print (result)
10
0
b must be different from 0

И последним словом, которое можно использовать в try.. except является finally - оно говорит совершать что-то в любом случае, даже если мы не обработали какую-то ошибку и прекращаем работу

In [72]:
a = input()
b = input()
try:
    a = int(a) # possible error
    b = int(b) # possible error
    result = a / b
    print(result[0])
except ValueError:
    print ("a and b must be integers")
except ZeroDivisionError:
    print ("b must be different from 0")
else:
    print (result)
finally:
    print ("Hi")
10
5
Hi
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-72-4135f06ba8b4> in <module>()
      5     b = int(b) # possible error
      6     result = a / b
----> 7     print(result[0])
      8 except ValueError:
      9     print ("a and b must be integers")

TypeError: 'float' object is not subscriptable

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

In [75]:
a = input()
b = input()
try:
    fl = open("infile.txt", "w")
    a = int(a) # possible error
    b = int(b) # possible error
    result = a / b
    fl.write(f"{result}\n")
except ValueError:
    print ("a and b must be integers")
except ZeroDivisionError:
    print ("b must be different from 0")
else:
    print (result)
finally:
    print("Closing file")
    fl.close()
10
0
b must be different from 0
Closing file