1 of 67

Введение

2 of 67

Ссылка на чат

В чате будут:

  • Ссылка на презентации
  • Ответы на вопросы
  • Административные вопросы

2

3 of 67

Познакомимся

3

Сабалевский Сергей

Руководитель отдела исследований данных в Skillbox

ex. СберКорус, ex. Газпромнефть-ЦР

@sabal202

4 of 67

Организационные моменты

Как обращаться? - Можно на “ты”

Когда можно задавать вопросы?

  1. Не перебивая, на месте возникновения вопроса
  2. Написать вопрос в чат, лучше в телеграм чат

Пожалуйста, не задавайте мета-вопросов ссылка

Как будет проходить оценивание? - TBD

4

5 of 67

Для кого этот курс будет максимально полезен

  • Ты хотел прочитать Fluent Python, но не дошли руки
  • Ты хочешь писать эффективный код
  • Ты хочешь более глубоко понимать инструмент, которым пользуешься

5

6 of 67

Структура курса

  1. Модель данных Python
  2. Виртуальные среды и скрипты
  3. Функциональное программирование и функции
  4. Углубленное ООП
  5. Концепции многозадачности
  6. Асинхронное программирование
  7. Оптимизация
  8. Метапрограммирование

6

7 of 67

Зачем это знать?

  • Написание библиотек
  • Оптимизация под высокую нагрузку
  • Отладка сложных случаев
  • Глубокое знание инструмента = профессионализм
  • Спрашивают на собесах
  • Вопросы на собеседованиях Это интересно

7

8 of 67

О презентациях

На некоторых слайдах есть кнопочка, для интересующихся, с дополнительной информацией по теме на слайде

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

8

Подробнее

9 of 67

Немного банальностей

9

10 of 67

Где используют Python

  • Работа с xml/html файлами
  • GUI (графический интерфейс)
  • CLI
  • Создание веб-сценариев
  • Работа с изображениями, аудио и видео файлами
  • Робототехника
  • Программирование математических и научных вычислений
  • Искусственный интеллект
  • Анализ данных
  • Разработка игр
  • Веб скрапинг
  • Чат-боты

10

11 of 67

Реализации Python

11

12 of 67

Python 2 и Python 3

  • Unicode vs. ASCII
  • Итераторы
  • И много другого

12

13 of 67

Краткое описание

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

Основные архитектурные черты Python:

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

13

14 of 67

Достоинства

Недостатки

  • Гибкость
  • Интерактивность
  • Батарейки включены
  • Простота (и сложность)
  • Динамическая типизация
  • Скоростные ограничения
  • Проблемы с многопоточностью
  • Проблемы с системой типов

14

15 of 67

Python - интерпретируемый язык

15

16 of 67

Интроспекция

  • Можно “на лету” узнавать тип переменной
  • Можно посмотреть структуру программы из самой программы
  • Можно исполнить код внутри программы
  • Доступ к состоянию всей рабочей области

16

17 of 67

Модель данных Python

18 of 67

Что такое тип данных?

  • python имеет динамическую типизацию;
  • проверить тип можно функцией type(x);
  • привести один тип к другому можно с помощью специальных функций.

18

19 of 67

“Переменная” в Python

Тип переменной определяется её текущим значением во время присваивания.

Типы тоже являются объектами, тип которых - просто "тип".

19

20 of 67

“Переменная” в Python

20

21 of 67

Все - объект

В интерпретаторе существует структура PyObject, от которой наследуются почти все, что есть в Питоне

Тип объекта - тоже объект, наследуется от PyObject и называется PyTypeObject, в нем содержится информация о функциях __init__, __str__, __hash__ и т.д. Некоторые из функций обязательные, некоторые нет

Например, тип int наследуется от PyTypeObject, а bool наследуется от int

21

22 of 67

Типы и утиная типизация

Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка

x = 1 # создается объект типа int со значением 1.

# объекту присваивается id. Переменная x ссылается на этот объект

# если объект по данному id изменит значение, то значение изменит и x, который ссылается на значение

print(x, type(x)) # 1 <class 'int'>

x = x * 1.5

print(x, type(x)) # 1.5 <class 'float'>

x = str(x)

print(x, type(x)) # 1.5 <class 'str'>

print(type(type(x))) # <class 'type'>

22

23 of 67

Присваивание

Если оператор присваивания используется для одиночных объектов, то его общая форма имеет вид:

label = object

где:

  • object– значение, присваиваемое имени label, которое может быть выражением или одиночным значением
  • label – имя, этикетка (переменная), которое будет ссылаться на object

23

24 of 67

Выражения присваивания

:= это моржовый оператор. Выражение с ним возвращает присваиваемое значение. Появились с Python 3.8

animals = {'lion': 130, 'giraffe': 800, 'hippo': 1300}

name = input()

if (weight := animals.get(name, 0)) > 500:

print(weight)

24

25 of 67

Разница выражения и оператора присваивания

Оператор присваивания NAME = expr является инструкцией, которая говорит записать в NAME ссылку на объект (значение) expr. Его нельзя применять внутри других выражений, он ничего не возвращает.

Выражение присваивания (NAME := expr) является выражением, которое кроме того, как записать в NAME ссылку на объект (значение) expr, еще и возвращает записанное значение expr. Таким образом мы можем использовать его внутри других выражений:

# Цикл, который не может быть тривиально переписан с помощью 2х аргументного iter()

while chunk := file.read(8192):

process(chunk)

25

26 of 67

The Walrus operator

command = input("> ")

while command != "quit":

print("You entered:", command)

command = input("> ")

Преобразуется в:

while (command := input("> ")) != "quit":

print("You entered:", command)

Еще пример:

if (n := len(a)) > 10:

print(f"List is too long ({n} elements, expected <= 10)")

26

27 of 67

Равенство и идентичность

Для проверки двух объектов в Python на “одинаковость” есть 2 метода:

  1. Проверка на идентичность is
  2. И стандартные !=, ==

i, j = 100, 100 # маленькие числа (от -5 до 255)

print('ints:', i == j, i is j, id(i), id(j)) # и равны, и идентичны

# >>> ints: True True 4531811472 4531811472

x, y = 10**100, 10**100 # большие числа

print('big ints:', x == y, x is y, id(x), id(y)) # равны, но не идентичны

# big ints: True False 4576818352 4576818496

27

28 of 67

Типы делятся на изменяемые и неизменяемые

  • Immutable
    • str, bytes
    • int, float, complex
    • bool
    • tuple
    • frozenset
    • fractions.Fraction
    • etc.
  • Mutable
    • list, bytearray
    • dict
    • set
    • collections.deque
    • collections.Counter, etc.

28

29 of 67

Целочисленные литералы

# Системы счисления

# 2-, 8-, 10- и 16-ричные литералы

print(0b11001100, 0o314, 204, 0xCC) # 204 204 204 204

# строковые представления

print(bin(204), oct(204), str(204), hex(204))# 0b11001100 0o314 204 0xcc

29

30 of 67

Вещественные - <class 'float'>

64-разрядные вещественные числа "двойной точности" (они же - double в языках семейства Си)

# фиксированная точка (внутреннее представление всё равно плавающее)

print(1., .1, 1.1) # 1.0 0.1 1.1

# экспоненциальная запись

print(.1e1, 1e-1, 1.1e0) # 1.0 0.1 1.1

# числа можно возводить в дробную степень

print(5.+.3, 5.-.3, 5.*.3, 5.**.3) # 5.3 4.7 1.5 1.6206565966927624

# деление без остатка, деление нацело и остаток от деления!

print(5./.3, 5.//.3, 5.%.3) # 16.666666666666668 16.0 0.20000000000000018

# как получить целую и дробную части числа?

print(123.45//1, 123.45%1) # 123.0 0.45000000000000284

30

31 of 67

Про точность

import sys

print(sys.float_info)

print(sys.float_info.max_10_exp)

print(sys.float_info.epsilon)

sys.float_info(

max=1.7976931348623157e+308,

max_10_exp=308,

min=2.2250738585072014e-308,

min_10_exp=-307,

epsilon=2.220446049250313e-16

)

31

32 of 67

Пустой тип - <class 'NoneType'>

Тип с названием NoneType и единственным значением None. Предназначен для указания отсутствия значений. Это аналог сишного типа void, но более полезный.

print(None) # None

print(type(None)) # <class 'NoneType'>

x = None

y = 123

print(x == None, x is None, x is not None) # True True False

print(y == None, y is None, y is not None) # False False True

32

33 of 67

Еще про bool

print('bool from pos int is ', bool(5)) # True

print('bool from zero is ', bool(0)) # False

print('bool from empty str is ', bool('')) # False

print('bool from empty list is ', bool([])) # False

print(True is 1) # False

print(True == 1) # True

print(True + False) # 1

print(True / False) # ZeroDivisionError: division by zero

33

34 of 67

or и not являются ленивыми операторами

Операции and, or, not - ленивые.

a = 6

b = 9

print('max:', a if a > b else b) # Тернарный оператор

x = 0

print(x or 'default') # default

print(x and 'finally') # 0

print('one' if x == 1 else 'two' if x == 2 else 'otherwise') # otherwise

34

35 of 67

Почему нет switch-case

x = 2*2-1 # запомним результат вычислений в переменную

# и далее - лесенка сравнений

if x==1:

print('one')

elif x==2:

print('two')

elif x==3:

print('three')

else:

print('unexpected')

Еще один вариант - использование словаря

35

36 of 67

С помощью словарей можно заменить if-else

36

if code == 1:

# code_1_handler()

elif code == 2:

# code_2_handle()

# ...

handlers = {

1: code_1_handler,

2: code_2_handler,

# ...

}

handlers[code]()

37 of 67

В 3.10 появился pattern matching

x = 2 * 2 - 1 # запомним результат вычислений в переменную

match x:

case 1:

print('one')

case 2:

print('two')

case 3:

print('three')

case _:

print('unexpected')

Pattern matching гораздо мощнее, чем switch-case, имеет больше возможностей, при этом и накладные расходы, соответственно, больше

37

38 of 67

Про циклы

for i in range(5): # в диапазоне [0,5)

print(i)

for c in 'abcde': #проходим по итерируемому объекту

print(c)

# Если нужен цикл, в котором есть индексы, - используется коллекция enumerate

for i, c in enumerate('abcde'):

print(i, c)

38

39 of 67

Ветка else

while CONDITION:

BODY

else: # если вышли по условию (а не по break)

FINISH

for VAR in DATASET:

BODY

else: # если исчерпали данные

FINISH

39

40 of 67

Как работает исполнение кода

40

41 of 67

Типичный CPython

41

42 of 67

Как работает интерпретатор Питона

  1. Код переводится в лексемы

42

from tokenize import tokenize

from io import BytesIO

from token import tok_name

code_string = 'print(222*555)'

tokens = tokenize(

BytesIO(code_string.encode('utf-8')).readline

) # tokenize the string

pprint([( token.string, tok_name[token.type])

for token in tokens])

[

('utf-8', 'ENCODING’),

('print', 'NAME’),

('(', 'OP’),

('222', 'NUMBER’),

('*', 'OP’),

('555', 'NUMBER’),

(')', 'OP’),

('', 'NEWLINE’),

('', 'ENDMARKER’)

]

43 of 67

Как работает интерпретатор Питона

2. Строится AST

43

import ast

ast_object = ast.parse("def to_power(a,b):

return a ** b;")

print(ast.dump(ast_object))

Module(body=[FunctionDef(name='to_power', args=arguments(posonlyargs=[], args=[arg(arg='a'), arg(arg=’b’)], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return(value=BinOp(left=Name(id='a', ctx=Load()), op=Pow(), right=Name(id='b', ctx=Load())))], decorator_list=[])], type_ignores=[])

44 of 67

Более красивое AST

44

45 of 67

Как работает интерпретатор Питона

3. AST переводится в байткод

45

import dis

dis.dis('print(123)')

1 0 LOAD_NAME 0 (print)

2 LOAD_CONST 0 (123)

4 CALL_FUNCTION 1

6 RETURN_VALUE

46 of 67

Как реализованы списки

Списки - динамические массивы.

Размер растет как: 0, 4, 8, 16, 25, 35, 46, 58, 72, …

Наследуется от PyObject

46

47 of 67

Понаблюдаем за списком

import sys

a = []

sys.getsizeof(a) # 56

a.append(1) # [1]

sys.getsizeof(a) # 88

a.append(1) # [1, 1]

sys.getsizeof(a) # 88

a.append(1) # [1, 1, 1]

sys.getsizeof(a) # 88

a.append(1) # [1, 1, 1, 1]

sys.getsizeof(a) # 88

a.append(1) # [1, 1, 1, 1, 1]

sys.getsizeof(a) # 120

47

48 of 67

Хэш-функции

48

49 of 67

__hash__

Можем самостоятельно определить как будет хэшироваться наш объект, если определим функцию __hash__, но есть один нюанс - мы не можем возвращать -1

Для целых чисел хэш числа равен самому числу, но есть нюанс:

49

hash(-1) == hash(-2) # True

50 of 67

Вопрос

Какая алгоритмическая сложность у хэш-таблицы (словаря в питоне) для операций:

  • Вставки
  • Чтения
  • Удаления

50

51 of 67

Как реализованы словари

  1. Считаем хэш от ключа
  2. Хэш - индекс в словаре
  3. Если произошла коллизия, то записываем в следующий свободный слот
  4. Если свободных слотов будет мало, то сложность всех операций будет ближе к линейной, поэтому не забываем аллоцировать побольше свободных мест

51

52 of 67

Не изменяйте словарь во время итерации

a = {'a':1, 'b':2}

try:

for i in a:

a['c'] = 3

except RuntimeError as e:

print(f'ERROR: {e})

# ERROR: dictionary changed size during iteration

52

53 of 67

Immutable > Mutable

  • Неизменяемые типы проще читать (понятно, что находится внутри)
  • Неизменяемые типы как правило хэшируемые (а значит можно использовать как ключ словаря), причем хэш не изменяется
  • Tuple работает быстрее List’а и занимает меньше памяти
  • По иммутабельным типам можно настроить кэширование

53

54 of 67

Вес списка и кортежа

import sys

print(sys.getsizeof([1])) # 64

print(sys.getsizeof((1,))) # 48

a = list(range(100))

print(sys.getsizeof(a)) # 856

b = tuple(range(100))

print(sys.getsizeof(b)) # 840

54

55 of 67

Копирование

55

import copy

a = [1]

# Копируем объект

b = copy.copy(a)

# или

b = copy.deepcopy(a)

# или (для списков)

b = a[:]

b.append(2)

print(a)

# [1]

a = [1]

b = a

b.append(2)

print(a)

# [1, 2]

56 of 67

Подробнее о памяти

56

57 of 67

Как питон работает с памятью

  • У Python есть несколько способов выделять память
  • В Python все - объекты, и зачастую они очень маленькие
  • Объектов очень много, поэтому используются object allocator для объектов меньше 512Б
  • Для объектов больше используется raw allocator

57

58 of 67

Как питон работает с памятью

  • Память выделяется блоками по 256 Кб и называется Ареной
  • Арены делятся на пулы по 4кб
  • Пулы делятся на блоки, внутри одного пула блоки одного размера

58

59 of 67

Как питон работает с памятью

  • Арена хранит количество свободных пулов
  • Если все пулы свободны, то арена освобождается и отдается ОС (происходит крайне редко)
  • Из-за того, что памяти всегда выделено больше, чем нужно, мы почти не тратим время на выделение памяти

59

60 of 67

Подсчет ссылок

Каждый объект питона ведет количество ссылок, которые ведут на него.

Как только количество ссылок начинает равняться нулю, объект удаляется.

import sys

a = 'hello'

sys.getrefcount(a)

# 2

60

61 of 67

Подсчет ссылок

Что выведет код?

import sys

a = 1

sys.getrefcount(a)

61

62 of 67

Упражнение

Почему разные результаты? Ответ в подробнее

import sys

print(sys.getrefcount("hello"))

a = "hello"

print(sys.getrefcount(a))

62

63 of 67

Когда счетчик ссылок не поможет?

Ссылки могут быть циклическими, тогда подсчет ссылок их не удалит.

a = [1, 2]

a.append(a)

Для того чтобы убирать такие структуры, нужен сборщик мусора.

63

64 of 67

Сборщик мусора (GC)

Ссылки могут быть циклическими, тогда подсчет ссылок их не удалит. Для того чтобы убирать такие структуры, нужен сборщик мусора.

Он работает в предположении о том, что чем дольше объект живет, тем меньше вероятность, что мы захотим его удалить, поэтому существуют 3 поколения

Каждое следующее поколение чистится реже предыдущего

64

65 of 67

Упражнения

Easy

Написать функцию для поиска первых n чисел Фибоначчи

Medium

Построить AST полученной выше функции. Для визуализации можно использовать networkx. Для корректного отображения кода (имена переменных и операторы) на графе можно использовать метод ast.unparse (Python >= 3.9) или библиотеку astunparse

Hard

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

65

66 of 67

Полезные ссылки

66

67 of 67

Для новичков в Python

Советую следующие материалы для знакомства с Python:

67