Главная страница
Навигация по странице:

  • Использование ** при создании вариадических функций

  • >>> def formMolecule(hydrogen, helium, lithium, beryllium, boron, --snip

  • >>> formMolecule(2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 --snip

  • >>> def formMolecule(hydrogen=0, helium=0, lithium=0, beryllium=0, --snip

  • Использование * и ** для создания функций-оберток

  • Функциональное программирование

  • TOTAL = 0 >>> def addToTotal(amount): ... global TOTAL Функциональное программирование207... TOTAL += amount ... return TOTAL

  • Чистыйкод дляпродолжающи х


    Скачать 7.85 Mb.
    НазваниеЧистыйкод дляпродолжающи х
    Дата13.05.2023
    Размер7.85 Mb.
    Формат файлаpdf
    Имя файлаPython_Chisty_kod_dlya_prodolzhayuschikh_2022_El_Sveygart.pdf
    ТипДокументы
    #1127485
    страница21 из 40
    1   ...   17   18   19   20   21   22   23   24   ...   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 обеспечивать чистоту функций.
    Самый распространенный способ сделать ваши функции чистыми — избегать использования глобальных переменных и следить за тем, чтобы они не взаимо- действовали с файлами, интернетом, системными часами, генератором случайных чисел или другими внешними ресурсами.

    Функциональное программирование
    209
    Функции высшего порядка
    Функции высшего порядка (higher-order functions) могут получать другие функции в аргументах или использовать функции как возвращаемые значения. Например, определим функцию с именем callItTwice()
    , которая вызывает заданную функ- цию дважды:
    >>> def callItTwice(func, *args, **kwargs):
    ... func(*args, **kwargs)
    ... func(*args, **kwargs)
    >>> callItTwice(print, 'Hello, world!')
    Hello, world!
    Hello, world!
    Функция callItTwice()
    работает с любой передаваемой функцией. В Python функ- ции являются первоклассными объектами (first-class objects); это означает, что они ничем не отличаются от других объектов: функции можно сохранять в переменных, передавать в аргументах или использовать как возвращаемые значения.
    Лямбда-функции
    Лямбда-функции (lambda functions), также называемые анонимными (anonymous) или безымянными (nameless) функциями, представляют собой упрощенные функ- ции, у которых нет имен, а код состоит из одной команды return
    . Лямбда-функции часто используются для передачи функций как аргументов других функций.
    Например, можно создать обычную функцию, которая получает список с шириной и высотой прямоугольника 4
    × 10:
    >>> def rectanglePerimeter(rect):
    ... return (rect[0] * 2) + (rect[1] * 2)
    >>> myRectangle = [4, 10]
    >>> rectanglePerimeter(myRectangle)
    28
    Эквивалентная лямбда-функция выглядит так:
    lambda rect: (rect[0] * 2) + (rect[1] * 2)
    Чтобы определить лямбда-функцию на Python, укажите ключевое слово lambda
    , за которым следуют: список параметров (если они есть), разделенных запятыми, двоеточие и выражение, которое действует как возвращаемое значение. Так как функции являются первоклассными объектами, лямбда-функцию можно присвоить переменной, фактически повторяя то, что делает команда def
    :

    210
    Глава 10.Написание эффективных функций
    >>> rectanglePerimeter = lambda rect: (rect[0] * 2) + (rect[1] * 2)
    >>> rectanglePerimeter([4, 10])
    28
    Лямбда-функция присваивается переменной с именем rectanglePerimeter
    , тем самым фактически создается функция rectanglePerimeter()
    . Как видите, функ- ции, созданные командами lambda
    , ничем не отличаются от функций, созданных командами def
    1   ...   17   18   19   20   21   22   23   24   ...   40


    написать администратору сайта