Функциональное программирование




Ноября: вводное занятие

 

Антон (Соколов)

 

языки программирования

—>

высокого уровня (…, в т.ч. 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) # выдаст уже необратимо изменённые данные!

 

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2019-04-04 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: