# Python

## Типы данных-коллекции

### Раздел 1. Типы данных-последовательности

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

#### Кортежи

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

Как и всегда, tuple может быть вызван как функция. Без передачи аргумента, будет возвращен пустой объект. При передачи в качестве аргумента объекта типа tuple, будет создана его поверхностная копия. Во всех остальных случаях будет совершена попытка привести аргумент к типу tuple. Данная функция принимает не более одного аргумента.

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

Для кортежей доступны всего два метода: count и index. t.count(x) возвращает число вхождений x в t, а t.index(x) возвращает индекс самого левого вхождения x в t, либо возбуждает ValueError, если x не входит в t. К тому же, для кортежей доступны операторы + (конкатенация), \* (повторение), [] (взятие среза), in и not in (проверка на вхождение). Комбинированные версии присваивания += и \*= могут использоваться, несмотря на то, что кортежи неизменяемы. Просто будет создан новый объект, который будет присвоен переменной (то же происходит и со строками). Для кортежей доступны операции сравнения: < <= == != >= >. Они выполняют сравнение поэлементно и рекурсивно.

Кортежи позволяют выполнить один из самых известных трюков в Python:


```python
x = 1
y = 2

x, y = y, x
    
print(x, y)
```

    2 1


Также, если элементами итератора являются кортежи, то вы их сразу можете распаковывать:


```python
for hero, villain in (('Batman', 'Joker'), ('Superman', 'Lex Luthor'), ('Green Lantern', 'Sinestro')):
    print(hero, 'vs', villain)
```

    Batman vs Joker
    Superman vs Lex Luthor
    Green Lantern vs Sinestro


#### Списки

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

Тип list может быть вызван как функция. Без аргументов list() вернет пустой список. Если в качестве аргумента был передан объект типа list, то будет создана его поверхностная копия. Если аргумент дан, но не является объектом класса list, то будет произведена попытка приведения его к типу list. Данная функция принимает не более одного аргумента.

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

Для типа list доступны операции сравнения, работают аналогично случаю с кортежами.

Списки могут быть вложены в друг друга, проитерированы, от них можно взять срез, — все то же, что и для кортежей. Можно использовать операторы in, not int, +, +=, *, *=, функцию len() и конструкцию del.

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


```python
heroes = ['Batman', 'Superman', 'Green Lantern', 'Flash']

batman, *rest = heroes
print(batman, rest)

batman, *mid, flash = heroes
print(batman, mid, flash)
```

    Batman ['Superman', 'Green Lantern', 'Flash']
    Batman ['Superman', 'Green Lantern'] Flash


Также есть оператор распаковки \*, который используется для передачи элементов списка по отдельности в качестве аргументов функции:


```python
heroes = ['Batman', 'Superman', 'Green Lantern', 'Flash']

def print4(a, b, c, d):
    print(a)
    print(b)
    print(c)
    print(d)
    
print4(*heroes)
```

    Batman
    Superman
    Green Lantern
    Flash


Для списков доступны следующие методы:

| Метод | Описание |
| ----- | :------- |
| L.append(x) | Добавляет элемент x в конец списка L |
| L.count(x) | Подсчитывает число вхождений элемента x в список L |
| L.extend(m) | Добавляет элементы m, к которому должен быть обеспечен доступ как к итератору, в конец списка L (то же, что и L += m |
| L.index(x, start, end) | Возвращает индекс самого левого вхождения x в L. Если start и end заданы, то поиск происходит в срезе L[start:end]. Если элемент не найден, то возбуждается исключение ValueError |
| L.insert(i, x) | Вставляет элемент x на позицию с индексом i в список L |
| L.pop() | Удаляет последний элемент списка L и возвращает его |
| L.pop(i) | Удаляет элемент списка L с индексом i и возвращает его |
| L.remove(x) | Удаляет самое левое вхождение элемента x из списка L. Если элемент не найден, то возбуждается исключение ValueError |
| L.reverse() | Разворачивает список |
| L.sort(...) | Сортирует список. Аргумент key отвечает за функцию порядка, а reversed за способ сортировки (по невозрастанию / неубыванию) |

#### Генераторы списков

Для получения списка как результат какой-то операции над итератором используются генераторы списков. Есть две формымы генератора списков:

[выражение for элемент in итератор]

[выражение for элемент in итератор if условие]

В самом простом случае выражение и есть элемент:


```python
print([x for x in range(10)])
print([2*x for x in range(10)])
print([2*x for x in range(10) if x % 3 == 0])
```

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
    [0, 6, 12, 18]


Последняя форма в разы удобнее, чем запись:


```python
lst = []
for x in range(10):
    if x % 3 == 0:
        lst.append(2*x)
print(lst)
```

    [0, 6, 12, 18]


### Раздел 2. Типы-множества

Типы данных-множества — типы, для которых доступны оператор вхождения in, функция len и доступ, как к итератору. В Python есть два типа множеств — set и frozenset. Тип frozenset является неизменяемым, поэтому для него недоступны методы для типа set, которые изменяют объект. В остальном они идентичны.

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

#### Множества

Множество типа set есть неупорядоченная коллекция из нуля или более ссылок на хешируемые объекты. Множества изменяемы, так что в них можно добавлять и удалять элементы. Будучи неупорядоченными, понятие индекса или позиции для них не имеет смысла.

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

Множество всегда хранит уникальные элементы:


```python
set('hello world') == {'h', 'e', 'l', 'o', ' ', 'w', 'r', 'd'}
```




    True



Множества поддерживают функцию len(), которое возвращает число элементов множества.

Для типа set доступны следующие методы:

| Метод | Описание |
| ----- | :------- |
| s.add(x) | Добавляет элемент x во множество s, если такого там нет |
| s.clear() | Удаляет все элементы множества s |
| s.copy() | Создает неглубокую копию множества s |
| s.difference(t) | Возвращает новое множество, которое содержит все элементы множества s, которые не входят во множество t. Эквивалент s - t |
| s.differenct_update(t) | Удаляет все элементы t из множества s. Эквивалент s -= t |
| s.discard(x) | Удаляет элемент x из множества s |
| s.intersection(t) | Возвращает новое множество, которое содержит элементы множества s, которые в то же время являются элементами множества t. Эквивалент s & t |
| s.intersection_update(t) | Удаляет из множества s все элементы, которые не входят в t. Эквивалент s &= t |
| s.isdisjoint(t) | Возвращает True, если у множеств s и t нет общих элементов |
| s.issubset(t) | Возвращает True, если s есть подмножество (несобственное) t. Эквивалент s <= t |
| s.issuperset(t) | Возвращает True, если t есть подмножество (несобственное) s. Эквивалент t <= s |
| s.pop() | Удаляет случайный элемент s и возвращает его. Возбуждает исключение KeyError, если множесво пустое |
| s.remove(x) | Удаляет элемент x из множества s. Возбуждает исключение KeyError, если x не входит в s |
| s.symmetric_difference(t) | Возвращает новое множество, которое содержит элементы, которые либо содержатся в s и не содержатся в t, либо содержатся в t и не содержатся в s. Эквивалент s ^ t |
| s.symmetric_difference_update(t) | Записывает в s симметрическую разность s и t. Эквивалент s ^= t |
| s.union(t) | Возвращает новое множество, которое содержит все элементы множеств s и t |
| s.update(t) | Добавляет все элементы множества t во множество s |

Последние два метода эквивалентны s | t и s |= t соответственно.

#### Генераторы множеств

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


```python
print({x for x in range(10)})
print({2*x for x in range(10)})
print({2*x for x in range(10) if x % 3 == 0})
```

    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    {0, 2, 4, 6, 8, 10, 12, 14, 16, 18}
    {0, 18, 12, 6}


### Раздел 3. Типы-отображения

Типы отображения — наборы пар "ключ - значение". Единственным встроенным типом-отображением является dictionary, который отображает хешируемые объекты на ссылки на другие объекты.

#### Словари

Тип dict (словарь) это упорядоченная коллекция пар "ключ - значение". Раньше dict был неупорядоченным типом, поэтому во избежания ошибок при исполнении кода более старыми интерпретаторами, советую вам придерживаться отношения к словорям как к неупорядоченным последовательностям.

Словари поддерживают оператор вхождения in, функцию len(), и доступ как к итератору. Среди операций сравнения поддерживаются только == и !=.

Как и обычно, тип dict может быть вызван как функция. Если ей не переданы аргументы, то создается пустой словарь. Если передан аргумент с типом-отображением, то создается словарь на основе этого аргумента. Например, если аргумент типа dict, то будет создана его поверхностная копия. Можно также передать аргумент-последовательность, каждый элемент в котором — последовательность из двух элементов: ключа и значения. Также пустой словарь можно создать при помощи конструкции {}, а словарь с элементами — перечислив пары ключ - значение в фигурных скобках через запятую, отделяя ключи от значений двоеточием:

{ключ1 : значение1, ключ2 : значение2}

К тому же доступны генераторы словарей.

Мы можем доступаться к элементам словаря, используя ключ: d[ключ] вернет значение элемента с ключем *ключ*.

Для добавления новых элементов используется оператор присваивания: d[новый\_ключ] = новое\_значение.

Для типа dict доступны следующие методы:

| Метод | Описание |
| ----- | :------- |
| d.clear() | Удаляет все элементы словаря d |
| d.copy() | Возвращает поверхностную копию словаря d |
| d.fromkeys(s, v) | Возвращает словарь, где ключами являются элементы последователности s, а значения равны v или None, если v не указано |
| d.get(k) | Возвращает значение, которое соответствует ключу k или None, если такого нет |
| d.get(k, v) | Возвращает значение, которое соответствует ключу k или v, если такого нет |
| d.items() | Возвращает view (read-only итератор), содержащий пары (ключ, значение) словаря d |
| d.keys() | Возвращает view, содержащий ключи словаря d |
| d.pop(k) | Удаляет элемент, соответствующий ключу k и возвращает его. Если такого нет, то возбуждается исключение KeyError |
| d.pop(k, v) | Удаляет элемент, соответствующий ключу k и возвращает его. Если такого нет, то возвращается значение v |
| d.popitem() | Удаляет из словаря d и возвращает некоторую пару (ключ, значение). Возбуждает исключение KeyError, если d пуст |
| d.setdefault(k, v) | ТТо же, что и d.get(k), но если такого элемента нет, то создается новый со значением None или v, если указано |
| d.values() | Возвращает view всех значений словаря d |

#### Генераторы словарей

Генераторы словарей имеют две уже знакомые нам формы:

[выражение\_ключа : выражение\_значения for ключ, значение in итератор]

[выражение\_ключа : выражение\_значения for ключ, значение in итератор if условие]

### Раздел 4. Итераторы. Функции и операци для работы с итераторами

Тип данных называется итерируемым, если он может возвращать свои элементы по одному. Технически, любой объект, реализующий метод \_\_iter\_\_() является итерируемым и может предоставить итератор. Итератор — объект, реализуюзий метод \_\_next()\_\_, возвращающий следующее значение и возбуждающий исключение StopIteration, если элементов больше нет.

Для итерируемых объектов доступны следующие функции и операторы:

| Функция или оператор | Описание |
| -------------------- | :------- |
| s + t | Возвращает конкатенацию последовательностей s и t |
| s * n | Возвращает последовательность, которая является результатом повторения последовательности s n раз |
| x in t | Проверяет, есть ли в последовательности i элемент x |
| all(i) | Возвращает True, если все элементы i при приведении к типу bool имеют значение True |
| any(i) | Возвращает True, если один из элементов i при приведении к типу bool имеет значение True |
| enumerate(i, start) | Возвращает последовательность элементов (индекс, значение) для итератора i, начиная с индекса 0 или start, если данный аргумент задан |
| len(x) | Возвращает число элементов в x |
| max(i, key) | Возвращает максимальный элемент в i. Если аргумент key задан, то возвращает элемент, для которого key максимален |
| min(i, key) | Возвращает минимальный элемент в i. Если аргумент key задан, то возвращает элемент, для которого key минимален |
| range(start, stop, step) | Возвращает итератор, содержащий целые числа от start до stop с шагом step. Аргумент start включен в последовательность, а stop — нет |
| reversed(i) | Возвращает итератор, в котором элементы идут в обратном порядке |
| sorted(i, key, reverse) | Возвращает отсортированный по неубыванию итератор. Если задан аргумент key, то сортировка идет по значением key(x) для каждого элемента x. Если reversre указан как True, то сортировка идет по невозрастанию |
| sum(i, start) | Возвращает сумму элементов в итераторе i плюс start. Если start не задан, то принимается равным 0 |
| zip(i1, ..., iN) | Возвращает итератор из кортежей, состоящий из элементов i1, ..., iN. Количество кортежей определяется минимальной длиной i1, ..., iN |

### Раздел 5. Копирование коллекций

При присваивании объектов будет выполнено присваивание по ссылке:


```python
x = ['Nightwing', 'Robin', 'Catwoman']
y = x
x[0] = 'Alfred'
print(y)
```

    ['Alfred', 'Robin', 'Catwoman']


Для создания поверхностной копии можно использовать оператор среза:


```python
x = ['Nightwing', 'Robin', 'Catwoman']
y = x[:]
x[0] = 'Alfred'
print(y)
```

    ['Nightwing', 'Robin', 'Catwoman']


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


```python
dc_villains = [['Lex Luther', 'General Zod'], ['Joker', 'Scarecrow']]
villains = dc_villains[:]

dc_villains[1][1] = 'Pinguin'
print(villains)
```

    [['Lex Luther', 'General Zod'], ['Joker', 'Pinguin']]


Чтобы избежать этого, необходимо использовать функцию deepcopy() модуля copy, которая создает глубокую копию объекта:


```python
from copy import deepcopy

dc_villains = [['Lex Luther', 'General Zod'], ['Joker', 'Scarecrow']]
villains = deepcopy(dc_villains)

dc_villains[1][1] = 'Pinguin'
print(villains)
```

    [['Lex Luther', 'General Zod'], ['Joker', 'Scarecrow']]


### Раздел 6. Практика: vedis и matplotlib

Есть достаточно интересный модуль vedis, который предоставляет доступ к key-value store типа Redis на диске, но при этом API этого доступа совпадает с доступом к словарю.

Решим с использованием модуля vedis [задачу о гипотезе Колатца](https://lambda-it.ru/post/26) и сделаем визуализацию при помощи matplotlib.

Для начала напишем функцию, которая из текущего числа получает следующее в последовательности:


```python
def next_kolatz(x):
    if x & 1:
        return 3*x + 1
    return x >> 1
```

Если число x является нечетным (последний разряд в двоичной системе равен единице), то мы возвращаем 3x + 1. В противном случае, мы делим число на 2 (соответствует сдвигу вправо на один разряд в двоичной системе счисления). Теперь импортируем модуль vedis. При помощи него мы будем хранить длину цепочки для чисел, которые мы уже встречали. Также напишем функцию, которая вычисляет и возвращает длину цепочки для заданного числа x, записывает ее в хранилище, а также записывает в хранилище длины цепочек для всех промежуточных чисел:


```python
def get_kolatz_length(x, storage):
    cur = x
    try:
        return int(storage.get(cur))
    except KeyError as e:
        pass
    stack = [cur]
    while True:
        cur = next_kolatz(cur)
        try:
            length = int(storage.get(cur))
            break
        except KeyError as e:
            pass
        stack.append(cur)
    for item in stack[::-1]:
        length += 1
        storage.set(item, length)
    return int(storage.get(x))
```

Теперь напишем функцию, которая возвращает число, меньшее заданного, для которого данная цепочка максимальна и длину цепочки:


```python
def get_max_kolatz_length(n, storage):
    num = max(range(1, n+1), key=lambda x: get_kolatz_length(x, storage))
    return num, get_kolatz_length(num, storage)
```

Теперь остается только инициализировать хранилище и испытать наши функции:


```python
from vedis import Vedis

db = Vedis('storage.db')
db.set(1, 1)

print(get_max_kolatz_length(100000, db))
db.close()
```

    (77031, 351)





    True



Теперь построим обычный scatter plot при помощи matplotlib:


```python
import matplotlib.pyplot as plt
 
db = Vedis('storage.db')

X = range(1, 101)
Y = [get_kolatz_length(x, db) for x in X]
 
db.close()

plt.scatter(X, Y)
plt.title('Визуализация длин цепочек Колатца')
plt.xlabel('Число')
plt.ylabel('Длина цепочки')
plt.show()
```


![png](lecture_3_files/lecture_3_35_0.png)


### Раздел 7. Домашнее задание

Решить задачи после третьей главы Саммерфилда

Загуглить гипотезу Гольдбаха

Загуглить динамическое программирование

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

Решить задачу, которая будет опубликована в субботу