Введение
Ссылка на чат
В чате будут:
2
Познакомимся
3
Сабалевский Сергей
Руководитель отдела исследований данных в Skillbox
ex. СберКорус, ex. Газпромнефть-ЦР
@sabal202
Организационные моменты
Как обращаться? - Можно на “ты”
Когда можно задавать вопросы?
Пожалуйста, не задавайте мета-вопросов ссылка
Как будет проходить оценивание? - TBD
4
Для кого этот курс будет максимально полезен
5
Структура курса
6
Зачем это знать?
7
О презентациях
На некоторых слайдах есть кнопочка, для интересующихся, с дополнительной информацией по теме на слайде
В некоторых презентациях будут задания для самостоятельного изучения. Они опциональны для выполнения, но могут научить новому.
8
Подробнее
Немного банальностей
9
Где используют Python
10
Реализации Python
11
Python 2 и Python 3
12
Краткое описание
Высокоуровневый язык программирования, который поддерживает несколько парадигм программирования.
Основные архитектурные черты Python:
13
Достоинства
Недостатки
14
Python - интерпретируемый язык
15
Интроспекция
16
Модель данных Python
Что такое тип данных?
18
“Переменная” в Python
Тип переменной определяется её текущим значением во время присваивания.
Типы тоже являются объектами, тип которых - просто "тип".
19
“Переменная” в Python
20
Все - объект
В интерпретаторе существует структура PyObject, от которой наследуются почти все, что есть в Питоне
Тип объекта - тоже объект, наследуется от PyObject и называется PyTypeObject, в нем содержится информация о функциях __init__, __str__, __hash__ и т.д. Некоторые из функций обязательные, некоторые нет
Например, тип int наследуется от PyTypeObject, а bool наследуется от int
21
Типы и утиная типизация
Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка
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
Присваивание
Если оператор присваивания используется для одиночных объектов, то его общая форма имеет вид:
label = object
где:
23
Выражения присваивания
:= это моржовый оператор. Выражение с ним возвращает присваиваемое значение. Появились с Python 3.8
animals = {'lion': 130, 'giraffe': 800, 'hippo': 1300}
name = input()
if (weight := animals.get(name, 0)) > 500:
print(weight)
24
Разница выражения и оператора присваивания
Оператор присваивания NAME = expr является инструкцией, которая говорит записать в NAME ссылку на объект (значение) expr. Его нельзя применять внутри других выражений, он ничего не возвращает.
Выражение присваивания (NAME := expr) является выражением, которое кроме того, как записать в NAME ссылку на объект (значение) expr, еще и возвращает записанное значение expr. Таким образом мы можем использовать его внутри других выражений:
# Цикл, который не может быть тривиально переписан с помощью 2х аргументного iter()
while chunk := file.read(8192):
process(chunk)
25
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
Равенство и идентичность
Для проверки двух объектов в Python на “одинаковость” есть 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
Целочисленные литералы
# Системы счисления
# 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
Вещественные - <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
Про точность
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
Пустой тип - <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
Еще про 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
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
Почему нет 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
С помощью словарей можно заменить 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]()
В 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
Про циклы
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
Ветка else
while CONDITION:
BODY
else: # если вышли по условию (а не по break)
FINISH
for VAR in DATASET:
BODY
else: # если исчерпали данные
FINISH
39
Как работает исполнение кода
40
Типичный CPython
41
Как работает интерпретатор Питона
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’)
]
Как работает интерпретатор Питона
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=[])
Более красивое AST
44
Как работает интерпретатор Питона
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
Как реализованы списки
Списки - динамические массивы.
Размер растет как: 0, 4, 8, 16, 25, 35, 46, 58, 72, …
Наследуется от PyObject
46
Понаблюдаем за списком
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
__hash__
Можем самостоятельно определить как будет хэшироваться наш объект, если определим функцию __hash__, но есть один нюанс - мы не можем возвращать -1
Для целых чисел хэш числа равен самому числу, но есть нюанс:
49
hash(-1) == hash(-2) # True
Вопрос
Какая алгоритмическая сложность у хэш-таблицы (словаря в питоне) для операций:
50
Как реализованы словари
51
Не изменяйте словарь во время итерации
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
Immutable > Mutable
53
Вес списка и кортежа
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
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
Как питон работает с памятью
57
Как питон работает с памятью
58
Как питон работает с памятью
59
Подсчет ссылок
Каждый объект питона ведет количество ссылок, которые ведут на него.
Как только количество ссылок начинает равняться нулю, объект удаляется.
import sys
a = 'hello'
sys.getrefcount(a)
# 2
60
Подсчет ссылок
Что выведет код?
import sys
a = 1
sys.getrefcount(a)
61
Упражнение
Почему разные результаты? Ответ в подробнее
import sys
print(sys.getrefcount("hello"))
a = "hello"
print(sys.getrefcount(a))
62
Когда счетчик ссылок не поможет?
Ссылки могут быть циклическими, тогда подсчет ссылок их не удалит.
a = [1, 2]
a.append(a)
Для того чтобы убирать такие структуры, нужен сборщик мусора.
63
Сборщик мусора (GC)
Ссылки могут быть циклическими, тогда подсчет ссылок их не удалит. Для того чтобы убирать такие структуры, нужен сборщик мусора.
Он работает в предположении о том, что чем дольше объект живет, тем меньше вероятность, что мы захотим его удалить, поэтому существуют 3 поколения
Каждое следующее поколение чистится реже предыдущего
64
Упражнения
Easy
Написать функцию для поиска первых n чисел Фибоначчи
Medium
Построить AST полученной выше функции. Для визуализации можно использовать networkx. Для корректного отображения кода (имена переменных и операторы) на графе можно использовать метод ast.unparse (Python >= 3.9) или библиотеку astunparse
Hard
Сделать отображение графа красивым. Помечать разными цветами константы, вызов функций, операторы. Подписи вершин не должны вылазить за границы. Граф должен быть похож на дерево.
65
Полезные ссылки
Школа бэкенд-разработки Яндекса 2019. Устройство CPython
Возвращаем память операционной системе с помощью Python
Dismissing Python Garbage Collection at Instagram
Григорий Петров. Python в 2022
Как работает интерпретатор Python
66
Для новичков в Python
Советую следующие материалы для знакомства с Python:
67