Скачать ipynb
04_python_2

Python для анализа данных

1. Кортежи

Кортежи (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]:
(1,)
Out[4]:
(1,)

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

In [5]:
t = 1, 2, 3
t
Out[5]:
(1, 2, 3)

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

In [6]:
len(t)
Out[6]:
3
In [7]:
t[1]
Out[7]:
2
In [8]:
u = 4, 5
t + u
Out[8]:
(1, 2, 3, 4, 5)
In [9]:
2 * u
Out[9]:
(4, 5, 4, 5)

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

In [10]:
x, y = 1, 2
In [11]:
x
Out[11]:
1
In [12]:
y
Out[12]:
2

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

In [13]:
x, y = y, x
In [14]:
x
Out[14]:
2
In [15]:
y
Out[15]:
1

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

2. Множества

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

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

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

In [17]:
1 in s, 2 in s, 1 not in s
Out[17]:
(True, False, False)

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

In [18]:
l = [0, 1, 0, 5, 5, 1, 0]
set(l)
Out[18]:
{0, 1, 5}
In [19]:
set('абба')
Out[19]:
{'а', 'б'}

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

In [20]:
set()
Out[20]:
set()

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

In [21]:
{}
Out[21]:
{}

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

In [22]:
len(s)
Out[22]:
3
In [23]:
for x in s:
    print(x)
0
1
5

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

In [24]:
{i for i in range(5)}
Out[24]:
{0, 1, 2, 3, 4}

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

In [25]:
s2 = s | {2, 5}
s2
Out[25]:
{0, 1, 2, 5}

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

In [26]:
s < s2, s > s2, s <= s2, s >= s2
Out[26]:
(True, False, True, False)

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

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

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

In [28]:
s2 - {1,3,5}
Out[28]:
{0, 2}
In [29]:
s2 ^ {1,3,5}
Out[29]:
{0, 2, 3}

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

In [30]:
s2.add(4)
s2
Out[30]:
{0, 1, 2, 4, 5}
In [31]:
s2.remove(1)
s2
Out[31]:
{0, 2, 4, 5}

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

In [32]:
s2 |= {1, 2}
s2
Out[32]:
{0, 1, 2, 4, 5}

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

In [33]:
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. Операции над такими множествами подобны обычным, только невозможно изменять их, а всего лишь добавлять и исключать элементы.

3. Словари

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

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

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

In [35]:
d['two']
Out[35]:
2
In [36]:
d['four']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-36-99f82dfe25e5> in <module>
----> 1 d['four']

KeyError: 'four'

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

In [38]:
'one' in d, 'four' in d
Out[38]:
(True, False)

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

In [39]:
d['one'] =- 1
d
Out[39]:
{'one': -1, 'two': 2, 'three': 3}
In [40]:
d['four'] = 4
d
Out[40]:
{'one': -1, 'two': 2, 'three': 3, 'four': 4}

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

In [41]:
len(d)
Out[41]:
4

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

In [42]:
del d['two']
d
Out[42]:
{'one': -1, 'three': 3, 'four': 4}

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

In [43]:
d.get('one'), d.get('five')
Out[43]:
(-1, None)
In [44]:
d.get('one', 0), d.get('five', 0)
Out[44]:
(-1, 0)

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

In [45]:
d = {}
d
Out[45]:
{}
In [46]:
d['zero'] = 0
d
Out[46]:
{'zero': 0}
In [47]:
d['one'] = 1
d
Out[47]:
{'zero': 0, 'one': 1}

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

In [48]:
d = {i: i ** 2 for i in range(5)}
d
Out[48]:
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

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

In [49]:
d = {}
d[0, 0] = 1
d[0, 1] = 0
d[1, 0] = 0
d[1, 1] = -1
d
Out[49]:
{(0, 0): 1, (0, 1): 0, (1, 0): 0, (1, 1): -1}
In [50]:
d[0, 0] + d[1, 1]
Out[50]:
0

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

In [51]:
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 [52]:
d.keys(), d.values(), d.items()
Out[52]:
(dict_keys(['one', 'two', 'three']),
 dict_values([1, 2, 3]),
 dict_items([('one', 1), ('two', 2), ('three', 3)]))
In [53]:
for x in sorted(d.keys()):
    print(x, '  ', d[x])
one    1
three    3
two    2
In [54]:
for x, y in d.items():
    print(x, '  ', y)
one    1
two    2
three    3
In [55]:
del x, y

Также иногда пригождается использование Лямбда-функции в сортировке. Например, когда имеется список кастомных объектов, сортировать которые нужно на основе значений одного из полей. В таком случае можно передать параметр key в sort или sorted. Он и будет являться функцией. Функция применяется ко всем элементам объекта, а возвращаемое значение — то, на основе чего выполнится сортировка. Рассмотрим пример.

In [56]:
class Employee:
    def __init__(self, name, age):
        self.name = name
        self.age = age

Alex = Employee('Alex', 20)
Amanda = Employee('Amanda', 30)
David = Employee('David', 15)
L = [Alex, Amanda, David]

# Лямбда-выражение было использовано в качестве параметра key 
# вместо отдельного ее определения и затем передачи в функцию sort.
print([item.name for item in L])
L.sort(key=lambda x: x.age) 
['Alex', 'Amanda', 'David']

len()— посчитает длину объекта. Если указать len в виде параметра key, то сортировка будет выполнена по длине.

In [57]:
l1 = ['blue', 'green', 'red', 'orange']
print(sorted(l1, key=len))
['red', 'blue', 'green', 'orange']

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

In [58]:
bool(False), bool(True)
Out[58]:
(False, True)
In [59]:
bool(None)
Out[59]:
False
In [60]:
bool(0), bool(123)
Out[60]:
(False, True)
In [61]:
bool(''), bool(' ')
Out[61]:
(False, True)
In [62]:
bool([]), bool([0])
Out[62]:
(False, True)
In [63]:
bool(set()), bool({0})
Out[63]:
(False, True)
In [64]:
bool({}), bool({0: 0})
Out[64]:
(False, True)
In [65]:
bool(0.0)
Out[65]:
False

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

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

4. Функции

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

In [66]:
def f():
    pass
In [67]:
f
Out[67]:
<function __main__.f()>
In [68]:
pass
In [69]:
type(f)
Out[69]:
function
In [70]:
r = f()
print(r)
None

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

In [71]:
def f(x):
    return x + 1
In [72]:
f(1), f(1.0)
Out[72]:
(2, 2.0)
In [73]:
f('abc')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-73-eb74912c9d38> in <module>
----> 1 f('abc')

<ipython-input-71-b2c2e2352aee> in f(x)
      1 def f(x):
----> 2     return x + 1

TypeError: can only concatenate str (not "int") to str

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

In [74]:
def f(x, a=0, b='b'):
    print(x, '  ', a, '  ', b)
In [75]:
f(1.0)
1.0    0    b
In [76]:
f(1.0, 1)
1.0    1    b
In [77]:
f(1.0, b='a')
1.0    0    a
In [78]:
f(1.0, b='a', a=2)
1.0    2    a
In [79]:
f(a=2, x=2.0)
2.0    2    b

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

In [80]:
a = 1
In [81]:
def f():
    a = 2
    return a
In [82]:
f()
Out[82]:
2
In [83]:
a
Out[83]:
1

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

In [84]:
def f():
    global a
    a = 2
    return a
In [85]:
f()
Out[85]:
2
In [86]:
a
Out[86]:
2

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

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

In [87]:
def f(x, l):
    l.append(x)
    return l
In [88]:
l = [1, 2, 3]
f(0, l)
Out[88]:
[1, 2, 3, 0]
In [89]:
l
Out[89]:
[1, 2, 3, 0]

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

In [90]:
def f(x, l=[]):
    l.append(x)
    return l
In [91]:
f(0)
Out[91]:
[0]
In [92]:
f(1)
Out[92]:
[0, 1]
In [93]:
f(2)
Out[93]:
[0, 1, 2]

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

In [94]:
def f(x, l=None):
    if l is None:
        l = []
    l.append(x)
    return l
In [95]:
f(0)
Out[95]:
[0]
In [96]:
f(1)
Out[96]:
[1]
In [97]:
f(2, [0, 1])
Out[97]:
[0, 1, 2]

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

In [98]:
def f(x, *l):
    print(x, '  ', l)
In [99]:
f(0)
0    ()
In [100]:
f(0, 1)
0    (1,)
In [101]:
f(0, 1, 2)
0    (1, 2)
In [102]:
f(0, 1, 2, 3)
0    (1, 2, 3)

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

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

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

In [104]:
(*l, 0, *c)
Out[104]:
(1, 2, 0, 'a', 'b')
In [105]:
[*l, 0, *c]
Out[105]:
[1, 2, 0, 'a', 'b']
In [106]:
[*l, 3]
Out[106]:
[1, 2, 3]

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

In [107]:
def f(x, y, **d):
    print(x, '  ', y, '  ', d)
In [108]:
f(0, 1, foo=2, bar=3)
0    1    {'foo': 2, 'bar': 3}

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

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

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

In [111]:
def f(**d):
    return d
In [112]:
f(x=0, y=1, z=2)
Out[112]:
{'x': 0, 'y': 1, 'z': 2}

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

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

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

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

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

In [115]:
d2 = {1: 'B', 2: 'C'}
{**d1, 3: 'D', **d2, 3: 'd'}
Out[115]:
{0: 'a', 1: 'B', 3: 'd', 2: 'C'}

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

In [116]:
def f(x, y, *l, **d):
    print(x, '  ', y, '  ', l, '  ', d)
In [117]:
f(0, 1, 2, 3, foo=4, bar=5)
0    1    (2, 3)    {'foo': 4, 'bar': 5}

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

In [118]:
def f0(x):
    return x + 2
In [119]:
def f1(x):
    return 2 * x
In [120]:
l = [f0, f1]
l
Out[120]:
[<function __main__.f0(x)>, <function __main__.f1(x)>]
In [121]:
x = 2.0
n = 1
l[n](x)
Out[121]:
4.0

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

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

In [122]:
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 [123]:
fib.__doc__
Out[123]:
'вычисляет n-е число Фибоначчи'
In [124]:
help(fib)
Help on function fib in module __main__:

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

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

In [125]:
fib?
In [126]:
[fib(n) for n in range(1, 10)]
Out[126]:
[1, 1, 2, 3, 5, 8, 13, 21, 34]
In [127]:
fib(-1)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-127-673c4d72cb80> in <module>
----> 1 fib(-1)

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

AssertionError: 
In [128]:
fib(2.0)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-128-1676211dbb8d> in <module>
----> 1 fib(2.0)

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

AssertionError: 

Указание типов данных в аргументах функции

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

In [129]:
def sum(a, b):
    return a + b

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

Вызов этой функции работает при поставке значений int и других значений, таких как строки, списки и кортежи:

In [130]:
sum(3, 5)                                                         
Out[130]:
8
In [131]:
sum([1, 0, 5], ['java', 'c++'])
Out[131]:
[1, 0, 5, 'java', 'c++']
In [132]:
sum('2', '3') 
Out[132]:
'23'

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

Чтобы указать, что мы хотим разрешить только типы int, мы можем изменить определение функции следующим образом

In [133]:
def sum(a: int, b: int):
    return a + b

Указание типа данных возврата функции

Можно также указать возвращаемое значение вызова функции

In [134]:
def sum(number1, number2) -> int:
    return number1 + number1

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

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

In [135]:
x = zip(range(5), range(0, 10, 2))
print(list(x))
[(0, 0), (1, 2), (2, 4), (3, 6), (4, 8)]

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

In [136]:
x = map(fib, range(1, 10))
print(list(x))
[1, 1, 2, 3, 5, 8, 13, 21, 34]

Функция sorted выполняет сортировку данныз

In [137]:
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')]

6. Некоторые полезные классы

Класс defaultdict

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

Приведем пример использования. Используя функцию list в качестве функции default_factory, легко сгруппировать последовательность кортежей ключ-значение в словарь списков:

In [138]:
from collections import defaultdict

s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list)
for k, v in s:
    d[k].append(v)

sorted(d.items())
Out[138]:
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

Класс Counter

Класс collections.Counter предназначен для удобных и быстрых подсчетов количества появлений неизменяемых элементов в последовательностях. Класс Counter модуля collections — это подкласс словаря dict для подсчета хеш-объектов (неизменяемых, таких как строки, числа, кортежи и т.д.). Это коллекция, в которой элементы хранятся в виде словарных ключей, а их счетчики хранятся в виде значений словаря.

In [139]:
from collections import Counter

cnt = Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])
dict(cnt)
Out[139]:
{'red': 2, 'blue': 3, 'green': 1}

DataClass

Конструкции dataclass доступны в Python 3.7 и старше. Их можно использовать как контейнеры данных и не только. Конструкции dataclass позволяют писать шаблонный код и упрощают процесс создания классов, ведь в них для этого есть специальные методы.

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

In [140]:
from dataclasses import dataclass

@dataclass
class Coordinate:
    x: int
    y: int
    z: int

По умолчанию у dataclass есть методы __init__, __repr__ и __eq__, поэтому их не нужно реализовывать самостоятельно.

In [141]:
from dataclasses import dataclass

@dataclass
class Coordinate:
    x: int
    y: int
    z: int

a = Coordinate(4, 5, 3)
print(a)
Coordinate(x=4, y=5, z=3)

При подготовке использованы материалы https://inp.nsk.su/~grozin/python/