# --- ! PEP-8 не соблюдён для компактности ! ---
# --- 1 ---
# Функция принимает аргумент, прибавляет к нему единицу в теле и возвращает +1 к нашему аргументу.
def num(a):
    a += 1      # Инструкция return говорит, что нужно вернуть значение.
    return a    # То, что идёт в теле после return выполняться не будет!~
print('1: ', num(10))
# --- 2 ---
# Функция map – позволяет проводить определённую операцию над каждым элементом списка или другого итерируемого объекта.
numbers = [1, 2, 3, 8, 9]
def times_ten(b):
    return b * 10
print('2: ', list(map(times_ten,numbers))) # Она принимает 2 аргумента:
                                           # 1. Функцию которая проводит операцию над элементом
                                           # 2. Сам итерируемый элемент
# --- 3 ---
# Функция filter – используется для отбора тех элементов итерируемого объекта, которые удовлетворяют условию.
# Иными словами, мы создаём специальную функцию, которая проверяет условие. Если оно true, тогда они попадают в список
def check(c):
    return c > 2
print('3: ', list(filter(check,numbers)))
# --- 4 ---
# Функции могут использоваться в качестве аргументов, для других функций.
def add(x, y):           # Функции, которые возвращают значение только от своих аргументов – называются чистыми функциями.
    return x + y         # Использовать чистые функции – одна из целей функционального программирования.
def do_twice(add, x, y): # Функции, которые возвращают или принимают другие функции – называются функциями высшего порядка.
    return add(add(x, y), add(x, y))
x, y = 5, 10
print('4: ', do_twice(add, x, y))
# --- 5 ---
def countdown(): # Генераторы в функциях создаются при помощи yield
    i = 5        # yield определяет генератор, заменяет значение возвращаемое функцией, возвращает результат, не меняя переменные
    while i > 0: # Если проще, это выражение превращает функцию в генератор
        yield i
        i -= 1
print('5: ')
for i in countdown():
    print('   ', i)
# --- 6 ---
# Декораторы. «Обёртка» функции.
# Они предназначены для модификации функций, при помощи других функций
def decor(func):                           # 1) создадим функцию, которую будем декорировать(оборачивать)
    def wrapper():                         # 2) вложим в неё функцию обёртку
        print('6: ', '---before---')
        func()
        print('   ', '---after---')
    return wrapper                         # 3) внутри wrapper будет код, который выполнится до выполнения функции
@decor
def show():
    print('   ', '---!now!---')            # 4) создадим саму функцию и обернём её в конструкцию декоратора
show()
# --- 7 ---
# *Args ** Kwargs. Параметр args превращается в кортеж,а kwargs – в словарь.
print('7: ')
def many(*args, **kwargs): # слова “args” и “kwargs” не так важны. Это просто сокращение. Главное –количество звездочек
    print('   ', args)     # Чтобы получить бесконечное количество аргументов, мы используем *args
    print('   ', kwargs)   # Чтобы получить бесконечное количество ключевых аргументов, мы используем **kwargs
# args - это кортеж из всех переданных аргументов функции, и с переменной можно работать также, как и с кортежем
# kwargs - В переменной kwargs у нас хранится словарь, с которым мы, опять-таки, можем делать все, что нам заблагорассудится
many(1, 2, 3, name='alex', job='programmer')
# --- 8 ---
# Можно определить функцию обычным способом, а можно использовать лямбда-функцию — маленькую функцию, возвращающую результат одного выражения
# lambda - анонимны, то есть без имени. На практике они часто используются, как способ получить встроенную функцию  или отложить выполнение  фрагмента программного кода.
f = lambda m, n, z: m + n + z
print('8: ', f(2, 3, 4))
# Лямбда-выражения отличаются от def по следующим признакам:
    # Их можно передавать мгновенно (переменная не нужна);
    # Они могут содержать только одну строку кода;
    # Они возвращаются автоматически;
    # Они не могут содержать docstring и иметь наименование;
    # Синтаксис отличается и мало знаком.
    # Тот факт, что lambda-выражения могут свободно передаваться является главным преимуществом. Автоматический возврат — тоже удобно, но его с трудом можно назвать весомым преимуществом, на мой взгляд
