Функции подробно: значения по умолчанию, функции как объекты, *args, **kwargs и lambda
- Определять параметры со значением по умолчанию и вызывать функцию с именованными аргументами
- Понимать, что функция — объект — её можно сохранить в переменной и передать другой функции
- Писать функции, которые принимают переменное число аргументов, с помощью
*argsи**kwargs - Писать простую lambda и использовать её с
sorted(key=...)
Что мы изучим?
В теме чистого кода и отладки мы изучили основы: def, параметры, return. Сегодня изучим несколько возможностей, которые делают функции намного гибче:
- Значения по умолчанию (default values) и именованные аргументы (keyword arguments).
- Функции как объекты (Functions as Objects) — функция — такое же значение, как любое другое.
*args— сколько угодно обычных аргументов.**kwargs— сколько угодно именованных аргументов.- Lambda — короткая функция без имени.
Зачем это нужно?
print("a", "b", "c", sep="-")принимает сколько угодно аргументов плюс именованный аргумент. Как это устроено? Через*args.sorted(students, key=...)принимает функцию как параметр, чтобы знать, по чему сортировать.- FastAPI (темы бэкенда и баз данных) целиком построен на идее, что функция — объект: вы пишете функцию, а FastAPI «регистрирует» её как Endpoint и запускает, когда приходит запрос.
Объяснение с основ
Значения по умолчанию
Параметру можно дать значение, которое будет использовано, если аргумент не передан:
def greet(name, greeting="Hello"):
return greeting + ", " + namegreet("Dana") → "Hello, Dana". greet("Dana", "Hi") → "Hi, Dana".
Правило: параметры со значением по умолчанию идут после параметров без него.
Именованные аргументы
При вызове можно указать имя параметра, и тогда порядок уже не важен:
greet(greeting="Hey", name="Avi")Это очень читаемо, когда параметров много: create_user("dana", age=24, is_admin=False).
Функции как объекты
В Python функция — это объект, как число или список. Поэтому можно:
- Сохранить её в переменной:
say = greet. - Положить её в список или dict.
- Передать её как аргумент другой функции.
Критическая разница: greet (без скобок) = сама функция. greet("Dana") (со скобками) = её запуск и его результат.
greet — это рецепт: его можно сфотографировать и отправить подруге. greet("Dana") — это торт: результат выпечки по рецепту. Когда вы передаёте функцию другой функции, вы отправляете рецепт, чтобы та могла печь по нему, когда понадобится.
*args — переменное число аргументов
Звёздочка перед именем параметра говорит: «собери все обычные аргументы (по позиции) в tuple»:
def total(*numbers):
return sum(numbers)
total(1, 2) # numbers = (1, 2)
total(5, 10, 20, 1) # numbers = (5, 10, 20, 1)Имя args — соглашение. Работу делает звёздочка.
**kwargs — переменное число именованных аргументов
Две звёздочки: «собери все именованные аргументы в dict»:
def describe(**details):
for key, value in details.items():
print(key, "=", value)
describe(name="Dana", age=24, city="Haifa")
# details = {"name": "Dana", "age": 24, "city": "Haifa"}kwargs = keyword arguments.
Lambda
lambda — способ записать маленькую функцию в одну строку, без имени:
lambda x: x * 2Равносильно:
def double(x):
return x * 2- После
lambda— параметры. После:— одно выражение, которое и есть возвращаемое значение (безreturn). - Главное применение: передать короткую функцию как аргумент, вместо того чтобы определять отдельный
def. Чаще всего сsorted,min,max.
Синтаксис
# default value + keyword arguments
def func(required, optional="default"):
...
func("a")
func("a", optional="b")
# function as an object
f = func # no parentheses: the function itself
f("a") # call it through the new name
def apply(operation, value):
return operation(value) # call the function we received
# *args and **kwargs
def func(*args, **kwargs):
# args -> tuple of positional arguments
# kwargs -> dict of keyword arguments
...
# lambda
lambda parameters: expression
sorted(items, key=lambda item: item["price"])Первый пример кода
week-2/functions_deep.py:
def make_greeting(name, greeting="Hello", punctuation="!"):
return greeting + ", " + name + punctuation
print(make_greeting("Dana"))
print(make_greeting("Avi", "Good morning"))
print(make_greeting("Noa", punctuation="?"))
def shout(text):
return text.upper()
def whisper(text):
return text.lower() + "..."
def say(message, style):
return style(message)
print(say("Hello there", shout))
print(say("Hello there", whisper))Вывод:
Hello, Dana!
Good morning, Avi!
Hello, Noa?
HELLO THERE
hello there...Разбор кода построчно
| Строка | Объяснение |
|---|---|
def make_greeting(name, greeting="Hello", punctuation="!") | name обязателен. Два других необязательны и имеют значения по умолчанию. |
make_greeting("Dana") | Оба необязательных параметра берут значение по умолчанию. |
make_greeting("Avi", "Good morning") | Второй аргумент по позиции идёт в greeting. |
make_greeting("Noa", punctuation="?") | Именованный аргумент: пропускаем greeting (по умолчанию) и задаём только punctuation. |
def say(message, style): | style — параметр, который ожидает функцию. |
return style(message) | Запуск полученной функции. say не знает и не интересуется, что это за функция. |
say("Hello there", shout) | Передаём shout без скобок, то есть саму функцию. |
Ещё один пример
*args, **kwargs и lambda в маленькой настоящей системе. week-2/store_tools.py:
def order_total(*prices, discount=0):
subtotal = sum(prices)
return subtotal - subtotal * discount / 100
print(order_total(10, 20, 30))
print(order_total(100, 50, discount=10))
def build_user(username, **extra):
user = {"username": username}
for key, value in extra.items():
user[key] = value
return user
print(build_user("dana"))
print(build_user("avi", age=30, city="Haifa", is_admin=True))
products = [
{"name": "Laptop", "price": 3500},
{"name": "Mouse", "price": 80},
{"name": "Desk", "price": 900},
]
by_price = sorted(products, key=lambda product: product["price"])
for product in by_price:
print(product["name"], product["price"])
cheapest = min(products, key=lambda product: product["price"])
print("Cheapest:", cheapest["name"])Вывод:
60.0
135.0
{'username': 'dana'}
{'username': 'avi', 'age': 30, 'city': 'Haifa', 'is_admin': True}
Mouse 80
Desk 900
Laptop 3500
Cheapest: MouseРазбор:
def order_total(*prices, discount=0):—*pricesсобирает любое число цен в tuple.discountидёт после*prices, поэтому его можно передать только по имени (discount=10). Иначе Python не знал бы, где заканчиваются цены.build_user("avi", age=30, ...)—usernameполучает"avi", а всё остальное собирается вextraкак dict:{"age": 30, "city": "Haifa", "is_admin": True}.sorted(products, key=lambda product: product["price"])—sortedне умеет сравнивать словари.key— это функция, которая говорит ему: «для каждого товара используй для сравнения его цену». lambda вызывается по разу для каждого товара.min(..., key=...)— та же идея: «минимум по цене». Возвращается весь товар, а не только цена.
Типичная ошибка
Ошибка 1: передать результат вместо функции.
print(say("Hello", shout()))TypeError: shout() missing 1 required positional argument: 'text'shout() со скобками пытается запустить shout сразу, без аргумента. Когда передаёте функцию, пишите только имя: shout.
Ошибка 2: список или dict в качестве значения по умолчанию.
def add_item(item, cart=[]):
cart.append(item)
return cart
print(add_item("milk"))
print(add_item("bread"))['milk']
['milk', 'bread']Во втором вызове мы ожидали ['bread']! Значение по умолчанию [] создаётся один раз, при определении функции, и все вызовы используют один и тот же список (изменяемый — предыдущий урок!). Принятое исправление:
def add_item(item, cart=None):
if cart is None:
cart = []
cart.append(item)
return cartОшибка 3: слишком сложная lambda.
result = sorted(users, key=lambda u: (0 if u["active"] else 1, -u["score"], u["name"].lower()))Это работает, но через неделю никто не поймёт (KISS!). Если lambda не читается с первого взгляда, напишите def с понятным именем.
Ошибка 4: параметр со значением по умолчанию перед обязательным параметром.
def greet(greeting="Hello", name):SyntaxError: parameter without a default follows parameter with a defaultПрактика
Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.
Упражнение 1: Значения по умолчанию и именованные аргументы
Напишите функцию make_greeting(name, greeting="Hello", punct="!").
print(make_greeting("Dana"))
print(make_greeting("Avi", "Good morning"))
print(make_greeting("Noa", punct="?"))Ожидаемый вывод:
Hello, Dana!
Good morning, Avi!
Hello, Noa?Показать решение
def make_greeting(name, greeting="Hello", punct="!"):
return greeting + ", " + name + punct
print(make_greeting("Dana"))
print(make_greeting("Avi", "Good morning"))
print(make_greeting("Noa", punct="?"))Hello, Dana!
Good morning, Avi!
Hello, Noa?В третьем вызове punct="?" передаётся по имени, поэтому можно пропустить greeting и оставить ему значение по умолчанию.
Упражнение 2: *args
Напишите функцию average(*numbers), которая получает любое количество чисел и возвращает среднее. Если не передано ни одного числа — выбрасывает ValueError с сообщением, показанным в ожидаемом выводе.
print(average(80, 90))
print(average(70, 80, 90, 100))
try:
average()
except ValueError as error:
print("error:", error)Ожидаемый вывод:
85.0
85.0
error: need at least one numberПоказать решение
def average(*numbers):
if len(numbers) == 0:
raise ValueError("need at least one number")
return sum(numbers) / len(numbers)
print(average(80, 90))
print(average(70, 80, 90, 100))
try:
average()
except ValueError as error:
print("error:", error)85.0
85.0
error: need at least one number*numbers собирает все аргументы по позиции в tuple.
Упражнение 3: **kwargs
Напишите функцию format_profile(name, **details), которая возвращает строку с именем, за которым следует каждая дополнительная деталь в формате key=value.
print(format_profile("Dana", age=24, city="Haifa"))
print(format_profile("Avi"))Ожидаемый вывод:
Dana: age=24 city=Haifa
Avi:Показать решение
def format_profile(name, **details):
line = name + ":"
for key, value in details.items():
line = line + " " + key + "=" + str(value)
return line
print(format_profile("Dana", age=24, city="Haifa"))
print(format_profile("Avi"))Dana: age=24 city=Haifa
Avi:**details собирает все именованные аргументы в dict.
Упражнение 4: lambda в сортировке
products = [("Laptop", 3500), ("Mouse", 80), ("Desk", 900)]- Отсортируйте по цене, от самого дешёвого к самому дорогому, и напечатайте каждый товар.
- Напечатайте название самого дорогого товара.
Ожидаемый вывод:
Mouse 80
Desk 900
Laptop 3500
Most expensive: LaptopПоказать решение
products = [("Laptop", 3500), ("Mouse", 80), ("Desk", 900)]
for name, price in sorted(products, key=lambda p: p[1]):
print(name, price)
most_expensive = max(products, key=lambda p: p[1])
print("Most expensive:", most_expensive[0])Mouse 80
Desk 900
Laptop 3500
Most expensive: Laptoplambda p: p[1] — короткая функция, которая возвращает цену, — по ней сравнивают sorted и max.
Упражнение 5: Изменяемое значение по умолчанию
def add_item(item, cart=[]):
cart.append(item)
return cart
print(add_item("milk"))
print(add_item("bread"))- Что будет напечатано во втором вызове и почему?
- Исправьте функцию.
Показать решение
- Напечатается
['milk', 'bread']. Значение по умолчанию[]создаётся один раз, при определении функции, — и все вызовы используют один и тот же список. - Используйте
Noneи создавайте новый список внутри функции:
def add_item(item, cart=None):
if cart is None:
cart = []
cart.append(item)
return cart
print(add_item("milk"))
print(add_item("bread"))['milk']
['bread']Итоги
- Значение по умолчанию:
def f(a, b=10). Параметры со значением по умолчанию — в конце. Никогда[]или{}по умолчанию (используйтеNone). - Именованный аргумент:
f(a=1, b=2). Читаемо, и порядок не важен. - Функция — объект:
f= функция,f()= её запуск. Её можно сохранить в переменной, в dict и передать как аргумент. *argsсобирает обычные аргументы в tuple.**kwargsсобирает именованные аргументы в dict.lambda x: expression— короткая функция без имени. Идеальна дляkey=вsorted/min/max. Если сложно — лучшеdef.