Курс по Python: https://stepik.org/course/100707
Сейчас мы с вами будем говорить
о способах определения параметров в функциях и передачи им аргументов. Казалось
бы, мы подробно разобрали эту тему и каждый из вас уже знает, что при
объявлении функции в круглых скобках через запятую можно указать сколько угодно
параметров. Например, простая функция вычисления объема прямоугольного
параллелепипеда, очевидно должна принимать, как минимум, три параметра (ширину,
высоту и глубину):
def get_V(a, b, c):
print(f"a = {a}, b = {b}, c = {c}")
return a * b * c
А, затем, может
быть вызвана с конкретными числовыми значениями:
v = get_V(1, 2, 3)
print(v)
Причем,
параметру a будет
соответствовать число 1, параметру b – число 2, а c – число 3. Это
так, потому что здесь используется позиционная запись аргументов при вызове
функции, то есть, значения параметров a, b, c определяются
порядком записи аргументов. А можно ли, не меняя порядка, параметру b присвоить 1,
параметру c – 2, а a – 3?
Оказывается да, в языке Python такое возможно,
если явно указывать имена параметров при вызове функции:
Такие аргументы
называются именованными. Теперь, при запуске программы, мы видим, указанные
значения у параметров a, b и c.
А можем ли мы
комбинировать позиционные и именованные аргументы? Да и такое тоже возможно.
Только вначале следует указывать позиционные, а в конце – именованные,
например, так:
Если же, мы не
будем следовать этому правилу и позиционные аргументы запишем после
именованного:
то возникнет
синтаксическая ошибка – так делать нельзя. Сначала всегда позиционные и только
потом – именованные:
Причем, обратите
внимание, последним именованным аргументом здесь может быть только имя
параметра c. Если записать
любой другой из трех, например b:
то у нас
получится дублирование передаваемых данных. Второй позиционный аргумент уже
присваивается параметру b, а далее, мы снова этому же параметру
присваиваем значение 3. Так делать нельзя.
Вернемся теперь
к параметрам самой функции. Мы их объявили просто через запятую с именами a, b, c. Однако, можно
задавать параметры со значениями по умолчанию, например, так:
def get_V(a, b, c, verbose=True):
if verbose:
print(f"a = {a}, b = {b}, c = {c}")
return a * b * c
Такие параметры
называются формальными, а обычные – фактическими. В чем отличие формальных
параметров от фактических, помимо значений по умолчанию? Их не обязательно
прописывать при вызове функции. Например, наш прежний вызов:
сработает без
каких-либо проблем. Мы не указали аргумент для последнего формального параметра
verbose. В этом случае
он принимает значение по умолчанию True. Если же
указать его:
v = get_V(1, 2, 3, False)
то функция print() внутри
функции вызвана уже не будет. Разумеется, можно использовать и соответствующий
именованный аргумент:
v = get_V(1, 2, 3, verbose=False)
Все будет
работать также.
Зачем вообще
нужны формальные параметры и когда их следует использовать? Я, думаю, ответ
здесь очевиден – для удобства использования функций. Как мы только что видели,
аргументы формальным параметрам можно не передавать, если нас устраивает
поведение функции по умолчанию. В других, как полагается, редких ситуациях,
всегда можно поменять значение такого параметра на другое и скорректировать
работу функции.
Чтобы это было
понятнее, приведу такой простой пример. Допустим, мы собираемся сравнивать
строки в разных режимах: с учетом и без учета регистра, а также учитывать или
не учитывать пробелы вначале и в конце. Для этого объявим следующую функцию:
def compare_str(s1, s2, reg=False, trim=True):
if reg:
s1 = s1.lower()
s2 = s2.lower()
if trim:
s1 = s1.strip()
s2 = s2.strip()
return s1 == s2
Формальные
параметры reg и trim определяют
наиболее частый вариант использования операции сравнения строк: с учетом
регистра и с удалением пробелов.
Теперь мы можем
взывать эту функцию, просто с двумя аргументами:
print(compare_str("Python ", " Python"))
или менять ее
поведение, указывая другие значения формальных параметров:
print(compare_str("Python ", " Python", trim=False))
print(compare_str("Python", "PYTHON", True, False))
В последнем
варианте записаны обычные позиционные аргументы. В этом случае параметр reg примет значение
True, а параметр trim – False. Конечно,
всегда можно воспользоваться и именованными аргументами, здесь полная свобода
выбора:
print(compare_str("Python", "PYTHON", reg=True, trim=False))
В заключение
этого занятия хочу показать вам один нюанс при объявлении функции с формальными
параметрами. Предположим, мы определяем вот такую очень простую функцию:
def add_value(value, lst=[]):
lst.append(value)
return lst
У нее один
фактический и один формальный параметр, причем, второй параметр lst по умолчанию
ссылается на изменяемый тип данных – список. Конечно, нам никто не запрещает
этого делать, но давайте посмотрим, как она будет работать. Вызовем ее два
раза:
l = add_value(1)
l = add_value(2)
print(l)
В консоли видим
два значения в списке 1 и 2. Возможно, объявляя таким образом функцию, мы
ожидали, что при каждом вызове параметр lst будет ссылаться
на пустой список и функция будет каждый раз возвращать один элемент в этом
списке. Но, получилось, что она сохраняет прежнее состояние списка при повторном
вызове. Почему так произошло?
На самом деле,
все просто. Когда мы объявляем функцию, то создается объект-функция и объекты
для формальных параметров, в данном случае пустой список. Когда, затем, мы
вызываем функцию, то (опуская некоторые детали) формальный параметр lst ссылается на
этот список и добавление элемента происходит в него. При повторном вызове
функции lst продолжает
ссылаться на этот же список и в него добавляется следующее значение. Это все
показывает, что при вызовах функции список не инициализируется повторно, а
используется один и тот же объект.
Конечно, мы
можем при вызовах функции каждый раз передавать пустой список:
l = add_value(1, [])
l = add_value(2, [])
И тогда
формальный параметр lst будет при каждом вызове ссылаться уже на новый
пустой список. А как сделать так, чтобы такое поведение было по умолчанию,
чтобы нам явно не приходилось передавать пустой список? Исправить ситуацию
можно, например, так:
def add_value(value, lst=None):
if lst is None:
lst = []
lst.append(value)
return lst
Значение
формального параметра определим неизменяемым значением None, а в самой
функции сделаем проверку, если этот параметр равен None, то есть,
функция вызвана с одним первым аргументом, то создаем новый пустой список и в
него помещаем значение. В этом случае, при каждом вызове функции:
l = add_value(1)
l = add_value(2)
будет
автоматически создаваться новый список. То, что мы и хотели. А если хотим
продолжить предыдущий, то достаточно его указать вторым аргументом:
Вот такой нюанс
существует при определении формальных параметров функции на изменяемые объекты,
такие как списки, словари и множества.
Итак, из этого
занятия вам нужно запомнить, что такое позиционные и именованные аргументы и
как их можно записывать и комбинировать. Что такое фактические и формальные
параметры, зачем нужны и как используются на практике, ну и, конечно же, как
работают формальные параметры с изменяемыми типами данных. Закрепляйте материал
практическими заданиями и переходите к следующему уроку.
Курс по Python: https://stepik.org/course/100707