- Функции и аргументы
- Область видимости
- Filter, map, reduce
- lambda функции
- Функции высшего порядка
- Homework
- Cursed questions
-
Функция - это исполняемый фрагмент кода, который можно переиспользовать множество раз. Функции возвращают какое-либо значение. Синтаксис функции начинается с ключевого слова
def, после идет название функции, потом в скобках список аргументов, две точки и на следующей строке с отступом сам код функции. Функция возвращает значение при помощи ключевого словаreturn:def find_lists_intersection(first_list, second_list): """Функция для нахождения элементов, которые есть и в первом и во втором списках. Возвращает список общих элементов. """ intersection = list() # инициализируем пустой список для общих элементов for i in first_list: # проходимся по всем элементам первого списка if i in second_list: # если элемент есть во втором списке intersection.append(i) # то добавляем его в пересечение return intersection # возвращаем список общих элементов
Данная функция находит общие элементы из двух списков(пересечение) и возвращает новый список. Вызывается она следующим образом:
list_intersect = find_lists_intersection([1, 2, 3, 3], [2, 3, 3, 4]) print(list_intersect) # [2,3,3]
Для вызова функции сначала пишется ее имя, потом передается список аргументов(в нашем случае это [1, 2, 3, 3], [2, 3, 3, 4]) в круглых скобках, количество передаваемых аргументов должно быть равно количеству аргументов при объявлении функции. Функции позволяют нам писать код только один раз и переиспользовать его множество раз, например можно найти пересечение сразу 4х списков при помощи уже написанной логики:
list_1 = [1,2,3,4,5,6,7,8] list_2 = [3,4,5,6,7,8,9,0] list_3 = [5,6,7,5,5,5,6,7] list_4 = [1,2,3,5,7,8,9,5] list_12_intersect = find_lists_intersection(list_1, list_2) # [3, 4, 5, 6, 7, 8] list_34_intersect = find_lists_intersection(list_3, list_4) # [5, 7, 5, 5, 5, 7] intersect = find_lists_intersection( list_12_intersect, list_34_intersect ) print(intersect) # [5, 7]
Функция, при ее вызове возвращает какое-либо значение, как только мы вызвали функцию с переданными ей аргументами(как тут
find_lists_intersection(list_1, list_2)), она уже перестает быть функцией и становится значением(в нашем случае она превращается в список), зная это, мы можем переписать предыдущий код:list_1 = [1,2,3,4,5,6,7,8] list_2 = [3,4,5,6,7,8,9,0] list_3 = [5,6,7,5,5,5,6,7] list_4 = [1,2,3,5,7,8,9,5] print(find_lists_intersection( find_lists_intersection(list_1, list_2), # здесь у нас уже значение [3, 4, 5, 6, 7, 8] find_lists_intersection(list_3, list_4) # и тут тоже уже значение [5, 7, 5, 5, 5, 7] )) # [5, 7]
-
Для удобства чтения, к аргументам в функциях стараются добавлять аннотации, а также возвращаемое значение, например:
# добавили аннотации типов, а также тип возвращаемого значения def find_sum(a: int, b: int) -> int: return a + b
Так же можно переписать и функцию по нахождению пересечения 2х листов:
from typing import List def find_lists_intersection(first_list: List[any], second_list: List[any]) -> List[any]: """Функция для нахождения элементов, которые есть и в первом и во втором списках. Возвращает список общих элементов. """ intersection = list() for i in first_list: if i in second_list: intersection.append(i) return intersection
В нашем примере аргументами являются коллекции, для их аннотации используются специальные типы из модуля
typing. Так как список может содержать элементы любого типа, то указываетсяList[any], если бы функция принимала только списки целых чисел, то указали быList[int], если бы списки строк, то List[str]. В модулеtypingесть множество различных аннотаций, их нужно подбирать по случаю и лучше пользоваться документацией или гуглить. В версиях python старше 3.8, типыlist,tuple,set,dictможно указывать без использования модуляtyping, то есть писать простоlist[int](с маленькой буквы) в старших версиях можно, однако в младших версиях это приведет к ошибке. -
Аргументы в функцию можно передавать так как мы передавали и раньше:
find_lists_intersection([1, 2, 3],[1, "2", 3])
а можно, используя именованные аргументы, то есть сразу при передачи указывать названия аргументов, которые мы передали:
def find_lists_intersection(first_list: List[any], second_list: List[any]) -> List[any]: ... find_lists_intersection([1, 2, 3],[1, "2", 3]) find_lists_intersection(first_list=[1, 2, 3],second_list=[1, "2", 3]) find_lists_intersection(second_list=[1, "2", 3], first_list=[1, 2, 3])
Эти 3 вызова функции абсолютно одинаковы, в первом случае мы просто передали значения, во втором указали их имена, а в третьем сначала передали первый список, а потом второй. Работать с именованными аргументами намного удобнее чем без них, так как разработчик сразу видит, что он передал в функцию. Часто названия аргументов говорят о них больше, чем документация, например именованный аргумент
sepв процедуреprint(), из его названия следует что мы передаем разделитель(separator). -
Давайте напишем еще одну небольшую функцию, она должна принимать 2 значения, ставить между ними какой-то разделитель и возвращать одну строку, где 2 значения разделены разделителем:
def separate_values(first_value: any, second_value: any, separator: any) -> str: return f"{first_value}{separator}{second_value}" string_1 = separate_values(1,2,", ") # "1, 2" string_2 = separate_values(separator="!!!", first_value=2, second_value=1) # "2!!!1" string_3 = separate_values(1,2) # ошибка! string_3 = separate_values(first_value=2, second_value=1) # ошибка!
Первые два вызова функции корректны, однако два других вызовут ошибку, потому что мы не передали значение аргумента
separator. Однако было бы неплохо, если бы, например, по умолчанию проставлялся какой-тоseparator, например", ", как в процедуреprint(). Для таких целей существуют аргументы по умолчанию, задаются они прямо в объявлении функции, в виде присвоения:def separate_values( first_value: any, second_value: any, separator: any = ", ") -> str: return f"{first_value}{separator}{second_value}" string_3 = separate_values(1,2) # "1, 2" string_3 = separate_values(first_value=2, second_value=1) # "1, 2"
Аргументы со значением по умолчанию при объявлении функции должны быть только ПОСЛЕ аргументов без значений по умолчанию, то есть, написать так:
def separate_values( separator: any = ", ", first_value: any, second_value: any ) -> str:
будет неправильно и вызовет ошибку, а так:
def separate_values( first_value: any, second_value: any, separator: any = ", " ) -> str:
Будет верно. Аргументы со значениями по умолчанию следуют после аргументов без значения по умолчанию.
-
Наша функция
separate_valuesсейчас работает следующим образом: принимает 2 аргумента и разделяет их. На самом деле, такая функция мало чем может помочь в разработке. Намного лучше было бы, если бы количество аргументов, принимаемых функцией было неограниченно. Специально для таких целей в Python есть знак*. Он позволяет обращаться к бесконечным аргументам, как к одному кортежу переменных. Давайте перепишем нашу функцию:def separate_values( separator: any = ", ", *values # добавили бесконечные аргументы ) -> str: clear_values = [] for value in values: # превращаем все нестроковые типы в строковые clear_values.append(str(value)) return separator.join(clear_values) # превращаем список в строку и возвращаем print(separate_values("!!!", 1,2,3,4)) # 1!!!2!!!3!!!4
работает это следующим образом: при вызове функции первое значение отдается аргументу
separator, остальные уходят в кортеж values. Если бы мы написали по-другому:def separate_values( *values, # бесконечные аргументы переместились до обычных аргументов separator: any = ", " ) -> str: clear_values = [] for value in values: clear_values.append(str(value)) return separator.join(clear_values) print(separate_values("!!!", 1,2,3,4)) # !!!, 2, 3, 4, 5, 6 print(separate_values(separator="!!!", 1,2,3,4)) # 1!!!2!!!3!!!4
то до сепаратора было бы невозможно достучаться без явного указания его имени, как в процедуре
print -
(extra) Кроме того передавать в функции можно и просто словари, но каждый элемент как аргумент, например:
agrs = [1,2,3,4,5,6] print(*args) #1, 2, 3, 4, 5, 6 print(*args, sep="! ") #1! 2! 3! 4! 5! 6
В данном примере мы передали в процедуру
printкаждый элемент коллекцииargs, но в качестве отдельного элемента. Сравните:agrs = [1,2,3,4,5,6] print(args) # [1,2,3,4,5,6] print(*args) # 1,2,3,4,5,6
Знак
*написанный перед коллекцией при передаче ее в качестве аргумента распаковывает данную коллекцию и передает каждый ее элемент в качестве отдельного аргумента в функцию. -
Множественные именованные аргументы. Часто в функциях python можно увидеть следующую запись:
def separate_values(*values, **format_items): ...
**перед аргументом значит, что этой функции можно передавать бесконечное количество именованных аргументов:def separate_values(*values, **format_items): separator = format_items.get("separator", "") end = format_items.get("end", "") clear_values = [] for value in values: clear_values.append(str(value)) return separator.join(clear_values) + end separate_values(1,2,3,4, separator="$$$", end="!") # 1$$$2$$$3$$$4!
Доступ к переданным именованным аргументам происходит через словарь
format_items. -
(extra) Распаковка словарей. Можно передавать словари в качестве именованных аргументов:
kwargs = {"sep": " ! ", "end": "?"} # kwargs расшифровывается как key word arguments args = [1,2,3,4,5] print(*args, **kwargs) # можно сделать так и вывод будет: 1 ! 2 ! 3 ! 4 ! 5? print(args, kwargs) # вывод будет другим: [1, 2, 3, 4, 5] {'sep': ' ! ', 'end': '?'}
-
Область видимости в программировании - это места в программе, где доступны объявленные переменные. Локальные переменные доступны только в определенных примерах, например, если мы в файле
1.pyобъявим переменнуюname = "Alex", то в другом файле2.pyэта переменная доступна не будет. То же самое работает и с функциями, только немного сложнее. -
Локальные переменные - это переменные, которые доступны только в пределах функции, например:
def do_smth(): a = 1 print(a) # вызывать эту переменную в функции - это нормально print(a) # но тут это вызовет ошибку, потому что не видит переменную a
-
Глобальные переменные - видны во всех функциях и вообще во всем файле, например:
GLOBAL_R = 10 def do_smth(): print(GLOBAL_R) do_smth() # 10
однако изменять глобальные переменные в локальном окружении нельзя, например:
GLOBAL_R = 10 def do_smth(): GLOBAL_R = 8 # теперь это другая переменная, в пределах функции print(GLOBAL_R) print(GLOBAL_R) # 10 do_smth() # 8 print(GLOBAL_R) # 10
Наша глобальная переменная
GLOBAL_Rпосле вызова функции никак не поменялись, хотя в функции мы ей присваивали новое значение8. Суть в том, что при таком присваивании в пределах функции у нас создается новая переменнаяGLOBAL_Rлокальная только для функции do_smth, и эта локальная переменная никак не влияет на глобальную. Для того чтобы присвоение в функции влияло на глобальную переменную существует ключевое словоglobal, используется оно следующим образом:GLOBAL_R = 10 def do_smth(): global GLOBAL_R # говорим что мы хотим изменить глобальную переменную GLOBAL_R = 8 # теперь мы меняем именно глобальную переменную print(GLOBAL_R) print(GLOBAL_R) # 10 do_smth() # 8 print(GLOBAL_R) # 8
Синтаксис Python позволяет изменять глобальные переменные, однако лучше так не делать и работать через аргументы функции.
-
Глобальные переменные настолько опасны для программ, что было приняло писать все названия глобальных переменных в верхнем регистре, как в нашем примере. Таким образом вы кричите вашим коллегам о том, что эту переменную лучше не трогать - это опасно.
-
Несмотря на природу области видимости, со сложными структурами данных все еще остаются определенные проблемы, например:
from typing import List def do_smth(some_list: List[any]) -> None: some_list[0] = 100 some_list.pop() int_list = [1,2,3] print(int_list) # [1, 2, 3] do_smth(int_list) print(int_list) # [10, 2]
В этом примере можно увидеть, что список
int_listпоменялся, хотя явно мы его не изменяли. Тут все еще работает ссылочная модель Python, чтобы избежать этого можно передавать копию листа:from typing import List def do_smth(some_list: List[any]) -> None: some_list[0] = 100 some_list.pop() int_list = [1,2,3] print(int_list) # [1, 2, 3] do_smth(int_list) print(list(int_list)) # [1, 2, 3]
Таким образом будет меняться копия, но не сам первоначальный лист.
-
Функции могут быть вложены друг в друга, например:
# объявили функцию def my_sum(a, b): # и функцию внутри функции def to_int(arg): return int(arg) return to_int(a) + to_int(b) my_sum("4", "5") # так будет работать to_int("4") # Так будет ошибка
Здесь мы объявили функцию внутри функции, самая внешняя функция(
my_sum) будет доступна во всем файле, однако функция вложенная(to_int) будет доступна только в пределах функцииmy_sum.
-
Над элементами коллекций можно делать различные операции, можно делать их в цикле
for, однако это не самый удобный способ. В Python есть инструменты для различных преобразований над коллекциями: для приведения коллекции к одному числу, для изменения коллекции, для фильтрации коллекции. Разберем из по порядку. -
reduce- функция в Python позволяющая свести все элементы коллекции к одному числу. Например сумме всех элементов или произведению. Для того, чтобы использовать эту функцию, сначала нужно написать функцию, которая будет выполнять преобразование. Эта функция будет принимать 2 аргумента - аккумулятор и текущий элемент, например для того, в случае нахождения суммы всех элементов функции, аккумулятором будет переменная, к которой будет прибавляться каждый текущий элемент. Для операции сложения всех элементов можно написать следующую функцию:def find_sum(acc: int, current: int) -> int: return acc + current
Первым всегда должен идти аккумулятор, а вторым текущий элемент. Далее можно эту функцию использовать:
from functools import reduce int_list = [1,2,3,4,5,6] int_sum = reduce(find_sum, int_list) print(int_sum) # 21
Функция
reduceне представлена в стандартном наборе функций Python, потому ее нужно брать из отдельного модуляfunctools. В данном примере происходит следующее:- Функция
reduceпринимает функцию обработкиfind_sumи коллекцию - Далее
reduceпод капотом заводит аккумулятор, присваивая ему значение первого элемента коллекции reduceпередает в рабочую функциюfind_sumаккумулятор и следующее значение в коллекции- Срабатывает рабочая функция
- Аккумулятору присваивается результат рабочей функции
- Если это был не последний элемент, то возвращаемся к п. 3
reduceвозвращает аккумулятор как результат своей работы. Вот еще несколько примеров использованияreduce: нахождение произведения всех элементов коллекции:
from functools import reduce def find_multiply(acc: int, current: int) -> int: return acc * current int_list = [1,2,3,4,5,6] int_multiply = reduce(find_multiply, int_list) print(int_multiply) # 720
Пример работы с более сложными структурами:
from functools import reduce def find_multiply(acc: int, current: int) -> int: age = current.get("age") return acc * age users = [ { "name": "Alex", "age": 22, }, { "name": "Bob", "age": 10, }, { "name": "Alice", "age": 20, }, ] int_multiply = reduce(find_multiply, users, 1) # последний аргумент - это инициализатор # инициализатор передается, если мы хотим, чтобы начальным значением аккумулятора # был не первый элемент коллекции, а какое-то другое значение # в данном случае это удобно, так как первый элемент коллекции # у нас словарь, а сложить его с int нельзя. print(int_multiply) # 4400
Функция
reduceявляется довольно сложной в понимании, потому на практике вместо нее используют обычные циклы. Она довольно редко используется, потому она даже вынесена в отдельный модульfunctools. К тому же Python и так предоставляет встроенные понятные функции для агрегации коллекций:sum- нахождение суммы всех элементов коллекции:int_list = [1,2,3] print(sum(int_list))
all- возвращает True, если все элементы коллекции имеют значение True:bool_list_1 = [True, False, False] bool_list_2 = [True, True, True] print(all(bool_list_1)) # False print(all(bool_list_2)) # True
any- возвращает True, если хотя бы один элемент коллекции имеет значение True:bool_list_1 = [True, False, False] bool_list_2 = [False, False, False] print(all(bool_list_1)) # True print(all(bool_list_2)) # False
len- возвращает количество элементов списка. Да, это тоже агрегация.max- находит самый большой элемент в коллекцииmin- находит самый маленький элемент в коллекции.
- Функция
-
функция
mapтакже принимает функцию и коллекцию, но в отличаи отreduce, возвращает новую коллекцию, а не сводит к одному значению. Применяется для быстрого изменения коллекции или приведения типов в ней, например:# как и для reduce, сначала завели функцию def cast_to_int(arg: any) -> int: return int(arg) print(cast_to_int("5")) # 5 trash_list = ["1", 2, "3", 4] clear_list = list( map(cast_to_int, trash_list) ) print(clear_list) # [1,2,3,4]
Можно и усложнить логику, например умножить каждый элемент на 5, для этого просто нужно переписать рабочую функцию:
def cast_to_int_and_mul_5(arg: any) -> int: mul_arg = int(arg) * 5 return mul_arg print(cast_to_int_and_mul_5("5")) # 25 trash_list = ["1", 2, "3", 4] clear_list = list( map(cast_to_int_and_mul_5, trash_list) ) print(clear_list) # [5,10,15,20]
При помощи
mapможно обрабатывать более сложные структуры, например получить только список лет всех пользователей:def get_age(arg: any) -> int: return arg.get("age") users = [ { "name": "Alex", "age": 22, }, { "name": "Bob", "age": 10, }, { "name": "Alice", "age": 20, }, ] ages = map(get_age, users) print(ages) # [22, 10, 20]
Функция
mapна самом деле более часто применяется чемreduce, потому она есть в стандартном пространстве имен Python. -
Для фильтрации элементов в списке или другой коллекции удобно использовать встроенную функцию
filter, она работает какmap, то есть принимает сначала функцию фильтрации, а потом коллекцию, которую нужно отфильтровать, возвращает новый список отфильтрованных по определенному признаку элементов. Функция фильтрации должна возвращать булевое значение(TrueилиFalse) например можно получить все элементы, больше 10:def get_more_10(arg: int) -> bool: return arg > 10 get_more_10(11) # True get_more_10(9) # False int_list = [7,8,9,10,11,12,4,5,22] more_10_list = list(filter(get_more_10, int_list)) print(more_10_list) # [11, 12, 22]
Или работать с более сложными структурами, например получить всех пользователей, которым есть 18:
def get_18(user): return user.get("age") >= 18 users = [ { "name": "Alex", "age": 22, }, { "name": "Bob", "age": 10, }, { "name": "Alice", "age": 20, }, ] adult_users = list(filter(get_18, users)) print(adult_users) # [{'name': 'Alex', 'age': 22}, {'name': 'Alice', 'age': 20}]
Функция
filterприменяется также довольно часто, потому она есть в стандартном пространстве имен Python.
- Бывают случаи, когда небольшая функция нужна только один раз. Для таких целей в языке существую специальные конструкции, называемые лямбда-функциями. Они хорошо подходят для функций
mapиfilter. Объявление лямбда-функций происходит следующим образом: сначала пишется ключевое словоlambda, потом список аргументов через запятую, двоеточие и исполняемый код самой функции. Например вот код, который получает из списка словарей значенияpriceкаждого элемента:Здесь# список словарей цен и названий mark_price_map = [ { "mark": "cucumber", "price": 10, }, { "mark": "tomato", "price": 8, }, { "mark": "banana", "price": 10 } ] # делаем из словаря лист цен с помощью лямбды price_list = map(lambda p: p.get("price"), mark_price_map) amount = sum(price_list) # применяем функцию sum print(amount)
lambda p: p.get("price")является лямбда-функцией. Эта функция получает значение из словаря по ключу, возвращает его и применяется при помощиmapк каждому элементу списка словарей. - Особенности lambda - присвоение в таких функциях запрещено, выполняют они только небольшие действия, типа получение данных из словаря по ключу, обратиться к ним по имени нельзя, потому иногда их еще называют анонимные функции
- Хотя обращение к лямбда-функции по имени невозможно, можно присвоить ее в переменную и использовать как обычную функцию, например:
simple_func = lambda a, b: a + b # объявили лямбду и положили ее в переменную simple_func print(simple_func(5, 10)) # можно вызывать как обычную функцию
-
В Python каждая функция является объектом, как int или str. Пока функция не вызвана, она является объектом функции. Это значит, что функции можно передавать другим функциям, например:
def simple_sum(a, b): return a + b def square(func, a, b): return func(a, b) * func(a, b) print(square, 5, 4) #81
В данном примере мы объявили функцию
simple_sumкоторая складывает 2 переменные, после мы объявили функциюsquare, которая принимает функцию и передаваемые ей аргументы. Функция square вызывает внутри себя переданную ей функциюsimple_sumи ее результат возводит в квадрат. -
Напишем еще один пример:
def apply_to_each(func, collection): result = list() for element in collection: result.append( func(element) ) return result some_list = [1,2,3,"4", 4, "10"] int_list = apply_to_each(int, some_list)
Мы написали свою реализацию встроенной функции
map, эта функция так же применяет переданную функцию к каждому элементу последовательности. -
Функции как
map,filterи любые функции, которые принимают другие функции называются функциями высшего порядка. -
Кроме принятия функций как аргументов, функции высшего порядка могут и отдавать функции как результат, например:
def get_sum(): # функция def simple_sum(a, b): # функция сложения к которой не получить доступ из вне return a + b return simple_sum # возвращаем как результат внутреннюю функцию smpl = get_sum() # получаем функцию сложения result = smpl(4,5) # вызываем функцию сложения и получаем результат print(result) # 9
Функция
get_sumявляется функцией высшего порядка. -
Внутренняя функция видит все пространство имен внешней функции, то есть:
def get_sum(): b = 5 # объявили здесь b и присвоили 5 def simple_sum(a): # убрали b как аргумент внутренней функции return a + b # имеем доступ к внешней переменной b return simple_sum # возвращаем внутреннюю функцию print(get_sum()(10)) # 15
В последней строчке мы получаем функцию сложения
get_sum(), а после вызываем результат этой функции, передавая значение 10. Внутренняя функция будет всегда слаживать переданный аргумент с 5-кой. Можно это изменить, передаваяbкак аргумент во внешнюю функцию:def get_sum(b): # передаем теперь b как аргумент def simple_sum(a): return a + b return simple_sum sum_5 = get_sum(5) # предали 5 и получили как результат функцию, которая складывает все с 5-кой sum_10 = get_sum(10) # предали 10 и получили как результат функцию, которая складывает все с 10-кой print(sum_5(30)) # 35 print(sum_10(30)) # 40
Теперь у нас есть возможность создавать новые функции только в одну строку. Такой прием в программировании называется замыканием
-
Как мы убедились, переданные функции можно исполнять внутри других функций, как делает, например, функция
map, напишем одну такую функцию:def add_separator(func, *args): # функция которая принимает другую функцию, исполняет ее и возвращает результат вместе с разделителями separator = "*" * 80 return separator + "\n" + func(*args) + "\n" + separator # возвращает результат исполнения функции def replace_spaces(some_string: str, char: str = "_"): # функция удаляет пробелы в строке и заменяет на нижнее подчеркивание по умолчанию return some_string.replace(" ", "_") format_str = add_separator(replace_spaces, "Hello World") # исполняем функцию replace_spaces через add_separator, который добавляет разделители print(format_str)
Далее сделаем так, чтобы эта функция возвращала тоже функцию:
def add_separator(func): def inner(*args): # объявили внутреннюю функцию, которая добавляет разделитель и возвращает результат separator = "*" * 80 return separator + "\n" + func(*args) + "\n" + separator return inner # возвращаем внутреннюю функцию def replace_spaces(some_string: str, char: str = "_"): return some_string.replace(" ", "_") format_str = add_separator(replace_spaces) # теперь возвращается функция result = format_str("Hello World") # Вызываем функцию и получаем результат print(result)
То, что мы сейчас сделали, называется декоратором функции. Он декорирует функционал изначальной функции. Такой прием в Python достаточно распространен, потому для этих целей придуман специальный синтаксис:
def add_separator(func): def inner(*args): separator = "*" * 80 return separator + "\n" + func(*args) + "\n" + separator return inner @add_separator # заменяет format_str = add_separator(replace_spaces) def replace_spaces(some_string: str, char: str = "_"): return some_string.replace(" ", "_") result = replace_spaces("Hello World") # теперь просто вызываем функцию print(result)
Теперь можно кастомизировать разделитель, для этого нужно добавить еще одну функцию-обертку:
def add_separator(sep = "*"): # еще одна обертка, которая получает разделитель def separate(func): # функция, которая оборачивает исполнение передаваемой функции def inner(*args): separator = sep * 80 # создаем разделитель, sep взяли из самой внешней функции return separator + "\n" + func(*args) + "\n" + separator return inner return separate @add_separator("_") # теперь можно передавать желаемый сепаратор def replace_spaces(some_string: str, char: str = "_"): return some_string.replace(" ", "_") result = replace_spaces("Hello World") # Вызываем функцию и получаем результат print(result)
Мы пришли от простой передачи функции в функцию к сложной структуре изменения работы функции, без изменения кода этой функции.
@add_separator("_")в примере можно заменить на более сложную структуру:func = add_separator("_")(replace_spaces) # заменили @add_separator("_") result = func("Hello World") print(result) # тот же самый результат что и прежде
-
Декораторы являются очень распространенной идеей в Python, многие фреймворки(Flask, FastAPI...) построены на декораторах и их использовании, частым примером использования декораторов в web-приложениях является: логгирование, проверка разрешений, проверка аутентификации, привязка обработчика к url и д.р.
- Что такое функция?
- Что такое область видимости функции?
- Как можно изменить глобальную переменную из функции?
- Что такое функции высшего порядка?
- Что такое замыкание? Напишите пример.
- Что такое декоратор? Напишите пример.
- Как сделать из обычного декоратора, декоратор с параметрами?