Кафедра дискретной математики МФТИ

Курс математической статистики

Никита Волков

На основе http://www.inp.nsk.su/~grozin/python/

Кортежи

Кортежи (tuples) очень похожи на списки, но являются неизменяемыми. Как мы видели, использование изменяемых объектов может приводить к неприятным сюрпризам.

Кортежи пишутся в круглых скобках. Если элементов $>1$ или 0, это не вызывает проблем. Но как записать кортеж с одним элементом? Конструкция (x) абсолютно легальна в любом месте любого выражения, и означает просто x. Чтобы избежать неоднозначности, кортеж с одним элементом x записывается в виде (x,).


In [1]:
(1, 2, 3)


Out[1]:
(1, 2, 3)

In [2]:
()


Out[2]:
()

In [3]:
(1,)


Out[3]:
(1,)

Скобки ставить не обязательно, если кортеж - единственная вещь в правой части присваивания.


In [4]:
t = 1, 2, 3
t


Out[4]:
(1, 2, 3)

Работать с кортежами можно так же, как со списками. Нельзя только изменять их.


In [5]:
len(t)


Out[5]:
3

In [6]:
t[1]


Out[6]:
2

In [7]:
u = 4, 5
t + u


Out[7]:
(1, 2, 3, 4, 5)

In [8]:
2 * u


Out[8]:
(4, 5, 4, 5)

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


In [9]:
x, y = 1, 2

In [10]:
x


Out[10]:
1

In [11]:
y


Out[11]:
2

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


In [12]:
x, y = y, x

In [13]:
x


Out[13]:
2

In [14]:
y


Out[14]:
1

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

Множества

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


In [15]:
s = {0, 1, 0, 5, 5, 1, 0}
s


Out[15]:
{0, 1, 5}

Принадлежит ли элемент множеству?


In [16]:
1 in s, 2 in s, 1 not in s


Out[16]:
(True, False, False)

Множество можно получить из списка, или строки, или любого объекта, который можно использовать в for цикле (итерабельного).


In [17]:
l = [0, 1, 0, 5, 5, 1, 0]
set(l)


Out[17]:
{0, 1, 5}

In [18]:
set('абба')


Out[18]:
{'а', 'б'}

Как записать пустое множество? Только так.


In [19]:
set()


Out[19]:
set()

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


In [20]:
{}


Out[20]:
{}

Работать с множествами можно как со списками.


In [21]:
len(s)


Out[21]:
3

In [22]:
for x in s:
    print(x)


0
1
5

Это генератор множества (set comprehension).


In [23]:
{i for i in range(5)}


Out[23]:
{0, 1, 2, 3, 4}

Объединение множеств.


In [24]:
s2 = s | {2, 5}
s2


Out[24]:
{0, 1, 2, 5}

Проверка того, является ли одно множество подмножеством другого.


In [25]:
s < s2, s > s2, s <= s2, s >= s2


Out[25]:
(True, False, True, False)

Пересечение.


In [26]:
s2 & {1, 2, 3}


Out[26]:
{1, 2}

Разность и симметричная разность.


In [27]:
s2 - {1,3,5}


Out[27]:
{0, 2}

In [28]:
s2 ^ {1,3,5}


Out[28]:
{0, 2, 3}

Множества (как и списки) являются изменяемыми объектами. Добавление элемента в множество и исключение из него.


In [29]:
s2.add(4)
s2


Out[29]:
{0, 1, 2, 4, 5}

In [30]:
s2.remove(1)
s2


Out[30]:
{0, 2, 4, 5}

Как и в случае +=, можно скомбинировать теоретико-множественную операцию с присваиванием.


In [31]:
s2 |= {1, 2}
s2


Out[31]:
{0, 1, 2, 4, 5}

Приведенные выше операции можно записывать и в другом стиле


In [32]:
x = set([1, 4, 2, 4, 2, 1, 3, 4])
print(x)

x.add(5)  # добавление элемента
print(x)

x.pop()  # удаление элемента
print(x)

print(x.intersection(set([2, 4, 6, 8])))  # Пересечение
print(x.difference(set([2, 4, 6, 8])))  # Разность
print(x.union(set([2, 4, 6, 8])))  # Объединение
print(x.symmetric_difference(set([2, 4, 6, 8])))  # Симметрическая разность

print(x.issubset(set([2, 4, 6, 8])))  # Является ли подмножеством
print(x.issubset(set(list(range(10)))))

print(x.issuperset(set([2, 4, 6, 8])))  # Является ли надмножеством
print(x.issuperset(set([2, 4])))


{1, 2, 3, 4}
{1, 2, 3, 4, 5}
{2, 3, 4, 5}
{2, 4}
{3, 5}
{2, 3, 4, 5, 6, 8}
{3, 5, 6, 8}
False
True
False
True

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

Словари

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


In [33]:
d = {'one': 1, 'two': 2, 'three': 3}
d


Out[33]:
{'one': 1, 'three': 3, 'two': 2}

Можно узнать значение, соответствующее некоторому ключу. Словари реализованы как хэш-таблицы, так что поиск даже в больших словарях очень эффективен. В языках низкого уровня (например, C) для построения хэш-таблиц требуется использовать внешние библиотеки и писать заметное количество кода. В скриптовых языках (perl, python, php) они уже встроены в язык, и использовать их очень легко.


In [34]:
d['two']


Out[34]:
2

In [35]:
d['four']


---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-35-a0944cd0c15b> in <module>()
----> 1 d['four']

KeyError: 'four'

Можно проверить, есть ли в словаре данный ключ.


In [36]:
'one' in d, 'four' in d


Out[36]:
(True, False)

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


In [37]:
d['one'] =- 1
d


Out[37]:
{'one': -1, 'three': 3, 'two': 2}

In [38]:
d['four'] = 4
d


Out[38]:
{'four': 4, 'one': -1, 'three': 3, 'two': 2}

Длина - число ключей в словаре.


In [39]:
len(d)


Out[39]:
4

Можно удалить ключ из словаря.


In [40]:
del d['two']
d


Out[40]:
{'four': 4, 'one': -1, 'three': 3}

Метод get, если он будет вызван с отсутствующим ключом, не приводит к ошибке, а возвращает специальный объект None. Он используется всегда, когда необходимо указать, что объект отсутствует (в какой-то мере он аналогичен null в C). Если передать методу get второй аргумент - значение по умолчанию, то будет возвращаться это значение, а не None.


In [41]:
d.get('one'), d.get('five')


Out[41]:
(-1, None)

In [42]:
d.get('one', 0), d.get('five', 0)


Out[42]:
(-1, 0)

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


In [43]:
d = {}
d


Out[43]:
{}

In [44]:
d['zero'] = 0
d


Out[44]:
{'zero': 0}

In [45]:
d['one'] = 1
d


Out[45]:
{'one': 1, 'zero': 0}

А это генератор словаря (dictionary comprehension).


In [46]:
d = {i: i ** 2 for i in range(5)}
d


Out[46]:
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

Ключами могут быть любые неизменяемые объекты, например, целые числа, строки, кортежи.


In [47]:
d = {}
d[0, 0] = 1
d[0, 1] = 0
d[1, 0] = 0
d[1, 1] = -1
d


Out[47]:
{(0, 0): 1, (0, 1): 0, (1, 0): 0, (1, 1): -1}

In [48]:
d[0, 0] + d[1, 1]


Out[48]:
0

Словари, подобно спискам, можно использовать в for циклах. Перебираются имеющиеся в словаре ключи (в каком-то непредсказуемом порядке).


In [49]:
d = {'one': 1, 'two': 2, 'three': 3}
for x in d:
    print(x, '  ', d[x])


one    1
two    2
three    3

Метод keys возвращает список ключей, метод values - список соответствующих значений (в том же порядке), а метод items - список пар (ключ,значение). Точнее говоря, это не списки, а некоторые объекты, которые можно использовать в for циклах или превратить в списки функцией list. Если хочется написать цикл по упорядоченному списку ключей, то можно использовать sorted(d.keys)).


In [50]:
d.keys(), d.values(), d.items()


Out[50]:
(dict_keys(['one', 'two', 'three']),
 dict_values([1, 2, 3]),
 dict_items([('one', 1), ('two', 2), ('three', 3)]))

In [51]:
for x in sorted(d.keys()):
    print(x, '  ', d[x])


one    1
three    3
two    2

In [52]:
for x, y in d.items():
    print(x, '  ', y)


one    1
two    2
three    3

In [53]:
del x, y

Что есть истина? И что есть ложь? Подойдём к этому философскому вопросу экспериментально.


In [54]:
bool(False), bool(True)


Out[54]:
(False, True)

In [55]:
bool(None)


Out[55]:
False

In [56]:
bool(0), bool(123)


Out[56]:
(False, True)

In [57]:
bool(''), bool(' ')


Out[57]:
(False, True)

In [58]:
bool([]), bool([0])


Out[58]:
(False, True)

In [59]:
bool(set()), bool({0})


Out[59]:
(False, True)

In [60]:
bool({}), bool({0: 0})


Out[60]:
(False, True)

На выражения, стоящие в булевых позициях (после if, elif и while), неявно напускается функция bool. Некоторые объекты интерпретируются как False: число 0, пустая строка, пустой список, пустое множество, пустой словарь, None и некоторые другие. Все остальные объекты интерпретируются как True. В операторах if или while очень часто используется список, словарь или что-нибудь подобное, что означает делай что-то если этот список (словарь и т.д.) не пуст.

Заметим, что число с плавающей точкой 0.0 тоже интерпретируется как False. Это использовать категорически не рекомендуется: вычисления с плавающей точкой всегда приближённые, и неизвестно, получите Вы 0.0 или 1.234E-12. Лучше напишите if abs(x)<epsilon:.

Функции

Это простейшая в мире функция. Она не имеет параметров, ничего не делает и ничего не возвращает. Оператор pass означает "ничего не делай"; он используется там, где синтаксически необходим оператор, а делать ничено не нужно (после if или elif, после def и т.д.).


In [61]:
def f():
    pass

In [62]:
f


Out[62]:
<function __main__.f>

In [63]:
pass

In [64]:
type(f)


Out[64]:
function

In [65]:
r = f()
print(r)


None

Эта функция более полезна: она имеет параметр и что-то возвращает.


In [66]:
def f(x):
    return x + 1

In [67]:
f(1), f(1.0)


Out[67]:
(2, 2.0)

In [68]:
f('abc')


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-68-410386031a44> in <module>()
----> 1 f('abc')

<ipython-input-66-58d91390ace5> in f(x)
      1 def f(x):
----> 2     return x + 1

TypeError: Can't convert 'int' object to str implicitly

Если у функции много параметров, то возникает желание вызывать её попроще в наиболее часто встречающихся случаях. Для этого в операторе def можно задать значения некоторых параметров по умолчанию (они должны размещаться в конце списка параметров). При вызове необходимо указать все обязательные параметры (у которых нет значений по умолчанию), а необязательные можно и не указывать. Если при вызове указывать параметры в виде имя=значение, то это можно делать в любом порядке. Это гораздо удобнее, чем вспоминать, является данный параметр восьмым или девятым при вызове какой-нибудь сложной функции. Обратите внимание, что в конструкции имя=значение не ставятся пробелы между символом =.


In [69]:
def f(x, a=0, b='b'):
    print(x, '  ', a, '  ', b)

In [70]:
f(1.0)


1.0    0    b

In [71]:
f(1.0, 1)


1.0    1    b

In [72]:
f(1.0, b='a')


1.0    0    a

In [73]:
f(1.0, b='a', a=2)


1.0    2    a

In [74]:
f(a=2, x=2.0)


2.0    2    b

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


In [75]:
a = 1

In [76]:
def f():
    a = 2
    return a

In [77]:
f()


Out[77]:
2

In [78]:
a


Out[78]:
1

Если в функции нужно использовать какие-нибудь глобальные переменные, их нужно описать как global.


In [79]:
def f():
    global a
    a = 2
    return a

In [80]:
f()


Out[80]:
2

In [81]:
a


Out[81]:
2

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

Если функции передаётся в качестве аргумента какой-нибудь изменяемый объект, и функция его изменяет, то это изменение будет видно снаружи после этого вызова. Мы уже обсуждали эту ситуацию, когда две переменные (в данном случае глобальная переменная и параметр функции) указывают на один и тот же изменяемый объект объект.


In [82]:
def f(x, l):
    l.append(x)
    return l

In [83]:
l = [1, 2, 3]
f(0, l)


Out[83]:
[1, 2, 3, 0]

In [84]:
l


Out[84]:
[1, 2, 3, 0]

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


In [85]:
def f(x, l=[]):
    l.append(x)
    return l

In [86]:
f(0)


Out[86]:
[0]

In [87]:
f(1)


Out[87]:
[0, 1]

In [88]:
f(2)


Out[88]:
[0, 1, 2]

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


In [89]:
def f(x, l=None):
    if l is None:
        l = []
    l.append(x)
    return l

In [90]:
f(0)


Out[90]:
[0]

In [91]:
f(1)


Out[91]:
[1]

In [92]:
f(2, [0, 1])


Out[92]:
[0, 1, 2]

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


In [93]:
def f(x, *l):
    print(x, '  ', l)

In [94]:
f(0)


0    ()

In [95]:
f(0, 1)


0    (1,)

In [96]:
f(0, 1, 2)


0    (1, 2)

In [97]:
f(0, 1, 2, 3)


0    (1, 2, 3)

Звёздочку можно использовать и при вызове функции. Можно заранее построить список (или кортеж) аргументов, а потом вызвать функцию с этими аргументами.


In [98]:
l=[1, 2]
c=('a', 'b')
f(*l, 0, *c)


1    (2, 0, 'a', 'b')

Такую распаковку из списков и кортежей можно использовать не только при вызове функции, но и при построении списка или кортежа.


In [99]:
(*l, 0, *c)


Out[99]:
(1, 2, 0, 'a', 'b')

In [100]:
[*l, 0, *c]


Out[100]:
[1, 2, 0, 'a', 'b']

In [101]:
[*l, 3]


Out[101]:
[1, 2, 3]

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


In [102]:
def f(x, y, **d):
    print(x, '  ', y, '  ', d)

In [103]:
f(0, 1, foo=2, bar=3)


0    1    {'foo': 2, 'bar': 3}

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


In [104]:
d={'foo': 2, 'bar': 3}
f(0, 1, **d)


0    1    {'foo': 2, 'bar': 3}

In [105]:
d['x'] = 0
d['y'] = 1
f(**d)


0    1    {'foo': 2, 'bar': 3}

Вот любопытный способ построить словарь с ключами-строками.


In [106]:
def f(**d):
    return d

In [107]:
f(x=0, y=1, z=2)


Out[107]:
{'x': 0, 'y': 1, 'z': 2}

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


In [108]:
d={0: 'a', 1: 'b'}
{**d, 2: 'c'}


Out[108]:
{0: 'a', 1: 'b', 2: 'c'}

Вот простой способ объединить два словаря.


In [109]:
d1 = {0: 'a', 1: 'b'}
d2 = {2: 'c', 3: 'd'}
{**d1, **d2}


Out[109]:
{0: 'a', 1: 'b', 2: 'c', 3: 'd'}

Если один и тот же ключ встречается несколько раз, следующее значение затирает предыдущее.


In [110]:
d2 = {1: 'B', 2: 'C'}
{**d1, 3: 'D', **d2, 3: 'd'}


Out[110]:
{0: 'a', 1: 'B', 2: 'C', 3: 'd'}

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


In [111]:
def f(x, y, *l, **d):
    print(x, '  ', y, '  ', l, '  ', d)

In [112]:
f(0, 1, 2, 3, foo=4, bar=5)


0    1    (2, 3)    {'foo': 4, 'bar': 5}

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


In [113]:
def f0(x):
    return x + 2

In [114]:
def f1(x):
    return 2 * x

In [115]:
l = [f0, f1]
l


Out[115]:
[<function __main__.f0>, <function __main__.f1>]

In [116]:
x = 2.0
n = 1
l[n](x)


Out[116]:
4.0

Если Вы пишете функцию не для того, чтобы один раз её вызвать и навсегда забыть, то нужна документация, объясняющая, что эта функция делает. Для этого сразу после строчки def пишется строка. Она называется док-строкой, и сохраняется при трансляции исходного текста на питоне в байт-код (в отличие от комментариев, которые при этом отбрасываются). Обычно эта строка заключается в тройные кавычки и занимает несколько строчек. Док-строка доступна как атрибут __doc__ функции, и используется функцией help. Вот пример культурно написанной функции, вычисляющей $n$-е число Фибоначчи.

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


In [117]:
def fib(n):
    '''вычисляет n-е число Фибоначчи'''
    
    assert type(n) is int and n>0
    
    if n <= 2:
        return 1
    
    x, y = 1, 1
    for i in range(n - 2):
        x, y = y, x + y
    
    return y

In [118]:
fib.__doc__


Out[118]:
'вычисляет n-е число Фибоначчи'

In [119]:
help(fib)


Help on function fib in module __main__:

fib(n)
    вычисляет n-е число Фибоначчи

В jupyter-ноутбуке к документации можно обращаться более удобным способом


In [120]:
fib?

In [121]:
[fib(n) for n in range(1, 10)]


Out[121]:
[1, 1, 2, 3, 5, 8, 13, 21, 34]

In [122]:
fib(-1)


---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-122-b876e14fb318> in <module>()
----> 1 fib(-1)

<ipython-input-117-ae184b40a1b3> in fib(n)
      2     '''вычисляет n-е число Фибоначчи'''
      3 
----> 4     assert type(n) is int and n>0
      5 
      6     if n <= 2:

AssertionError: 

In [123]:
fib(2.0)


---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-123-363564e722ae> in <module>()
----> 1 fib(2.0)

<ipython-input-117-ae184b40a1b3> in fib(n)
      2     '''вычисляет n-е число Фибоначчи'''
      3 
----> 4     assert type(n) is int and n>0
      5 
      6     if n <= 2:

AssertionError: 

Некоторые полезные функции

zip скрещивает два массива одной длины


In [124]:
x = zip(range(5), range(0, 10, 2))
print(list(x))


[(0, 0), (1, 2), (2, 4), (3, 6), (4, 8)]

map применяет функию к каждому элементу массива


In [125]:
x = map(lambda tmp: tmp ** 2, range(5))
print(list(x))


[0, 1, 4, 9, 16]

sorted --- сортировка


In [126]:
x = list(zip([7, 3, 4, 4, 5, 3, 9], ['a', 'n', 'n', 'a', 'k', 'n', 'a']))
# сначала сортировка по букве по алфавиту, потом сортировка по убыванию по числу 
x = sorted(x, key=lambda element: (element[1], -element[0]))
print(list(x))


[(9, 'a'), (7, 'a'), (4, 'a'), (5, 'k'), (4, 'n'), (3, 'n'), (3, 'n')]