Функции. Свойства и параметры функций
Работа с функциями
Проблема в задаче №3
В задаче №3 про сотрудников мы рассмотрели несколько вариантов решения словари, списковые включения. Первый вариант решения задачи приведен в блоке кода ниже.
# словарь с сотрудниками
employees = {
'Alice' : 100000,
'Bob' : 99817,
'Carol' : 122908,
'Frank' : 88123,
'Eve' : 93121
}
# Пустой список топ-менеджеров
top_mgrs = []
# перебираем ключи и сравниваем значения зп с порогом 100к$
for name in employees:
if employees[name] >= 100000:
top_mgrs.append(name)
# вывод в консоль
print(f"Топ-менеджеры: {top_mgrs}")
Это решение хорошо работает для одного словаря. А что если требуется обработать несколько словарей? Тогда каждый раз необходимо повторять один и тот же код.
# Несколько словарей сотрудников
employees = {'Alice': 100000, 'Bob': 99817}
devs = {"Mike": 60000, "John": 45000}
# решение для employees
top_mgrs = []
for name in employees:
if employees[name] >= 100000:
top_mgrs.append(name)
# копия решения для devs
top_devs = []
for name in devs:
if devs[name] >= 100000:
top_devs.append(name)
Создание и вызов функции
Выходом из ситуации является создание функции. Функция
- это последовательность инструкций, возвращающая некое значение. Функции позволяют вызывать часть кода, который описан в ней.
Работа с функциями подразумевает несколько этапов:
- этап создания функции
- этап вызова функции
Для того, чтобы объявить функцию, необходимо:
- указать оператор
def
, - задать имя функции,
- передать аргументы в скобках (если они нужны).
В данном примере:
- оператор
def
задает начало функции, get_top_mgrs
- имя функции,staff
- аргумент функции.- оператор
pass
- пропуск действий
Для примера пусть функция возвращает значение аргумента staff
. Для возврата значения из функции необходимо использовать оператор return
, который указывает, когда функция должна завершить свою работу и вернуть результат. Тогда функция выполняет свою задачу: принимает данные и возвращает обработанные значения. print()
внутри функций, желательно не использовать!
Обратите внимание, что созданные нами функции являются такими же, как и встроенные print(), round(), type() и др. с одним только отличием, что встроенные функции хранятся внутри Python.
Перейдем к следующему этапу - вызов функции. Чтобы применить код, который написан внутри, необходимо написать имя функции и в скобочках записать значение параметра staff. Например, напишем строку "Сотрудники!". Ожидаемо, функция вернет строку. Для того чтобы увидеть результат на экране используем print().
# Создание функции
def get_top_mgrs(staff):
return staff
# Вызов функции
print(get_top_mgrs("Сотрудники!"))
Теперь эту функцию можно вызвать несколько раз с разным значением параметра.
# Создание функции
def get_top_mgrs(staff):
return staff
# Три вызова функции с разными параметрами
print(get_top_mgrs("Сотрудники!"))
print(get_top_mgrs("Разработчики!"))
print(get_top_mgrs("Менеджеры!"))
Функции очень важны в программировании. Они позволяют реализовать принцип разработки программного обеспечения Don't repeat yourself (принцип DRY), нацеленный на снижение повторения одинаковых операций в коде. Также функция является минимальной единицей программы, которую можно протестировать и выполнить другие операции над ней.
Имена функций
Имена переменных в Python обычно обозначают существительные. Тогда функция должна содержать указание на действие (get, add и др.) и то над чем, это действие совершается (get_value, divide_number).
Остальные правила повторяют правила записи имен переменных:
- имя функции должно начинаться с буквы;
- имя функции должно быть записано маленькими латинскими буквами;
- имя функции должно быть разделено нижним подчеркиванием
_
, если в названии переменной несколько слов.
Обратите внимание, что функция должна делать ровно одно действие. Например, вам нужно проделать над числом два или более арифметических действия: сложить add и умножить multiply. Тогда лучше создать две разные функции, чем создавать функцию add_and_multiply_numbers.
Решение задачи №3 в виде функции
Вернемся к решению задачи №3. Подготовим шаблон функции get_top_mgrs()
.
# словарь с сотрудниками
employees = {
'Alice' : 100000,
'Bob' : 99817,
'Carol' : 122908,
'Frank' : 88123,
'Eve' : 93121
}
# Создание функции
def get_top_mgrs(staff):
pass
# Вызов функции для словаря employees
print(get_top_mgrs(employees))
Теперь запишем решение задачи внутри функции. Обратите внимание, что внутри функции действует аргумент staff
, который обозначает обрабатываемый словарь. Поэтому внутри функции все операции будут проводится со staff
, а не c employees
.
# словарь с сотрудниками
employees = {
'Alice' : 100000,
'Bob' : 99817,
'Carol' : 122908,
'Frank' : 88123,
'Eve' : 93121
}
# Создание функции
def get_top_mgrs(staff):
top_mgrs = []
for name in staff:
if staff[name] >= 100000:
top_mgrs.append(name)
return top_mgrs
# Вызов функции для словаря employees
print(get_top_mgrs(employees))
Функция get_top_mgrs()
вызвана для словаря employees
. Интерпретатор передает содержимое employees
в аргумент staff
и дальше работает с ним. В результате функция возвращает результат - список топ-менеджеров. Также это будет работать и для нескольких словарей.
# словарь с сотрудниками
# Несколько словарей сотрудников
employees = {'Alice': 100000, 'Bob': 99817}
devs = {"Mike": 60000, "John": 45000}
# Вызов функции для словарей
print(get_top_mgrs(employees), get_top_mgrs(devs))
Свойства параметров функции
Функции могут выполнять различные задачи внутри кода. Ниже приведено несколько простых примеров функций. В общем случае функция принимает аргументы, обрабатывает их и возвращает результат.
# Функция для проверки четности числа.
def is_odd(x):
return x % 2 == 0
# Функция целочисленного деления
def divide(val, oth_val):
int_div = 0
while val > 0:
val -= oth_val
int_div += 1
return int_div
# Функция для расчета площади трапеции
def trapezoid_s(a, b, h):
return h * (a+b) / 2
Рассмотрим подробнее последнюю функцию trapezoid_s()
.
Именованные и позиционные параметры
Напишем вызов функции trapezoid_s()
для расчета площади трапеции с заданными значениями.
Обратите внимание, что параметров у функции может быть много. Если мы изменим порядок параметров при вызове, ответ будет другим.
Для определения порядка вызова параметров в Python используют позиционные и именованные параметры.
- Позиционные - Параметры функции, которым соответствует конкретный номер позиции.
- Именованные - Если параметры именованные, то их порядок при вызове не важен.
Ниже рассмотрен пример вызова с позиционными и именованными параметрами при заданной функции trapezoid_s()
.
# Позиционные параметры
S = trapezoid_s(8, 4, 10)
print(S)
# Именованные параметры
S = trapezoid_s(b=4, h=10, a=8)
print(S)
При создании функции можно указать, чтобы при вызове некоторые параметры указывались явно. Для этого перед параметрами необходимо поставить символ *
как отдельный параметр.
def trapezoid_s(a, b, *, h):
return h * (a+b) / 2
# параметр h обязательно нужно вызвать с именем
S = trapezoid_s(8, 4, h=10)
print(S)
Обратите внимание! В случае некорректного вызова параметров могут происходить ошибки следующего рода:
- несоответствующее число параметров;
- повтор параметров;
- именованные параметры перед позиционными.
# Ошибки
S = trapezoid_s() # параметры отсутствуют
S = trapezoid_s(8, 4) # параметров недостаточно
S = trapezoid_s(8, 4, 10, 3) # параметров слишком много
S = trapezoid_s(a=8, b=4, a=10) # параметр a повторяется
S = trapezoid_s(8, b=4, 10) # именованные параметр перед позиционным
S = trapezoid_s(a=8, b=4, h=10, x=3) # неизвестные параметры
Достоинством именованных параметров является то, что не ломается логика вызова следующих функций при введении нового параметра! Рассмотрим пример вставки нового параметра param
в середине на этапе создания функции.
# Плохой пример вызова позиционных параметров
param = 2
def trapezoid_s(a, b, param, h):
return h * (a + b) / 2 ** param
# в каждом вызове нужно указать param на своем месте
trapezoid_s(8, 4, param, 10)
trapezoid_s(7, 2, param, 4)
Теперь пример вызова для именованных параметров.
# Хороший пример вызова именованных параметров
def trapezoid_s(a, b, *, param, h):
return h * (a + b) / 2 ** param
# не важно где находится параметр param
trapezoid_s(8, 4, h = 10, param = 3)
trapezoid_s(7, 2, param = 3, h = 4)
Параметры по умолчанию
В функции встречаются параметры, которые не нужно вызывать каждый раз. Для упрощения вызова можно использовать параметры по умолчанию. Если при вызове функции параметр не передан, то берется тот, который указан при создании функции.
def trapezoid_s(a, b, *, h, param=1):
return h * (a + b) / 2 ** param
# если парам при вызове отсутствует, то он примет значение 1
S = trapezoid_s(8, 4, h=10)
print(S)
# но все также param можно передать при вызове
S = trapezoid_s(8, 4, h=10, param=2)
Обратите внимание на возможные логические ошибки. Может возникнуть потребность передать пустой список в качестве значения параметра по умолчанию.
Рассмотрим пример. Для начала создадим функцию, которая добавляет элемент к списку в качестве параметра. Если параметр не указан, то в функции будет использован пустой список. Однако если повторно вызвать с параметром по умолчанию то список не будет пустым, а будет наполнен данными из прошлых вызовов.
def add_element_to_list(element, list_to_add=[]):
list_to_add.append(element)
return list_to_add
# первый вызов с параметром по умолчанию
lst = add_element_to_list(element=1)
print(lst) # [1]
# второй вызов - параметры переданы именовано
add_element_to_list(element=7, list_to_add=lst)
print(lst) # [1, 7]
# третий вызов с параметром по умолчанию
# исходный список не пуст!
new_lst = add_element_to_list(element='a')
print(new_lst) # [1, 7, 'a']
Учитывайте, что значения по умолчанию вычисляются в точке определения функции. Поэтому изменяемый объект в качестве параметра по умолчанию использовать не следует!
Решить проблему можно заменив изменяемый элемент на None
, а внутри функции следует сделать проверку на равенство параметра list_to_add и None. В таком случае функция создаст список.
def add_element_to_list(element, list_to_add=None)
if list_to_add is None:
list_to_add = []
list_to_add.append(element)
return list_to_add
Распаковка и запаковка параметров
При передаче списка с соответствующим количеством элементов можно распаковать их в качестве параметров использовав символ *
. Словарь можно распаковать по ключам, указав перед параметром **
.
# список параметров
param_lst = [8, 4, 10]
# распаковка с помощью *
S = trapezoid_s(*param_lst)
print(S)
# словарь параметров
param_dict = {'a': 8, 'b': 4, 'h': 10}
# распаковка с помощью **
S = trapezoid_s(**param_dict)
print(S)
Обратите внимание, что программисту может быть не всегда известно точное количество распакованных параметров из коллекции, поэтому стабильнее вызывать функции, используя именованные параметры.
Параметры также можно и запаковывать. Отличным примером такой функции является print()
. Обращали ли вы внимание, что функция print()
может принимать любое количество параметров?
Произвольное число параметров определяется на этапе создания функции. В Python выделяют:
- Произвольное число позиционных параметров
*args
. Все параметры будут запакованы в кортежargs
. - Произвольное число именованных параметров
**kwargs
. Все параметры будут запакованы в словарьkwargs
.
Создадим произвольную функцию print_them_all()
, которая будет выводить в консоль все запакованные параметры.
def print_them_all(*args, **kwargs):
for i, arg in enumerate(args):
print('позиционный параметр:', i, arg)
for key, value in kwargs.items():
print('именованный аргумент:', key, '=', value)
# вызов функции с позиционными параметрами
print_them_all(1, 2, 3, 4)
# вызов функции с позиционными и именованными параметрами
print_them_all(1, 2, 3, x=100, y=50)
Документирование и аннотация функции
Функцию, как полноценный элемент программы, желательно сделать более удобной для использования. Например, желательно создать для функции описание: для чего нужны функция, какие параметры можно передать и так и далее. Такой процесс называется документированием функции, которое можно задать при создании под именем в тройных кавычках """
.
Создадим описание для функции trapezoid_s()
.
def trapezoid_s(a, b, *, h):
"""
Функция для расчета площади трапеции.
a - нижнее основание,
b - верхнее основание,
h - высота.
"""
return h * (a + b) / 2 ** param
Для вывода документации можно воспользоваться функцией help()
.
Существуют правила как документация в функции должна быть оформлена. Sphinx
- это инструмент для создания документации на языке Python, который поддерживает автоматическое создание документации из соответствующего блока внутри функции. Например, в среде разработки Pycharm документация будет оформлена автоматически.
def trapezoid_s(a, b, *, h):
"""
Функция для расчета площади трапеции.
:param a: нижнее основание
:type a: int
:param b: верхнее основание,
:type b: int
:param h: высота.
:type h: int
:return: площадь трапеции
:rtype: float
"""
return h * (a + b) / 2 ** param
В Python нет переменных как в других языках, где переменные имеют тип и значение; у него есть имена, указывающие на объекты, которые знают их тип.
После реализации PEP3107 можно создавать аннотацию типов данных, то есть фактически указать тип параметра и тип возвращаемого результата функции. Возвращаемое значение указывается после стрелки ->
.
Аннотация выводит информацию о функции при наведении курсора на имя функции во время вызова. Указать тип данных в аннотации можно следующим способом:
param: str
- указание типа данных;param: str = "hello"
- указание типа данных для параметра по умолчанию;def func() -> str:
- указание типа данных для возвращаемого значения.
Пример оформление аннотации для функции trapezoid_s()
.
def trapezoid_s(a: int, b: int, *, h: int = 1) -> float:
"""
Функция для расчета площади трапеции.
:param a: нижнее основание
:type a: int
:param b: верхнее основание,
:type b: int
:param h: высота.
:type h: int
:return: площадь трапеции
:rtype: float
"""
return h * (a + b) / 2 ** param
S = trapezoid_s(2, 2)
print(S)
Можно конкретизировать тип данных для параметра a
. Несмотря на синтаксическую ошибку, код будет работать. Более того, функция будет вызвана, даже если передать параметр с другим типом данных.
def trapezoid_s(a: "lower base", b: int, *, h: int = 1) -> float:
"""
Функция для расчета площади трапеции.
:param a: нижнее основание
:type a: int
:param b: верхнее основание,
:type b: int
:param h: высота.
:type h: int
:return: площадь трапеции
:rtype: float
"""
return h * (a + b) / 2 ** param
S = trapezoid_s(2, 2)
print(S)
# Ошибка! Указание типа данных не оградит от ошибки
S = trapezoid_s(2, "a")
Следует относиться к аннотации как к удобной подсказке для программиста. К сожалению, возможности оградить функцию от использования неправильных параметров в Python нельзя. К тому же, косвенно об этом гласит Дзен Python
"Мы все здесь взрослые люди", что означает, что программист должен знать, что можно передавать в функцию и что нельзя"
Промежуточный итог и справка по вопросам
Теперь вы знаете что такое функции, как объявлять функции и какими они обладают свойствами. Следующие параграфы посвящены вопросам по теме. Постарайтесь ответить на предложенные вопросы сначала самостоятельно, потом перейти к объяснениям. В объяснениях будет рассмотрен подробнее новый материал, поэтому обязательно изучите их.
Вопросы по теме
Вопрос 1
Каким будет результат выполнения кода ниже?
alst = [1, 2, 3]
blst = [1, 2, 3]
def afnc(alst):
alst = alst * 2
return None
def bfnc(blst):
blst = blst.extend([1, 2, 3])
return None
afnc(alst)
bfnc(blst)
print(alst, 'и', blst)
# [1, 2, 3] и [1, 2, 3]
# [1, 2, 3] и [1, 2, 3, 1, 2, 3]
# [1, 2, 3, 1, 2, 3] и [1, 2, 3, 1, 2, 3]
# Error
Вопрос 2
Каким будет результат выполнения кода ниже?
def say(text, times = 2):
print(text * times, end=' ')
say('Заяц, ты меня слышишь?', 1)
say('Слышу ')
# Заяц, ты меня слышишь? Заяц, ты меня слышишь? Слышу Слышу
# Заяц, ты меня слышишь? Слышу
# Заяц, ты меня слышишь? Заяц, ты меня слышишь? Слышу
# Заяц, ты меня слышишь? Слышу Слышу
Вопрос 3
Каким будет результат выполнения кода ниже?
def attentivness(farg, sarg, true, false):
if farg == true and sarg == false:
print('True')
else:
print('False')
attentivness(1, 0, False, True)
# True
# False
# 1
# 0
Вопрос 4
Каким будет результат выполнения кода ниже?
def func(num):
return num
def in_(num):
return num * 3
def func_(num):
return num + 3
print(func(in_(func_(3))))
# 12
# 18
# 27
# Error
Вопрос 5
Каким будет результат выполнения кода ниже?
str = 'закрепление материала'
def func():
str = '*проверка*'
def enclosed():
print(str, end=' ')
enclosed()
func()
print(str, end=' ')
# *проверка* закрепление материала
# закрепление материала *проверка*
# закрепление материала закрепление материала
# Error
Вопрос 6
Каким будет результат выполнения кода ниже?
def hmm(itm = []):
itm.append(1)
return itm
for _ in range(2):
hmm()
print(hmm())
# [1]
# [1, 1]
# [1, 1, 1]
# Error
Вопрос 7
Каким будет результат выполнения кода ниже?
def func(argOne, argTwo):
newArgTwo = argTwo ** 2
var = (argOne + newArgTwo) / 2
varOne = 6
varTwo = 2
print(func(varOne, varTwo))
# 5
# 5.0
# Error
# Ничего из вышеперечисленного
Ответы на вопросы и пояснения
Ответ на вопрос 1
Каким будет результат выполнения кода ниже?
alst = [1, 2, 3]
blst = [1, 2, 3]
def afnc(alst):
alst = alst * 2
return None
def bfnc(blst):
blst = blst.extend([1, 2, 3])
return None
afnc(alst)
bfnc(blst)
print(alst, 'и', blst)
# [1, 2, 3] и [1, 2, 3]
# [1, 2, 3] и [1, 2, 3, 1, 2, 3] <- правильно
# [1, 2, 3, 1, 2, 3] и [1, 2, 3, 1, 2, 3]
# Error
Обратите внимание!
Выражение alst = alst * 2
внутри afnc()
создаёт новый список, а не меняет существующий. И при выходе из функции это значение теряется.
В то же время extend()
модифицирует именно исходный список, а потому blst
превращается из [1, 2, 3]
в [1, 2, 3, 1, 2, 3]
.
Ответ на вопрос 2
Каким будет результат выполнения кода ниже?
def say(text, times = 2):
print(text * times, end=' ')
say('Заяц, ты меня слышишь?', 1)
say('Слышу ')
# Заяц, ты меня слышишь? Заяц, ты меня слышишь? Слышу Слышу
# Заяц, ты меня слышишь? Слышу
# Заяц, ты меня слышишь? Заяц, ты меня слышишь? Слышу
# Заяц, ты меня слышишь? Слышу Слышу <- правильно
Обратите внимание!
Функция say()
печатает переданный ей текст столько раз, сколько указано во втором аргументе.
Если же не задать это значение вручную, то используется значение по умолчанию, которое в данном случае равняется 2.
Таким образом, текст «Заяц, ты меня слышишь?» печатается один раз, так как это явно задано при вызове функции. А «Слышу » печатается дважды, так как используется значение по умолчанию.
И да, между первым и вторым вызовами нет перехода на новую строку, так как в print()
задано, что заканчивается функция символом " "
.
Ответ на вопрос 3
Каким будет результат выполнения кода ниже?
def attentivness(farg, sarg, true, false):
if farg == true and sarg == false:
print('True')
else:
print('False')
attentivness(1, 0, False, True)
# True
# False <- правильно
# 1
# 0
Обратите внимание!
Решение достаточно простое, надо лишь быть чуточку более внимательным.
Вызвав attentiveness и передав ему аргументы, мы получаем в if следующее выражение: if 1 == False and 0 == True
. Оно ложно, а потому печатается то, что написано в else, т.е. 'False'.
Для того чтобы получить True, параметры можно вызвать именовано.
Ответ на вопрос 4
Каким будет результат выполнения кода ниже?
def func(num):
return num
def in_(num):
return num * 3
def func_(num):
return num + 3
print(func(in_(func_(3))))
# 12
# 18 <- правильно
# 27
# Error
Обратите внимание!
В данном случае func(in_(func_(3)))
= func(in_(3 + 3))
= func(6 * 3)
= 18
Ответ на вопрос 5
Каким будет результат выполнения кода ниже?
str = 'закрепление материала'
def func():
str = '*проверка*'
def enclosed():
print(str, end=' ')
enclosed()
func()
print(str, end=' ')
# *проверка* закрепление материала <- правильно
# закрепление материала *проверка*
# закрепление материала закрепление материала
# Error
Обратите внимание!
Во-первых, функцию можно создавать внутри функции. Этот прием можно встретить часто в Python.
Во-вторых, сначала выводится str, который находится внутри func()
— его печать происходит внутри enclosed()
Затем печатается уже str
, который был объявлен в самом начале кода — соответствующая команда является последней в программе.
Ответ на вопрос 6
Каким будет результат выполнения кода ниже?
def hmm(itm = []):
itm.append(1)
return itm
for _ in range(2):
hmm()
print(hmm())
# [1]
# [1, 1]
# [1, 1, 1] <- правильно
# Error
Обратите внимание!
При каждом вызове hmm()
в список itm добавляется один новый элемент. В for _ in range(2)
функция вызывается дважды, а во время print(hmm())
ещё один раз.
Именно поэтому itm == [1, 1, 1]
.
Ответ на вопрос 7
Каким будет результат выполнения кода ниже?
def func(argOne, argTwo):
newArgTwo = argTwo ** 2
var = (argOne + newArgTwo) / 2
varOne = 6
varTwo = 2
print(func(varOne, varTwo))
# 5
# 5.0
# Error
# Ничего из вышеперечисленного <- правильно
Обратите внимание!
Функция func()
не возвращает никакого значения. Поэтому и print()
ничего не выводит.