ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 03.12.2023
Просмотров: 384
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Параметры и аргументы функций
201
Использование * при создании вариадических функций
Синтаксис
*
также может использоваться в командах def для создания вариадических
(variadic, или varargs) функций, получающих переменное количество позиционных аргументов. Например, функция print()
является вариадической, потому что ей можно передать любое количество строк, например print('Hello!')
или print('My name is',
name)
. Обратите внимание: если в предыдущем разделе синтаксис
*
ис- пользовался при вызове функций, здесь мы его используем в определениях функций.
Рассмотрим пример: функция product()
получает произвольное количество аргу- ментов и перемножает их:
>>> def product(*args):
... result = 1
... for num in args:
... result *= num
... return result
>>> product(3, 3)
9
>>> product(2, 1, 2, 3)
12
Внутри функции args
— обычный кортеж Python, содержащий все позиционные аргументы. С технической точки зрения можно присвоить параметру любое имя, при условии, что оно начинается со звездочки (
*
), но среди программистов принято использовать имя args
Вопрос о том, в каких случаях стоит использовать
*
, не так прост. Ведь альтерна- тивой вариадической функции становится создание одного параметра, в который передается список (или другой итерируемый тип данных) с переменным количе- ством элементов. Этот способ используется встроенной функцией sum()
:
>>> sum([2, 1, 2, 3])
8
Функция sum()
ожидает получить один итерируемый аргумент, и при попытке передать несколько аргументов выдается исключение:
>>> sum(2, 1, 2, 3)
Traceback (most recent call last):
File "
TypeError: sum() takes at most 2 arguments (4 given)
С другой стороны, встроенные функции min()
и max()
, определяющие минимум и максимум среди нескольких значений, получают один итерируемый аргумент или несколько разных аргументов:
202
Глава 10.Написание эффективных функций
>>> min([2, 1, 3, 5, 8])
1
>>> min(2, 1, 3, 5, 8)
1
>>>
1 ... 16 17 18 19 20 21 22 23 ... 40
max([2, 1, 3, 5, 8])
8
>>> max(2, 1, 3, 5, 8)
8
Все эти функции получают переменное количество аргументов, так почему же их параметры устроены по-разному? И когда функция должна получать один итерируемый аргумент, а когда — несколько разных аргументов с использованием синтаксиса
*
?
Вопрос о структуре параметров зависит от ваших намерений относительно того, как будет использоваться ваш код. Функция print()
получает несколько аргументов, потому что программисты часто передают несколько строк или переменных, со- держащих строки, — например, print('My name is',
name)
. Вариант с объединением этих строк в список и последующей передачей списка print()
встречается реже.
Кроме того, если передать print()
список, функция выведет его полностью; она не может использоваться для вывода отдельных значений из списка.
Вызов sum()
с разными аргументами не имеет смысла, потому что в Python для этого уже есть оператор
+
. Так как вы можете написать код вида
2
+
4
+
8
, вам не понадобится код sum(2,
4,
8)
. Логично, что переменное число аргументов в sum()
может передаваться только в виде списка.
Функции min()
и max()
поддерживают оба стиля. Если программист передает один аргумент, то функция предполагает, что это список или кортеж с проверяемыми значениями. Эти две функции обычно обрабатывают списки значений во время выполнения программы, как при вызове функции min(allExpenses)
. Им также приходится иметь дело с наборами отдельных аргументов, выбранных програм- мистом во время написания кода — например, max(0,
someNumber)
. Из-за этого функции написаны так, чтобы они поддерживали обе разновидности аргументов.
Следующая функция myMinFunction()
— моя собственная реализация функции min()
— демонстрирует обе возможности:
def myMinFunction(*args):
if len(args) == 1:
values = args[0]
❶
else:
values = args
❷
if len(values) == 0:
raise ValueError('myMinFunction() args is an empty sequence') (
❸
)
for i, value in enumerate(values):
❹
Параметры и аргументы функций
203
if i == 0 or value < smallestValue:
smallestValue = value return smallestValue myMinFunction()
использует синтаксис
*
для получения переменного количества аргументов в виде кортежа. Если кортеж содержит только одно значение, предпо- лагается, что это последовательность проверяемых значений
❶
. В противном слу- чае предполагается, что args содержит кортеж проверяемых значений
❷
. В любом случае переменная values будет содержать последовательность значений, которые проверяются в остальной части кода. Как и реальная функция min()
, функция выдает исключение
ValueError
, если вызывающая сторона не передала никаких аргументов или передала пустую последовательность
❸
. Остальная часть кода перебирает значения и возвращает наименьшее из найденных значений
❹
. Чтобы не усложнять пример, myMinFunction()
принимает только такие последовательности, как списки или кортежи (вместо произвольного итерируемого объекта).
Почему же мы не всегда пишем функции так, чтобы они поддерживали оба способа передачи переменного количества аргументов? Потому что лучше делать ваши функции как можно проще. Если только оба способа вызова не используются одинаково часто, выберите один из них. Когда функция имеет дело со структурой данных, создаваемой во время работы программы, то лучше, чтобы она принима- ла один параметр. А когда — с аргументами, которые задаются программистом во время написания кода, стоит использовать синтаксис
*
для получения переменного количества аргументов.
Использование ** при создании вариадических функций
Вариадические функции могут также использовать синтаксис
**
. И если синтак- сис
*
в командах def представляет переменное количество позиционных аргумен- тов, синтаксис
**
представляет переменное количество необязательных ключевых аргументов.
Когда вы определите функцию, которая получает числовые необязательные ключе- вые аргументы без синтаксиса
**
, ваша команда def быстро усложнится. Возьмем гипотетическую функцию formMolecule()
, которая получает параметры для всех
118 известных химических элементов:
>>> def formMolecule(hydrogen, helium, lithium, beryllium, boron, --snip--
Передавать значение
2
для параметра hydrogen и
1
для параметра oxygen
(молекула воды состоит из 2 атомов водорода и 1 атома кислорода) было бы громоздко и не- удобно, потому что для всех остальных элементов пришлось бы передавать нули:
>>> formMolecule(2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 --snip--
'water'
204
Глава 10.Написание эффективных функций
Чтобы функция была более удобной, можно воспользоваться именованными ключевыми параметрами, каждому из которых назначен аргумент по умолчанию, что избавляет вас от необходимости передавать аргумент для этого параметра при вызове функции.
ПРИМЕЧАНИЕ
Хотя термины «аргумент» и «параметр» четко определены, программисты часто исполь- зуют термины «ключевой аргумент» и «ключевой параметр» как синонимы.
Например, следующая команда def назначает аргументы по умолчанию
0
для каж- дого из ключевых параметров:
>>> def formMolecule(hydrogen=0, helium=0, lithium=0, beryllium=0, --snip--
Это упрощает вызов formMolecule()
, потому что вам достаточно лишь задать ар- гументы для параметров со значениями, отличными от аргументов по умолчанию.
Кроме того, ключевые аргументы можно задавать в любом порядке:
>>> formMolecule(hydrogen=2, oxygen=1)
'water'
>>> formMolecule(oxygen=1, hydrogen=2)
'water'
>>> formMolecule(carbon=8, hydrogen=10, nitrogen=4, oxygen=2)
'caffeine'
Однако остается громоздкая команда def с 118 именами параметров. А если вдруг будут открыты новые химические элементы? Придется обновлять команду def функции со всей документацией, относящейся к параметрам функции.
Вместо этого можно объединить все параметры с их аргументами в виде пар
«ключ — значение» в словаре с использованием синтаксиса
**
для ключевых ар- гументов. С технической точки зрения можно присвоить параметру
**
любое имя, но у программистов принято использовать имя kwargs
:
>>> def formMolecules(**kwargs):
... if len(kwargs) == 2 and kwargs['hydrogen'] == 2 and
kwargs['oxygen'] == 1:
... return 'water'
... # (... остальной код функции ...)
>>> formMolecules(hydrogen=2, oxygen=1)
'water'
Синтаксис
**
показывает, что параметр kwargs может использоваться для всех аргументов «ключ — значение», переданных при вызове функции. Они будут хра- ниться в виде пар «ключ — значение» в словаре, присвоенном параметру kwargs
Параметры и аргументы функций
205
При обнаружении новых химических элементов достаточно обновить код функции, но не ее команду def
, потому что все ключевые аргументы помещены в kwargs
:
>>> def formMolecules(**kwargs):
❶
... if len(kwargs) == 1 and kwargs.get('unobtanium') == 12:
❷
... return 'aether'
... # (... остальной код функции ...)
>>> formMolecules(unobtanium=12)
'aether'
Как видите, команда def
❶
осталась неизменной и в обновлении нуждается только код функции
❷
. При использовании синтаксиса
**
команда def и вызовы функций значительно упрощаются, а код все еще нормально читается.
Использование * и ** для создания функций-оберток
Синтаксисы
*
и
**
в командах def часто используются для создания функций-обер- ток, которые передают аргументы другой функции и возвращают возвращаемое значение этой функции. Вы можете использовать синтаксисы
*
и
**
для передачи любых аргументов функции, скрытой за оберткой. Например, можно создать функцию printLowercase()
, которая является оберткой для встроенной функции print()
. Вся реальная работа выполняется функцией print()
, а обертка сначала переводит строковые аргументы в нижний регистр:
>>> def printLower(*args, **kwargs):
❶
... args = list(args)
❷
... for i, value in enumerate(args):
... args[i] = str(value).lower()
... return print(*args, **kwargs)
❸
>>> name = 'Albert'
>>> printLower('Hello,', name)
hello, albert
>>> printLower('DOG', 'CAT', 'MOOSE', sep=', ')
dog, cat, moose
Функция printLower()
❶
использует синтаксис
*
для получения переменного коли- чества позиционных аргументов в кортеже, присвоенном параметру args
, тогда как синтаксис
**
присваивает все ключевые аргументы словарю из параметра kwargs
Если функция использует
*args вместе с
**kwargs
, параметр
*args должен пред- шествовать параметру
**kwargs
. Они передаются функции print()
, заключенной в обертку, но сначала наша функция изменяет некоторые аргументы, поэтому мы создаем списковую форму кортежа args
❷
После преобразования строк в args к нижнему регистру элементы args и пары
«ключ — значение» в kwargs передаются как отдельные аргументы функции print()
206
Глава 10.Написание эффективных функций с использованием синтаксисов
*
и
**
❸
. Возвращаемое значение print()
также возвращается как возвращаемое значение printLower()
. Таким образом создается обертка для функции print()
Функциональное программирование
Функциональное программирование — парадигма программирования, уделяющая особое внимание написанию функций, которые выполняют вычисления без из- менения глобальных переменных или какого-либо внешнего состояния (файлов на жестком диске, подключений к интернету или баз данных). Некоторые языки программирования — такие как Erlang, Lisp и Haskell — в значительной мере про- ектировались на основе концепций функционального программирования. И хотя язык Python не прикован намертво к этой парадигме, в нем реализованы некоторые средства функционального программирования. Главные из них, которые могут ис- пользоваться в программах Python, — функции, свободные от побочных эффектов, функции высшего порядка и лямбда-функции.
Побочные эффекты
К побочным эффектам относятся любые изменения, вносимые функцией в части программы, существующие за рамками ее собственного кода и локальных перемен- ных. Для демонстрации создадим функцию subtract()
, которая реализует оператор вычитания Python (
-
):
>>> def subtract(number1, number2):
... return number1 - number2
>>> subtract(123, 987)
-864
Эта функция subtract()
не имеет побочных эффектов. Другими словами, она не влияет в программе ни на что за пределами ее кода. По состоянию программы или компьютера невозможно определить, была ли функция subtract()
ранее вызвана один раз, два раза или миллион раз. Функция может изменять локальные перемен- ные внутри функции, но эти изменения остаются изолированными от остального кода программы.
Теперь рассмотрим функцию addToTotal()
, которая прибавляет числовой аргумент к глобальной переменной с именем
TOTAL
:
>>> TOTAL = 0
>>> def addToTotal(amount):
... global TOTAL
Функциональное программирование
207
... TOTAL += amount
... return TOTAL
>>> addToTotal(10)
10
>>> addToTotal(10)
20
>>> addToTotal(9999)
10019
>>> TOTAL
10019
Функция addToTotal()
имеет побочный эффект, потому что она изменяет элемент, существующий за пределами функции, — глобальную переменную
TOTAL
. По- бочные эффекты не ограничиваются изменением глобальных переменных. К ним относится обновление или удаление файлов, вывод текста на экран, подключение к базе данных, аутентификация на сервере или внесение любых других изменений за пределами функции. Любой след, оставленный вызовом функции после возврата управления, является побочным эффектом.
К побочным эффектам также можно отнести модификацию на месте изменяемых объектов, ссылки на которые существуют за пределами функции. Например, следующая функция removeLastCatFromList()
изменяет последний аргумент на месте:
>>> def removeLastCatFromList(petSpecies):
... if len(petSpecies) > 0 and petSpecies[-1] == 'cat':
... petSpecies.pop()
>>> myPets = ['dog', 'cat', 'bird', 'cat']
>>> removeLastCatFromList(myPets)
>>> myPets
['dog', 'cat', 'bird']
В этом примере переменная myPets и параметр petSpecies содержат ссылки на один и тот же список. Любые изменения на месте, вносимые в объект списка вну- три функции, также будут существовать за пределами функции, вследствие чего изменение становится побочным эффектом.
С концепцией побочных эффектов связана концепция детерминированной функции
(deterministic function), которая для одного набора аргументов всегда возвращает одно и то же значение. Вызов функции subtract(123,
987)
всегда возвращает
−864
Встроенная функция Python round()
всегда возвращает
3
, если передать ей аргу- мент
3.14
. Недетерминированная функция (nondeterministic function) не всегда возвращает одно и то же значение при передаче одного набора аргументов. Напри- мер, вызов random.randint(1,
10)
возвращает случайное целое число от 1 до 10.
Функция time.time()
не получает аргументов, но возвращает разные значения
208
Глава 10.Написание эффективных функций в зависимости от показаний компьютерных часов на момент вызова. В случае time.
time()
часы являются внешним ресурсом, который фактически предоставляет входные данные функции, так же как это делает аргумент. Функции, зависящие от ресурсов, внешних по отношению к функции (включая глобальные переменные, файлы на жестком диске, базы данных и подключения к интернету), не считаются детерминированными.
Одно из преимуществ детерминированных функций — возможность кэширования их значений. Нет необходимости вызывать subtract()
для вычисления разности
123
и
987
более одного раза, если функция может запомнить возвращаемое значе- ние при первом вызове с этими аргументами. Таким образом, детерминированные функции позволяют выбрать компромисс между затратами памяти и затратами времени, ускоряя выполнение функции за счет использования памяти для кэши- рования предыдущих результатов.
Детерминированная функция, свободная от побочных эффектов, называется
чистой функцией. Функциональные программисты стараются создавать в своих программах только чистые функции. В дополнение к уже упоминавшимся чистые функции имеют следующие преимущества.
Они хорошо подходят для модульного тестирования, потому что не требуют подготовки внешних ресурсов.
В чистых функциях проще воспроизводятся ошибки, для чего достаточно вызвать функцию с теми же аргументами.
Чистые функции могут вызывать другие чистые функции, оставаясь чи- стыми.
В многопоточных программах чистые функции являются потоково-безопас- ными и могут выполняться параллельно. (Тема многопоточности выходит за рамки книги.)
Множественные вызовы чистых функций способны выполняться на парал- лельных ядрах процессора или в многопоточной программе, потому что они не зависят от внешних ресурсов, требующих их выполнения в определенной последовательности.
На языке Python можно и нужно писать чистые функции там, где это возможно.
Функции Python делаются чистыми только по соглашению; нет никаких настро- ек, которые бы заставляли интерпретатор Python обеспечивать чистоту функций.
Самый распространенный способ сделать ваши функции чистыми — избегать использования глобальных переменных и следить за тем, чтобы они не взаимо- действовали с файлами, интернетом, системными часами, генератором случайных чисел или другими внешними ресурсами.