Функции в языке Python

Хорошей практикой структурирования кода является написание функций.

Синтаксис:
def function_name(argument1, argument2):
 # function body
 return argument1 + argument2
x = function_name(2, 8)
y = function_name(x, 21)
print(y)
Отступ в четыре пробела в теле функции является важным.
Имена переменных в языке Python, в том числе имена функций и имена аргументов могут содержать в себе только латиницу, цифры и символ нижнего подчеркивания. И при этом не могут начинаться с цифры.

Определение функции не выполняется интерпретатором построчно, а выполняется целиком.

Функции в языке Python также являются объектами:


Объект функции хранит в себе очень много всего: название функции, аргументы и тело функции.


Пример:
def list_sum(lst):
 result = 0
 for element in lst:
  result += element
 return result

def sum(a, b):
 return a + b

y = sum(14, 29)
z = list_sum([1, 2, 3])
print(y)
print(z)


Стек вызовов

Стек - это абстрактная структура данных, которую можно сравнить со стопкой. Всё что мы можем сделать это либо положить какой-нибудь элемент сверху (push), либо убрать верхний элемент (pop).


Стек вызовов хранит функции, которые мы вызвали. Если в процессе исполнения кода интерпретатор видит, что нужно исполнить какую-то функцию, то он кладет ее на стек. Если эта функция завершает свое исполнение, то он со стека её снимает.


Функция module занимается тем, что исполняет наши запросы. Это именно она складывает для нас числа и определяет функции.

Все функции внутри стека вызовов исполняются, но одна из них исполняется реально, та функция, которая лежит на верхушке стека, а остальные функции жду соседа сверху, пока он исполнится.

Если пользоваться у списка только методами append и pop, то по сути это будет стек:


Возвращаемое значение

Для возвращения значения в языке Python используется конструкция return. После return функция далее не выполняется. Функции необязательно что-то возвращать: если в функции используется "пустой" return или его вообще нет, то вернется None.


Типом объекта None является NoneType.

Единственным правильным способом проверки на None является проверка через is:
x is None

Передача аргументов в функцию



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

Механизм для передачи в функцию неопределенного числа аргументов:


Для именованных аргументов есть похожий синтаксис:


Эти подходы можно совмещать. Пример синтаксически верной конструкции:


Пример:

def s(a, *vs, b=10):
   res = a + b
   for v in vs:
       res += v
   return res

print('s(21) = ', s(21)) # 31
print('s(11, 10) = ', s(11, 10)) # 31
print('s(0, 0, 31) = ', s(0, 0, 31)) # 41

# TypeError: s() missing 1 required positional argument: 'a'
# print('s(b=31) = ', s(b=31))

print('s(11, 10, b=10) = ', s(11, 10, b=10)) # 31
print('s(11, b=20) = ', s(11, b=20)) # 31
print('s(5, 5, 5, 5, 1) = ', s(5, 5, 5, 5, 1)) # 31
print('s(11, 10, 10) = ', s(11, 10, 10)) # 41

# SyntaxError
# positional argument follows keyword argument
# print('s(b=31, 0) = ', s(b=31, 0))

Прием аргументов функцией



Рекурсивные функции



Программа, которая для заданных n и k вычисляет C(n, k):
def C(n, k):
    if k == 0:
        return 1
    elif k > n:
        return 0
    else:
        return C(n - 1, k) + C(n - 1, k - 1)

n, k = map(int, input().split())
print(C(n, k))

Локальные пространства имен

Необходимость создавать локальные пространства имен тесно связана с функциями, со стеком вызовов и с локальными переменными. Когда функция вызывается, она создает локальный неймспейс в котором хранятся аргументы функции и локальные переменные объявленные внутри функции.


Когда функция завершает свое исполнение она снимается со стека и разрушает свой локальный неймспейс.

Конструкции global и nonlocal

Для того что изменить значение глобальной переменной или переменной в пространстве имен вызвавшей нас функции в языке Python есть конструкции global и nonlocal.


Однако и локальных и глобальных переменных тоже может иногда не хватать. Если обернуть весь наш код внутри одной функции f, то тогда ok_status бы означал насколько хорошо исполняется функция f.


ok_status создается в локальном неймспейспе функции f, поэтому ни в каком глобальном неймспейсе у нас нет имени ok_status. Такое поведение не совсем то что мы хотели. Для того чтобы исправить ситуацию можно использовать ключевое слово nonlocal.


Конструкция nonlocal ok_status, говорит интерпретатору, что ему нужно найти такое пространство имен, которое бы содержало имя ok_status, при этом это должно быть ближайшее пространство имен по пути от текущего пространства имен до неймспейса global. На этом пути мы никогда не будем смотреть ни в global, ни в check.


Ключевое слово yield и генераторы

Генераторы - это по сути функции в которых вместо return мы используем слово yield и при этом вместо того чтобы возвращать нам значение, она будет нам его генерировать. Если интерпретатор встречает в теле функции слово yield, то он понимает, что эта функция является генератором. Исполнение тела функции происходит лишь тогда, когда мы вызываем next() и происходит оно от одного yield до следующего, а если следующего нет тогда бросается ошибка StopIteration.
def simple_gen():
    print("Checkpoint 1")
    yield 1
    print("Checkpoint 2")
    yield 2
    print("Checkpoint 3")

gen = simple_gen()
x = next(gen)
print(x)  # 1
y = next(gen)
print(y)  # 2
z = next(gen)  # StopIteration
Ключевое слово return в функции генератора равносильно выбросу ошибки StopIteration. То что передается в return будет служить сообщением ошибки StopIteration.

Генераторы это удобный синтаксис для написания итераторов.
from random import random


def random_generator(k):
    for i in range(k):
        yield random()
gen = random_generator(3)
print(type(gen))
for i in gen:
    print(i)

Передача функций внутрь других функций

Строго говоря в языке Python функцией можно называть только то, у чего есть тип function. Но с другой стороны, если мы рассмотрим конструктор какого-нибудь типа например int, он тоже принимает какие-то аргументы и возвращает какие-то значения. Здесь мы можем применить метод так называемой уточной типизации: если что-то плавает и крякает как утка, похоже на утку, то вероятно это утка. Также можно относиться к функциям. Всё что можно вызвать, всё что принимает аргументы и возвращает значения можно назвать функцией.

map

Большая часть стандартной библиотеки языка Python принимает последовательности (списки и итераторы) и функции в качестве аргументов. В конструктор класса map мы передаем класс int и последовательность считанных чисел:
x = input().split()
print(x)
map_obj = map(int, x)  # f [a, b, c, ...] -> f(a), f(b), f(c) ...
print(map_obj)
n = next(map_obj)
k = next(map_obj)
print(n + k)
Однако в языке Python есть механизм распаковки, что позволяет использовать множественное присваивание:
n, k = map(int, input().split())  # интерпретатор пытается распихать элементы последовательности по переменным
print(n + k)

Объекты класса map вычисляют значения достаточно лениво. Мы запоминаем лишь две ссылки: на функцию и на итератор второго аргумента. В тот момент, когда нас спрашивают какой элемент является следующим, лишь в этот момент мы считаем следующий элемент из нашего итератора, и затем применяем ту функцию, которую нам передали. Можно сделать то же самое используя обычный генератор:
x = input().split()
print(x)
n, k = (int(i) for i in x)
print(n + k)

filter

Класс filter принимает в конструктор также два аргумента: функция и последовательность, элементы которой мы хотим отфильтровать. Наша функция должна возвращать True, если элемент последовательности нам подходит и False, если он нам не подходит. Давайте попробуем считать список целых чисел и оставить только те, которые являются четными:
x = input().split()
xs = (int(i) for i in x)

def even(x):
 return x % 2 == 0

evens = filter(even, xs)
for i in evens:
 print(i)
Конструктор класса filter возвращает нам filter object, который является итератором. Если вы захотите пользоваться итераторами, то вероятнее всего вам нужен конструктор класса list. Если в конструктор класса list передать итератор, то он попробует собрать все элементы из данного итератора и поместить их внутрь одного списка.
x = input().split()
xs = (int(i) for i in x)

def even(x):
 return x % 2 == 0

evens = list(filter(even, xs))
print(evens)

Лямбда-функции

Лямбда-функции - это всего лишь синтаксис для создания новых объектов-функций.
x = input().split()
xs = (int(i) for i in x)

evens = list(filter(lambda x: x % 2 == 0, xs))
print(evens)

Сортировка списка по суммарной длине имени:


То же, но через лямбда-функцию:


Использование стандартных операторов языка Python в качестве функций

import operator as op

print(op.add(4, 5))  # 9
print(op.mul(4, 5))  # 20
print(op.contains([1, 2, 3], 4))  # False


# достать элемент коллекции

x = [1, 2, 3]
f = op.itemgetter(1)  # f(x) == x[1]
print(f(x))  # 2

y = {"123": 3}
g = op.itemgetter("123")
print(g(y))  # 3


# достать атрибут объекта
p = op.attrgetter("sort")  # p(x) == x.sort
print(p([]))  # <built-in method sort of list object at 0x013144B8>

Сортировка списка по последнему элементу в каждом кортеже:


Когда мы используем -1 элементы, мы берем всегда последний элемент из кортежа или списка.

Функция partial

Функция partial позволяет нам запомнить некоторые аргументы с которыми мы хотели бы вызвать функцию и возвращает функцию, в которую нам эти аргументы передавать больше не нужно.
from functools import partial

x = int("1101", base=2)
print(x)  # 13

int_2 = partial(int, base=2)
x = int_2("1101")
print(x)  # 13

Сортировка списка по последнему элементу в каждом кортеже:



Комментариев нет:

Отправить комментарий