Ноября: вводное занятие
Антон (Соколов)
языки программирования
—>
высокого уровня (…, в т.ч. Python)
низкого уровня (Ассемблер) — на уровне регистров, памяти, без абстракций
—>
компилируемые
интерпретируемые (в т.ч. Python)
— на самом деле интерпретатор тоже создаёт некоторый (промежуточный) файл на выходе — байт-код (ещё не машинный, но уже не человекочитаемый — сжатый, соптимизированный для нужд интерпретатора)
— байт-код позволяет выполнять быстрее код, который не был модифицирован с прошлого раза.
подход JIT — just-in-time компиляция.
то же самое в Java — генерируется промежуточный файл.
и Python тоже язык с байт-кодом.
архитектуры:
—ARM
—[Intel] x86
— имеют разные наборы команд, для них нужны разные компиляторы и разные интерпретаторы
типизация — может быть строгая (типы заранее заданы) или слабая (на ходу)
утиная типизация: «то, что летает, как утка, крякает, ходит, как утка, наверняка является уткой»
— тоже лежит в философии Питона
Python:
— разработан в 1989, в 1991 попал в публичный доступ
— автор: Guido Van Rossum (Гвидо Ван Россум), голландский программист и математик.
— 2000: версия 2.0 (подхвачена сообществом)
— 2008: версия 3.0 (язык сильно изменён, обратной совместимости с 2.0 уже нет)
— в честь Монти Пайтона
— Ван Россум сохраняет роль BDFL — Benevolent Dictator for Life
— основная реализация — CPython (интерпретатор написан на C), существует также PyPy (!)
философия Python: направленность на то, чтобы код был читаемым (и читать его было приятно).
— читаемый в смысле синтаксиса (меньше безумия типа $, #, …)
— для него были разработаны
Python Enhancement Proposals (PEP)
— рекомендации/правила для разработчика на Питоне
два основных:
—PEP8 (Python style guide) — общий стиль кода
—PEP20 (Zen of Python) — двадцать афоризмов, определяющих общую философию написания кода.
—> слово Pyhtonic — ‘написанный в духе Питона’ (т.е. соответствующий его философии)
— у Python есть интерактивный режим (по команде / набору команд за раз, в консоли)
— свободная лицензия
— недостаток — более низкая скорость (из-за интерпретируемости)
PyCon — мировая конференция разработчиков на Python
Python 2.0:
print "Hello world"
Python 3.0:
print("Hello world")
установка на Windows:
—галочка «добавить в переменные среды»
—галочка «установить PIP»
—привязать файлы *.py
—нужна будет cmd (консоль) — лучше поставить что-то более мощное
—добавить файлам.py права «Read+Execute»
IT-шный чат:
slack.com
(по приглашениям)
д/з:
— всё установить
— см. PEP8, PEP20 (в т.ч. командой "import this")
==============
Ноября
(пропустил)
==============
29 ноября:
Строки:
тройные одинарные ('''string''') = тройные двойные("""string""") кавычки
— для ввода строк с переносами строки
— то же самое достигается вводом \n вместо переноса строки
строки в 3-ем Питоне — (по умолчанию) в Unicode UTF-8
bytes — представление кодированной строки в байтах
array_name [index] — вообще для архивов
str_name [start: stop: step] — работа со строками
str[::-1] — самый простой способ перевернуть строку
str[-5::1] — последние пять символов строки
len(str) — длина строки
str[len(str) - 1] — последний символ
str.replace('OLD', 'NEW') — поиск+замена подстрок
Tuple = кортеж — неизменяемая последовательность одноимённых объектов
— unmutable-тип (не работает присвоение t[0] = 42)
t = () — пустой
из одного объекта:
t = (42,)
присвоение нескольким переменным значений в одну строку через кортеж (экономит код):
a, b, c = 1, 2, 3
+ мена значений переменных через кортеж:
a, b = b, a
оператор in — позволяет проверить, есть ли некий элемент в кортеже:
1 in t
NB! кортеж может содержать объекты разных типов!
кортеж может быть вложенным (кортеж в кортеже)
длина:
len(t)
можно итерировать, etc.
Списки:
l = []
l = [1, 2, 3]
— mutable! (один из немногих таких!):
l[0] = 42
len(l) — длина списка
l.count(42) — сколько раз встретился объект в списке
l.extend(list2) — присоединить к списку другой список
l.index(42) — позиция первого элемента списка, совпадающего с искомым
l.insert(позиция, значение_нового_элемента)
l.pop() / l.pop(3) — удалить последний / удалить находящийся_в_позиции 3
l.push(42) — добавить новый элемент
l.remove(42) — ищет и удаляет (первое вхождение)
l.reverse()
l.sort() — сортировка
l.clear() — очищает список
min(l)/max(l) — минимальное/максимальное значение в списке
sum(l) — сумма списка (или любой последовательности)
l1 + l2
— конкатенация списков
l * 3
— дублирует список (конкатенирует с самим собой несколько раз, получая заданное суммарное число копий)
Множества:
пустое:
s = set()
с заданными элементами
s = {1, 2, 42}
s.add(42)
s.remove(1)
множества можно пересекать (s1 & s2, s1 | s2, s1 - s2)
множество — неупорядоченный объект!
(т.е. нет внутренней индексации; не работает s[1])
в множестве автоматически производится проверка на повторение элементов!
— оно хранит только уникальные элементы
обычное множество — изменяемое
s = frozenset({1, 2, 42})
— неизменяемое множество
Хеш — необратимое изменение данных (с использованием определённой математической функции, дающей максимальный разброс итоговых значений) (+ с потерей части информации! т.е. не то же самое, что шифрование ключом)
хеш-таблица:
для более быстрого поиска данные распределяются по таблице с заданным числом строк в соответствии с посчитанными для них хешами (точнее, с остатками от деления этих хешей на число строк в таблице), при поиске вычисляется хеш искомого объекта и находится нужная строка в таблице.
т.к. хеширование теряет часть информации, то хеши у разных объектов могут иногда совпадать
—> коллизия
Словарь:
содержит пары ключ(key)-значение(value)
типа:
"name" "Anton"
"surname" "Sokolov"
потом можно получать значения по ключу: dict["name"]
— ищет пары как раз при помощи таблицы хешей, т.к. так можно искать более оптимально: по строке ключа считается хеш, и потом по таблице хешей поиск идёт быстрее и из нужной строки выдаётся соответствующее ключу значение
(а в случае коллизий уже можно искать обычным образом внутри соответствующей строки таблицы)
словарь — тоже неупорядоченный!
создание:
d = {}
— пустой словарь
d = dict(A=1, Z=-1)
ИЛИ
d = {'A':1, 'Z':-1}
ИЛИ
d = dict([('A', 1), ('Z', -1)])
присвоение/изменение значений:
d['A'] = 3
del d['A']
ИЛИ
del(d['A'])
Продвинутые коллекции:
import collections
— например, именованный кортеж:
vision = collections.namedtuple('Vision', ['left', 'right'])
vis = vision(1.0, 0.5)
==============
6 декабря:
…продолжение циклов
While
— когда не знаем точное количество итераций
while expression:
statement1
statement2
…
statement3
statement4
найти наибольший общий делитель
есть числа a и b.
из бо́льшего вычитаем меньшее.
потом разницу сравниваем с оставшимся числом.
так, пока числа не окажутся равны
a = 42
b = 54
while(a!= b):
if(a > b):
a = a - b
else:
b = b - a
print("Largest common divisor = ", a)
# print("Largest common divisor = " + str(a)) # — тоже можно
# print("Largest common divisor = ", str(a)) # — тоже можно
# print("Largest common divisor = " + a) # — а вот так не получится:)
диапазон:
a = list(range(0, 10))
— в циклах:
for value in range(0, 10):
print(value)
общая формула:
for iterator in object:
statement1
statement2
…
C-подобный вариант:
surnames = ['Ivanov', 'Petrov', 'Sidorov']
for i in range(0, len(surnames)):
print(surnames[i])
или
surnames = ['Ivanov', 'Petrov', 'Sidorov']
for i in range(len(surnames)):
print(surnames[i])
— но лучше так не делать, можно проще (аналог “for each”):
surnames = ['Ivanov', 'Petrov', 'Sidorov']
for surname in surnames:
print(surname)
— + enumerate() — возвращает кортеж из пар индекс-значение
surnames = ['Ivanov', 'Petrov', 'Sidorov']
for index, surname in enumerate(surnames):
print("index = ", index, ", surname = ", surname, sep='')
— изменение шага в цикле:
шаг 2:
surnames = ['Ivanov', 'Petrov', 'Sidorov']
for surname in surnames[::2]:
print(surname)
шаг –1 (обратный порядок):
surnames = ['Ivanov', 'Petrov', 'Sidorov']
for surname in surnames[::-1]:
print(surname)
прерывание цикла:
e.g. надо в списке найти первое отрицательное число
values = [3, 5, 7, -1, 10, 2]
negative_found = False
i = 0
while(i < len(values) and (negative_found == False)):
if(values[i] < 0):
negative_found = True
i = i + 1
if(negative_found):
print('A negative number was found', sep='')
— но проще с break:
values = [3, 5, 7, -1, 10, 2]
negative_found = False
for value in values:
if(value < 0):
negative_found = True
break
if(negative_found):
print("A negative number was found", sep='')
values = [3, 5, 7, -1, 10, 2]
negative_found = False
position = 0
for index, value in enumerate(values):
if(value < 0):
negative_found = True
position = index
break
if(negative_found):
print("A negative number was found, it's ", values[position], sep='')
Continue
(логичнее было бы назвать его “skip”)
values = [3, 5, 7, -1, 10, 2]
skip_numbers = {7, -1, 2}
for value in values:
if(not value in skip_numbers):
print(value, "^2 = ", value ** 2, sep='')
print(value, "^3 = ", value ** 3, sep='')
— нагляднее без отрицания и с continue (прекращает текущую итерацию и переходит к следующей):
values = [3, 5, 7, -1, 10, 2]
skip_numbers = {7, -1, 2}
for value in values:
if(value in skip_numbers):
continue
print(value, "^2 = ", value ** 2, sep='')
print(value, "^3 = ", value ** 3, sep='')
к циклу for можно добавить else!
— будет выполняться в конце после всех итераций цикла, если только не был сделан break (или если число итераций оказалось нулевым!)
обычный вариант:
values = [3, 5, 7, -1, 10, 2]
negative_found = False
position = 0
for index, value in enumerate(values):
if(value < 0):
negative_found = True
position = index
break
if(negative_found):
print("A negative number was found, it's ", values[position], sep='')
else:
print("No negative numbers were found", sep='')
— вариант с else:
values = [3, 5, 7, -1, 10, 2]
position = 0
for index, value in enumerate(values):
if(value < 0):
position = index
print("A negative number was found, it's ", values[position], sep='')
break
else:
print("No negative numbers were found", sep='')
* * *
д/з:
написать программу, печатающую все простые числа в заданном диапазоне (e.g. от 0 до 100)
1. тестовый код:
i = int(input())
while(i!= 42):
for j in range(2, i if (i > 2) else 2):
print(i, j, i % j)
i = int(input())
— показывает, что у простых чисел появляются хоть где-то нули, а у прочих — ни одного нуля.
2. итоговый код:
lower_limit = 0
upper_limit = 100
for i in range(lower_limit, upper_limit + 1):
for j in range(2, i if (i > 2) else 2):
if(i % j == 0):
print(i, "is not a prime!", i, "%", j, "=", i % j)
break
else:
if (i > 1):
print(i, "is a prime!")
+ только простые числа:
lower_limit = 0
upper_limit = 100
for i in range(lower_limit, upper_limit + 1):
for j in range(2, i if (i > 2) else 2):
if(i % j == 0):
break
else:
if (i > 1):
print(i, end='\t')
3. проверка с другим диапазоном:
между 7200 и 7300:
7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297.
lower_limit = 7200
upper_limit = 7300
for i in range(lower_limit, upper_limit + 1):
for j in range(2, i if (i > 2) else 2):
if(i % j == 0):
break
else:
if (i > 1):
print(i, end='\t')
==============
…
(пропустил)
==============
17 января:
замена плейсхолдеров на строки при выводе:
print("Hello {0} world {1}".format("cruel", "and goodbye"))
print("Hello {0}"
"world {1}".format("cruel", "and goodbye"))
Функции
всегда возвращают хоть что-то (None)
объявляются при помощи def:
def foo():
pass
pass — не делать ничего
def sum(value_a, value_b):
return value_a + value_b
a = sum(a, b)
return — вернуть
можно списками вводить:
def min_max(input_list):
return min(input_list), max(input_list)
— выводит кортеж!
test = [1, 2, 3, 4]
min, max = min_max(test)
д/з: посмотреть про сферы действия переменных, про операторы global и nonlocal
можно приписать новое имя функции, т.к. функции тоже объекты (но надо написать название функции без скобок):
a = sum
функции позволяют:
— избежать дублирования кода
— инкапсуляция: внутренние переменные и реализация функции не видны и не важны конечному пользователю
по возможности функция должна делать примерно одну «вещь», не следует перегружать функции
функции не должны иметь побочных эффектов (т.е. по возможности не должны «вылезать» в глобальные переменные и менять в них что-либо)
Аргументы функций:
1. позиционные (positional) аргументы — перечисляются через запятую;
обязательно все должны передаваться!
2. «аргументы с ключевыми словами» (keyword) = с заданным дефолтным значением — пишутся через равно (обязательно после всех позиционных!):
def sum(value_a, value_b = 10):
return value_a + value_b
a = sum(4)
— можно задать несколько, а поменять только второй:
def sum(value_a = 10, value_b = 10):
return value_a + value_b
a = sum()
a = sum(value_b = 2)
3. случаи, когда неизвестно количество позиционных входных аргументов:
— то, что под звёздочкой, становится кортежем
def var_pos_args(* args):
print(args)
— NB! а звёздочка в обычном коде — «разворачивает» кортеж, т.е. «снимает» верхний уровень с кортежа, делает его обычными данными (последовательностью переменных):
print(*args)
4. случаи, когда неизвестно количество входных аргументов с ключевыми словами (т.е. получаем на входе словарь!) (NB! уже без дефолтных значений):
def var_keyword_args(**kwargs):
print(kwargs)
var_keyword_args(a = 1, b = 42)
var_keyword_args(**{'a': 1, 'b': 42})
var_keyword_args(**dict(a = 1, b = 42))
— одно и то же
5. keyword-only аргументы:
def keyword_only(*a, c):
print(a, c)
keyword_only(1, 2, 3, 4, 5, 6, 7, c = 8)
— первые семь значений пойдут в кортеж a, и только явно прописанное последнее — в c
— возможно именно после кортежа
+ при помощи звёздочки можно и без кортежа делать keyword-only аргументы:
def mixed_keyword(a, b = 42, *, c):
def bar(*, value1, value2):
print(value1, value2)
bar(value1 = 2, value2 = 42)
bar(value2 = 42, value1 = 2)
some_dict = {'value1': 2, 'value2': 42}
bar(** some_dict) # — разыменовываем словарь в последовательность пар!
максимальный набор видов аргументов и их порядок:
def foo(a, b = 42, *args, c, **kwargs):
==============
24 января:
Рекурсия
пример: вычисление факториала при помощи рекурсивной функции:
def factorial(value):
if value in (0, 1):
return 1
return value * factorial(value - 1)
итеративная (на цикле) имплементация факториала, например, быстрее, чем рекурсивная (на рекурсивном цикле) — см. ниже
вычисление времени выполнения произвольной функции:
from time import time
def factorial(value):
if value in (0, 1):
return 1
return value * factorial(value - 1)
def iterative_factorial(value):
result = 1
for x in range(1, value + 1):
result *= x
return result
def measure_time(function, *args, **kwargs):
start_time = time()
function(* args, ** kwargs)
end_time = time()
return end_time - start_time
n = 60
print("time = {0}".format(measure_time(factorial, n)))
print("time = {0}".format(measure_time(iterative_factorial, n)))
— NB! мы передаём не выполняемую функцию, а имя функции! а затем отдельно её аргументы
Отладка кода (debugger):
добавить к аргументам командной строки Python "-m pdb":
> python -m pdb FILENAME.py
+ Функция в функции:
def measure(function):
def wrapper(*args, **kwargs):
start_time = time()
function(*args, **kwargs)
end_time = time()
return end_time - start_time #правда, тут мы гробим саму функцию, т.к. заменяем её вывод)
return wrapper
factorial = measure(factorial)
print("time = {0}".format(measure(n)))
iterative_factorial = measure(iterative_factorial)
print("time = {0}".format(measure(n)))
— техника «декорирования»
— жирные строчки — называются «точками декорации»
— мы добавляем дополнительный функционал в функцию, не переписывая её полностью
в Питоне даже есть специальный синтаксис для декораторов — с символом “@”:
— позволяет записать декорирование в виде простой строчки перед объявлением функции:
def measure(function):
def wrapper(*args, **kwargs):
start_time = time()
function(*args, **kwargs)
end_time = time()
return end_time - start_time
return wrapper
@measure # то есть эта строчка эквивалентна зачёркнутой строчке ниже
def factorial(value):
if value in (0, 1):
return 1
return value * factorial(value - 1)
factorial = measure(factorial)
print("time = {0}".format(measure(n)))
узнать «реальное» имя функции, к которой отсылает переменная:
print(factorial .__name__)
при декорировании должно вывести “wrapper”! (а без декорирования — “factorial”)
более правильный декоратор:
def measure(function):
def wrapper(*args, **kwargs):
start_time = time()
result = function(*args, **kwargs)
end_time = time()
print ("Time = {0}".format(end_time - start_time))
return result
return wrapper
— теперь он не трогает вывод функции, а только выводит дополнительно на экран время её выполнения.
==============
Января
Функциональное программирование
— основная идея в том, что мы оперируем больше не данными, а функциями
— оперируем функциями как объектами, можем применять функции к функциям к функциям
— много рекурсии, мало/нет циклов
— функции без побочных эффектов, «чистые»
— характерные представители языков для функционального программирования: Lisp, Prolog, Haskell, Scala (надстройка над Java)
в процедурном программировании мы используем данные, кормим их функциям, сохраняем, получаем результат.
в Питоне мы тоже можем оперировать функциями как объектами
— элементы функционального программирования:
def sum(a, b):
return a + b
b = sum
type(b) # выдаёт <class 'function'>
b(1, 2) # выдаёт 3
пример:
функция, которая меняет каждый элемент в списке:
def modify_list(mod_function, input_list):
result_list = []
for element in input_list:
result_list.append(mod_function( element ))
return result_list
def increment(value):
return value + 1
test_list = [1, 2, 3, 4, 5, 6]
print("result list = ", modify_list(increment, test_list))
NB! если не создавать копию входного списка в функции, а менять его самого, то портятся данные в глобальной переменной-списке!
def modify_input_list(mod_function, input_list):
for index, element in enumerate(input_list):
input_list[index] = mod_function(element)
return input_list
def increment(value):
return value + 1
test_list = [1, 2, 3, 4, 5, 6]
print("result list = ", modify_input_list(increment, test_list))
print(test_list) # выдаст уже необратимо изменённые данные!