Книга: Простой Python. Современный стиль программирования. 2-е изд.
Назад: Глава 8. Словари и множества
Дальше: Глава 10. Ой-ой-ой: объекты и классы

Глава 9. Функции

Чем меньше функция, тем лучше управление.

Сирил Норткот Паркинсон

До этого момента все наши примеры кода были небольшими фрагментами. Они подходят для решения небольших задач, но никому не хочется раз за разом эти фрагменты перепечатывать. Нужна какая-то форма преобразования большого кода в ряд удобных в применении частей.

Первый шаг к повторному использованию кода — создание функций. Функция — это именованный фрагмент кода, отделенный от других. Она может принимать любое количество любых входных параметров и возвращать любое количество любых результатов.

С функцией можно сделать две вещи:

определить ее, указав ноль или больше параметров;

вызвать ее и получить ноль или больше результатов.

Определяем функцию с помощью ключевого слова def

Чтобы определить функцию, вам нужно написать def, имя функции, входные параметры, заключенные в скобки, и, наконец, двоеточие (:). Имена функций подчиняются тем же правилам, что и имена переменных (они должны начинаться с буквы или символа подчеркивания и содержать только буквы, цифры или символ подчеркивания).

Будем действовать пошагово. Сначала определим и вызовем функцию, которая не имеет параметров. Перед вами пример простейшей функции:

>>> def do_nothing():

...     pass

Даже если функции не нужны параметры, вам все равно придется указать круглые скобки и двоеточие в ее определении. Следующую строку необходимо выделить пробелами точно так же, как если бы это был оператор if. Python требует использовать выражение pass для демонстрации того, что функция ничего не делает. Оно эквивалентно утверждению «Эта страница специально оставлена пустой» (хотя теперь это и не так).

Вызываем функцию с помощью скобок

Функцию можно вызвать, просто написав ее имя и скобки. Она сработает так, как я и обещал, вполне успешно не сделав ничего:

>>> do_nothing()

>>>

Теперь определим и вызовем другую функцию, которая не имеет параметров и выводит на экран одно слово:

>>> def make_a_sound():

...     print('quack')

...

>>> make_a_sound()

quack

Когда вы вызываете функцию make_a_sound(), Python выполняет код, расположенный внутри ее описания. В этом случае он выводит одно слово и возвращает управление основной программе.

Попробуем написать функцию, которая не имеет параметров, но возвращает значение:

>>> def agree():

...    return True

...

Вы можете вызвать эту функцию и проверить значение, которое она вернула, с помощью if:

>>> if agree():

...     print('Splendid!')

... else:

...     print('That was unexpected.')

...

Splendid!

Только что вы шагнули далеко вперед. Комбинация функций с проверками вроде if и циклами вроде while позволяет вам делать то, чего вы раньше не могли.

Аргументы и параметры

Пришло время поместить что-нибудь в эти скобки. Определим функцию echo(), имеющую один параметр anything. Она использует оператор return, чтобы отправить значение anything вызывающей стороне дважды, разделив их пробелом:

>>> def echo(anything):

...    return anything + ' ' + anything

...

>>>

Теперь вызовем функцию echo(), передав ей строку 'Rumplestiltskin':

>>> echo('Rumplestiltskin')

'Rumplestiltskin Rumplestiltskin'

Значения, которые вы передаете в функцию при вызове, называются аргументами. Когда вы вызываете функцию с аргументами, значения этих аргументов копируются в соответствующие параметры внутри функций.

109115.png

Другими словами, значения вне функции называются аргументами, а значения внутри — параметрами.

В предыдущем примере функции echo() передавалась строка 'Rumplestiltskin'. Это значение копировалось внутри функции echo() в параметр anything, а затем возвращалось (в этом случае оно удваивалось и разделялось пробелом) вызывающей стороне.

Эти примеры функций довольно просты. А сейчас напишем функцию, которая принимает аргумент и что-то с ним делает. Адаптируем предыдущий фрагмент кода, который комментировал цвета. Назовем его commentary и сделаем так, чтобы он принимал в качестве аргумента строку color. Заставьте его вернуть описание строки вызывающей стороне, которая может решить, что с ней делать дальше:

>>> def commentary(color):

...     if color == 'red':

...         return "It's a tomato."

...     elif color == "green":

...         return "It's a green pepper."

...     elif color == 'bee purple':

...         return "I don't know what it is, but only bees can see it."

...     else:

...         return "I've never heard of the color "  + color +  "."

...

>>>

Вызовем функцию commentary(), передав ей в качестве аргумента строку 'blue':

>>> comment = commentary('blue')

Функция сделает следующее:

присвоит значение 'blue' параметру функции color;

• пройдет по логической цепочке if-elif-else;

вернет строку.

Затем вызывающая сторона присвоит строку переменной comment.

Что мы получили в результате?

>>> print(comment)

I've never heard of the color blue.

Функция может принимать любое количество аргументов (включая ноль) любого типа. Она может возвращать любое количество результатов (также включая ноль) любого типа. Если функция не вызывает return явно, вызывающая сторона получит результат None:

>>> print(do_nothing())

None

None — это полезно

None — это специальное значение в Python, которое заполняет собой пустое место, если функция ничего не возвращает. Оно не является булевым значением False, хоть и похоже на него при проверке булевой переменной. Рассмотрим пример:

>>> thing = None

>>> if thing:

...     print("It's some thing")

... else:

...     print("It's no thing")

...

It's no thing

Чтобы отличить None от булева значения False, используйте оператор is:

>>> thing = None

>>> if thing is None:

...     print("It's nothing")

... else:

...     print("It's something")

...

It's nothing

Разница кажется небольшой, однако в Python она важна: None потребуется вам, чтобы различать отсутствующие значения и пустые. Помните, что целочисленные нули, нули с плавающей точкой, пустые строки (''), списки ([]), кортежи ((,)), словари ({}) и множества (set()) все равны False, но это не то же самое, что None.

Напишем небольшую функцию, которая выводит на экран, является ли ее аргумент None, True или False:

>>> def whatis(thing):

...     if thing is None:

...         print(thing, "is None")

...     elif thing:

...         print(thing, "is True")

...     else:

...         print(thing, "is False")

...

Теперь выполним несколько проверок:

>>> whatis(None)

None is None

>>> whatis(True)

True is True

>>> whatis(False)

False is False

Как насчет реальных значений?

>>> whatis(0)

0 is False

>>> whatis(0.0)

0.0 is False

>>> whatis('')

is False

>>> whatis("")

is False

>>> whatis('''''')

is False

>>> whatis(())

() is False

>>> whatis([])

[] is False

>>> whatis({})

{} is False

>>> whatis(set())

set() is False

 

>>> whatis(0.00001)

1e-05 is True

>>> whatis([0])

[0] is True

>>> whatis([''])

[''] is True

>>> whatis(' ')

  is True

Позиционные аргументы

Python довольно гибко обрабатывает аргументы функций в сравнении со многими другими языками. Наиболее распространенный тип аргументов — это позиционные аргументы, чьи значения копируются в соответствующие параметры по порядку.

Эта функция создает словарь из позиционных входных аргументов и возвращает его:

>>> def menu(wine, entree, dessert):

...     return {'wine': wine, 'entree': entree, 'dessert': dessert}

...

>>> menu('chardonnay', 'chicken', 'cake')

{'wine': 'chardonnay', 'entree': 'chicken', 'dessert': 'cake'}

Несмотря на распространенность аргументов такого типа, у них есть недостаток: вам нужно запоминать значение каждой позиции. Если бы мы вызвали функцию menu(), передав в качестве последнего аргумента марку вина, обед вышел бы совершенно другим:

>>> menu('beef', 'bagel', 'bordeaux')

{'dessert': 'bordeaux', 'wine': 'beef', 'entree': 'bagel'}

Аргументы — ключевые слова

Для того чтобы избежать путаницы с позиционными аргументами, вы можете указать аргументы с помощью имен соответствующих параметров. Порядок следования аргументов в этом случае может быть иным:

>>> menu(entree='beef', dessert='bagel', wine='bordeaux')

{'dessert': 'bagel', 'wine': 'bordeaux', 'entree': 'beef'}

Можно объединять позиционные аргументы и аргументы — ключевые слова. Сначала выберем вино, а для десерта и основного блюда используем аргументы — ключевые слова.

>>> menu('frontenac', dessert='flan', entree='fish')

{'entree': 'fish', 'dessert': 'flan', 'wine': 'frontenac'}

Если вы вызываете функцию, имеющую как позиционные аргументы, так и аргументы — ключевые слова, то позиционные аргументы необходимо указывать первыми.

Указываем значение параметра по умолчанию

Вы можете указать для параметров значения по умолчанию. Значения по умолчанию используются в том случае, если вызывающая сторона не предоставила соответствующий аргумент. Это стандартное действие оказывается достаточно полезным. Обратимся к предыдущему примеру:

>>> def menu(wine, entree, dessert='pudding'):

...     return {'wine': wine, 'entree': entree, 'dessert': dessert}

В этот раз мы вызовем функцию menu(), не передав ей аргумент dessert:

>>> menu('chardonnay', 'chicken')

{'wine': 'chardonnay', 'entree': 'chicken', 'dessert': 'pudding'}

Если вы предоставите аргумент, он будет использован вместо аргумента по умолчанию:

>>> menu('dunkelfelder', 'duck', 'doughnut')

{'wine': 'dunkelfelder', 'entree': 'duck', 'dessert': 'doughnut'}

109120.png

Значение параметров по умолчанию высчитывается, когда функция определяется, а не выполняется. Распространенной ошибкой новичков (а иногда и не совсем новичков) является использование изменяемого типа данных, таких как список или словарь, в качестве параметра по умолчанию.

В следующей проверке функция buggy() каждый раз должна запускаться с новым пустым списком result, добавлять в него аргумент arg, а затем выводить на экран список, состоящий из одного элемента. Однако в этой функции есть баг: список пуст только при первом вызове. Во второй раз список result будет содержать элемент, оставшийся после предыдущего вызова:

>>> def buggy(arg, result=[]):

...     result.append(arg)

...     print(result)

...

>>> buggy('a')

['a']

>>> buggy('b')   # ожидаем увидеть ['b']

['a', 'b']

Функция работала бы корректно, если бы код выглядел так:

>>> def works(arg):

...     result = []

...     result.append(arg)

...     return result

...

>>> works('a')

['a']

>>> works('b')

['b']

Решить проблему можно, передав в функцию что-либо еще, указывающее на то, что вызов является первым:

>>> def nonbuggy(arg, result=None):

...     if result is None:

...         result = []

...     result.append(arg)

...     print(result)

...

>>> nonbuggy('a')

['a']

>>> nonbuggy('b')

['b']

Иногда этот вопрос задают на собеседованиях, связанных с Python. Теперь вы предупреждены.

Получаем/разбиваем аргументы — ключевые слова с помощью символа *

Если вы работали с языками программирования C или C++, то можете предположить, что астериск (*) в Python имеет какое-то отношение к указателям. Нет, у Python нет указателей.

Если символ * будет использован внутри функции с параметром, произвольное количество позиционных аргументов сгруппируется в один кортеж. В следующем примере args является кортежем параметров, который был создан из нуля или более аргументов, переданных в функцию print_args():

>>> def print_args(*args):

...     print('Positional argument tuple:', args)

...

Если вы вызовете функцию без аргументов, то получите пустой кортеж:

>>> print_args()

Positional argument tuple: ()

Все аргументы, которые вы передадите, будут выведены на экран как кортеж args:

>>> print_args(3, 2, 1, 'wait!', 'uh...')

Positional argument tuple: (3, 2, 1, 'wait!', 'uh...')

Это удобно при написании таких функций, как print(), которые принимают произвольное количество аргументов. Если в вашей функции имеются также обязательные позиционные аргументы, поместите их в начало — *args отправится в конец списка и получит все остальные аргументы:

>>> def print_more(required1, required2, *args):

...     print('Need this one:', required1)

...     print('Need this one too:', required2)

...     print('All the rest:', args)

...

>>> print_more('cap', 'gloves', 'scarf', 'monocle', 'mustache wax')

Need this one: cap

Need this one too: gloves

All the rest: ('scarf', 'monocle', 'mustache wax')

109126.png

При использовании * вам не нужно обязательно называть кортеж аргументов args, однако это распространенная идиома в Python. Также часто внутри функции используется конструкция *args, как это показано в предыдущем примере, несмотря на то что технически она является параметром и должна называться *params.

Подведем итоги.

Можно передавать позиционный аргумент в функцию, которая соотнесет его с позиционным параметром. Подобные примеры вы уже видели в этой книге.

• Можно передать в функцию кортеж, и внутри он также останется кортежем. Это более простой вариант предыдущей ситуации.

• Можно передать позиционные аргументы в функцию и объединить их в параметр *args, который будет развернут в кортеж args. Эту ситуацию мы описали в текущем подразделе.

• Можно «разбить» кортеж с именем args на позиционные параметры *args внутри функции, которые затем будут пересобраны в кортеж-параметр args:

>>> print_args(2, 5, 7, 'x')

Positional tuple: (2, 5, 7, 'x')

>>> args = (2,5,7,'x')

>>> print_args(args)

Positional tuple: ((2, 5, 7, 'x'),)

>>> print_args(*args)

Positional tuple: (2, 5, 7, 'x')

Символ * можно использовать только при описании функции и ее вызове:

>>> *args

  File "<stdin>", line 1

SyntaxError: can't use starred expression here

Итак:

находясь за пределами функции, *args разбивает кортеж args на разделенные запятыми позиционные параметры;

будучи внутри функции, *args собирает все позиционные аргументы в единый кортеж args. Вы можете использовать имена *params и params, но чаще всего имя *args используется как для аргументов, так и для параметров.

Читатели, обладающие синестезией, могут неявно услышать, что *args произносится как выдох-args снаружи и вдох-args изнутри.

Получаем/разбиваем аргументы — ключевые слова с помощью символов **

Вы можете использовать два астериска (**), чтобы сгруппировать аргументы — ключевые слова в словарь, где имена аргументов станут ключами, а их значения — соответствующими значениями в словаре. В следующем примере определяется функция print_kwargs(), в которой выводятся ее аргументы — ключевые слова:

>>> def print_kwargs(**kwargs):

...     print('Keyword arguments:', kwargs)

...

Теперь попробуйте вызвать ее, передав несколько аргументов:

>>> print_kwargs()

Keyword arguments: {}

>>> print_kwargs(wine='merlot', entree='mutton', dessert='macaroon')

Keyword arguments: {'dessert': 'macaroon', 'wine': 'merlot', 'entree': 'mutton'}

Внутри функции kwargs является параметром-словарем.

Порядок аргументов будет следующим:

обязательные позиционные аргументы;

• необязательные позиционные аргументы (*args);

необязательные аргументы — ключевые слова (**kwargs).

Как и в случае с args, вам не обязательно называть этот аргумент kwargs, однако такова распространенная практика.

Синтаксис ** можно применять только при вызове функции или ее определении:

>>> **kwparams

  File "<stdin>", line 1

    **kwparams

     ^

SyntaxError: invalid syntax

Подведем итоги:

вы можете передавать аргументы в функцию по ключевым словам — они будут соотнесены с параметрами. Вы уже видели этот подход на практике;

• вы можете передать в функцию словарь — внутри он также будет представлять собой словарь с параметрами. Это частный случай предыдущей ситуации;

• вы можете передать в функцию один или несколько аргументов по ключевым словам (в формате имя=значение) и собрать их внутри в виде конструкции **kwargs, которая будет преобразована в параметр-словарь с именем kwargs. Такую ситуацию мы рассмотрели в этом подразделе;

• находясь за пределами функции, **kwargsразбивает словарь kwargs на аргументы в формате имя=значение;

находясь внутри функции, **kwargsсобирает аргументы в формате имя=значение в словарь с именем kwargs.

Если вам помогает аналогия со слуховыми образами, представьте, что при разбиении аргументов для каждой звездочки делается выдох, а при их сборе — вдох.

Аргументы, передаваемые только по ключевым словам

В функцию можно передать аргумент — ключевое слово с тем же самым именем, что и у позиционного параметра. Однако, скорее всего, это приведет не к тому результату, который вы ожидаете. Python 3 позволяет указывать аргументы, которые передаются только по ключевым словам. Соответственно названию их нужно предоставлять в формате имя=значение, а не позиционно, как значение. Знак * в определении функции говорит о том, что параметры start и end нужно предоставлять как именованные аргументы, если мы не хотим использовать их значения по умолчанию:

>>> def print_data(data, *, start=0, end=100):

...     for value in (data[start:end]):

...         print(value)

...

>>> data = ['a', 'b', 'c', 'd', 'e', 'f']

>>> print_data(data)

a

b

c

d

e

f

>>> print_data(data, start=4)

e

f

>>> print_data(data, end=2)

a

b

Изменяемые и неизменяемые аргументы

Вы не забыли, что, если присвоить один список двум переменным, вы сможете изменить его, используя любую из этих переменных? И что это не сработает, если обе переменные ссылаются, например, на целое число или строку? Это происходит потому, что список является изменяемым, а строка и целое число — нет. Вам нужно помнить про эту особенность при передаче аргументов в функцию. Если аргумент изменяемый, то его значение можно изменить изнутри функции с помощью соответствующего параметра:

>>> outside = ['one', 'fine', 'day']

>>> def mangle(arg):

...    arg[1] = 'terrible!'

...

>>> outside

['one', 'fine', 'day']

>>> mangle(outside)

>>> outside

['one', 'terrible!', 'day']

Хорошим тоном считается умение избегать подобных ситуаций. Любой документ, являющийся аргументом, может измениться или вернуть новое значение.

Строки документации

В Python удобочитаемость имеет значение. Вы можете прикрепить документацию к определению функции, включив в начало ее тела строку, которая и называется строкой документации:

>>> def echo(anything):

...     'echo returns its input argument'

...     return anything

Вы можете сделать строку документации довольно длинной и даже применить к ней, если хотите, форматирование. Это показано в следующем примере:

def print_if_true(thing, check):

    '''

    Prints the first argument if a second argument is true.

    The operation is:

        1. Check whether the *second* argument is true.

        2. If it is, print the *first* argument.

    '''

    if check:

        print(thing)

Для вывода строки документации функции вызовите функцию help(). Передайте ей имя функции, чтобы получить список всех аргументов и красиво отформатированную строку документации:

>>> help(echo)

Help on function echo in module __main__:

 

echo(anything)

    echo returns its input argument

Если хотите увидеть только строку документации без форматирования:

>>> print(echo.__doc__)

echo returns its input argument

Подозрительно выглядящая строка __doc__ является внутренним именем строки документации как переменной внутри функции. Двойное нижнее подчеркивание (на сленге его называют dunder) часто используется для именования внутренних переменных Python, поскольку программисты, скорее всего, не будут использовать подобные имена переменных.

Функции — это объекты первого класса

Я уже упоминал мантру Python: все является объектом. Числа, строки, кортежи, списки, словари и даже функции — это объекты. Функции в Python выступают объектами первого класса. Вы можете присвоить их переменным, использовать как аргументы для других функций и возвращать из функций. Это дает возможность решать в Python такие задачи, которые трудно выполнить во многих других языках.

Для того чтобы убедиться в этом, определим простую функцию answer(), которая не имеет аргументов и просто выводит число 42:

>>> def answer():

...     print(42)

Вы знаете, что получите в качестве результата, если запустите эту функцию:

>>> answer()

42

Теперь определим еще одну функцию с именем run_something. Она имеет один аргумент с именем func, представляющий собой функцию, которую нужно запустить и которая просто вызывает другую функцию:

>>> def run_something(func):

...     func()

Если мы передадим answer в функцию run_something(), то используем ее в качестве данных, как и другие объекты:

>>> run_something(answer)

42

Обратите внимание: вы передали строку answer, а не answer(). В Python круглые скобки означают «вызови эту функцию». Если скобок нет, Python относится к функции как к любому другому объекту. Это происходит потому, что функция является объектом, как и все остальное в Python:

>>> type(run_something)

<class 'function'>

Попробуем запустить функцию с аргументами. Определим функцию add_args(), которая выводит на экран сумму двух числовых аргументов arg1 и arg2:

>>> def add_args(arg1, arg2):

...     print(arg1 + arg2)

Чем является add_args()?

>>> type(add_args)

<class 'function'>

Теперь определим функцию, которая называется run_something_with_args() и принимает три аргумента:

func — функция, которую нужно запустить;

• arg1 — первый аргумент функции func;

• arg2 — второй аргумент функции func.

>>> def run_something_with_args(func, arg1, arg2):

...     func(arg1, arg2)

Когда вы вызываете функцию run_something_with_args(), та функция, что передается вызывающей стороной, присваивается параметру func, а переменные arg1 и arg2 получают значения, которые следуют далее в списке аргументов. Вызов func(arg1,arg2) выполняет данную функцию с этими аргументами, потому что круглые скобки указывают Python сделать это.

Проверим функцию run_something_with_args(), передав ей имя функции add_args и аргументы 5 и 9:

>>> run_something_with_args(add_args, 5, 9)

14

Внутри функции run_something_with_args() аргумент add_args, представляющий собой имя функции, был присвоен параметру func, 5 — параметру arg1, а 9 — параметру arg2. В итоге получается следующая конструкция:

add_args(5, 9)

Вы можете объединить этот прием с использованием *args и **kwargs.

Определим тестовую функцию, которая принимает любое количество позиционных аргументов, определяет их сумму с помощью функции sum() и возвращает ее:

>>> def sum_args(*args):

...    return sum(args)

Я не упоминал функцию sum() ранее. Она встроена в Python и высчитывает сумму значений итерабельного числового (целочисленного или с плавающей точкой) аргумента.

Определим новую функцию run_with_positional_args(), принимающую функцию и произвольное количество позиционных аргументов, которые нужно будет передать в нее:

>>> def run_with_positional_args(func, *args):

...    return func(*args)

Теперь вызовем ее:

>>> run_with_positional_args(sum_args, 1, 2, 3, 4)

10

Вы можете использовать функции как элементы списков, кортежей, множеств и словарей. Функции неизменяемы, поэтому их можно применять даже в качестве ключей для словарей.

Внутренние функции

Вы можете определить функцию внутри другой функции:

>>> def outer(a, b):

...     def inner(c, d):

...         return c + d

...     return inner(a, b)

...

>>>

>>> outer(4, 7)

11

Внутренние функции могут быть полезны при выполнении некоторых сложных задач более одного раза внутри другой функции. Это позволит избежать использования циклов или дублирования кода. Рассмотрим пример работы со строкой, когда внутренняя функция добавляет текст в свой аргумент:

>>> def knights(saying):

...     def inner(quote):

...         return "We are the knights who say: '%s'" % quote

...     return inner(saying)

...

>>> knights('Ni!')

"We are the knights who say: 'Ni!'"

Замыкания. Внутренняя функция может действовать как замыкание. Замыкание — это функция, которая динамически генерируется другой функцией. Обе они могут изменяться и запоминать значения переменных, которые были созданы вне функции.

Обратимся еще раз к примеру knights(). Назовем новую функцию knights2(), поскольку у нас нет воображения, и превратим функцию inner() в замыкание, которое называется inner2(). Различия заключаются в следующем:

inner2() использует внешний параметр saying непосредственно, вместо того чтобы получить его как аргумент;

knights2() возвращает имя функции inner2, вместо того чтобы вызывать ее:

>>> def knights2(saying):

...     def inner2():

...         return "We are the knights who say: '%s'" % saying

...     return inner2

...

Функция inner2() знает значение переменой saying, которое было передано в функцию, и запоминает его. Строка inner2 возвращает эту особую копию функции inner2 (но не вызывает ее). Получается своего рода замыкание: динамически созданная функция, которая запоминает, откуда она появилась.

Вызовем функцию knights2() два раза с разными аргументами:

>>> a = knights2('Duck')

>>> b = knights2('Hasenpfeffer')

О’кей, чем являются a и b?

>>> type(a)

<class 'function'>

>>> type(b)

<class 'function'>

Они являются функциями, а также замыканиями:

>>> a

<function knights2.<locals>.inner2 at 0x10193e158>

>>> b

<function knights2.<locals>.inner2 at 0x10193e1e0>

Если мы вызовем их, они запомнят значение переменной saying, которое было использовано, когда они создавались функцией knights2:

>>> a()

"We are the knights who say: 'Duck'"

>>> b()

"We are the knights who say: 'Hasenpfeffer'"

Анонимные функции: лямбда-выражения

В Python лямбда-функция — это анонимная функция, выраженная в виде одного оператора. Ее можно использовать вместо обычной небольшой функции.

Чтобы проиллюстрировать анонимные функции, сначала создадим пример, в котором используются обычные функции. В первую очередь определим функцию edit_story(). Она имеет следующие аргументы:

words — список слов;

• func — функцию, которая должна быть применена к каждому слову в списке words:

>>> def edit_story(words, func):

...     for word in words:

...         print(func(word))

Теперь нам нужны список слов и функция, которую требуется к ним применить. В качестве слов я возьму список звуков (гипотетических), которые мог бы издать мой кот, если бы (гипотетически) он не заметил одну из лестниц:

>>> stairs = ['thud', 'meow', 'thud', 'hiss']

Функция запишет каждое слово с большой буквы и добавит к нему восклицательный знак, что идеально подойдет для заголовка какой-нибудь желтой кошачьей газетенки:

>>> def enliven(word):   # больше эмоций!

...     return word.capitalize() + '!'

Смешаем наши ингредиенты:

>>> edit_story(stairs, enliven)

Thud!

Meow!

Thud!

Hiss!

Наконец переходим к лямбде. Функция enliven() была такой короткой, что мы можем заменить ее лямбдой:

>>>

>>> edit_story(stairs, lambda word: word.capitalize() + '!')

Thud!

Meow!

Thud!

Hiss!

>>>

Лямбда-выражение имеет ноль или больше аргументов, разделенных запятой, за которыми следуют двоеточие (:) и определение функции. Этому лямбда-выражению мы передаем один аргумент word. Для вызова лямбда-выражения не используются круглые скобки, в отличие от вызова функции, созданной с помощью ключевого слова def.

Зачастую использование реальных функций, таких как enliven(), гораздо понятнее, чем использование лямбд. Лямбды наиболее полезны в случаях, когда вам нужно определить множество мелких функций и запомнить все их имена. В частности, вы можете использовать лямбды в графических пользовательских интерфейсах, чтобы определить функции внешнего вызова. Примеры можно найти в главе 20.

Генераторы

В Python генератор — это объект, который предназначен для создания последовательностей. С его помощью вы можете итерировать потенциально огромные последовательности, не создавая и не сохраняя всю последовательность в памяти сразу. Генераторы часто становятся источником данных для итераторов. Как вы помните, один из них, range(), мы уже использовали в примерах кода для того, чтобы сгенерировать последовательность целых чисел. В Python 2 функция range() возвращает список, ограниченный так, чтобы он помещался в память. В Python 2 также есть функция xrange(), которая стала обычной функцией range() в Python 3. В этом примере складываются все целые числа от 1 до 100:

>>> sum(range(1, 101))

5050

Каждый раз, когда вы итерируете через генератор, он отслеживает, где находился во время последнего вызова, и возвращает следующее значение. Это отличает его от обычной функции, которая не помнит о предыдущих вызовах и всегда начинает работу с первой строки в том же состоянии.

Функции-генераторы

Если вы хотите создать потенциально большую последовательность, вы можете написать функцию-генератор. Это обычная функция, однако она возвращает значение с помощью выражения yield, а не return. Напишем собственную функцию range():

>>> def my_range(first=0, last=10, step=1):

...     number = first

...     while number < last:

...         yield number

...         number += step

...

Это обычная функция:

>>> my_range

<function my_range at 0x10193e268>

И она возвращает объект генератора:

>>> ranger = my_range(1, 5)

>>> ranger

<generator object my_range at 0x101a0a168>

Мы можем проитерировать по этому объекту генератора:

>>> for x in ranger:

...     print(x)

...

1

2

3

4

109132.png

Генератор можно запустить лишь однажды. Списки, множества, строки и словари существуют в памяти, а генераторы создают свои значения на лету и выдают их по одному с помощью итератора. Генератор не запоминает значения, поэтому вы не можете перезапустить его или создать резервную копию.

Если вы попробуете снова проитерировать по генератору, вы увидите, что он истощился:

>>> for try_again in ranger:

...     print(try_again)

...

>>>

Включения генераторов

Вы уже сталкивались с включениями списков, словарей и множеств. Включение генератора выглядит так же, но заключается в круглые скобки, а не в квадратные или фигурные. Оно похоже на сокращенную функцию-генератор, которая неявно выполняет yield и возвращает объект генератора:

>>> genobj = (pair for pair in zip(['a', 'b'], ['1', '2']))

>>> genobj

<generator object <genexpr> at 0x10308fde0>

>>> for thing in genobj:

...     print(thing)

...

('a', '1')

('b', '2')

Декораторы

Иногда вам нужно модифицировать существующую функцию, не меняя при этом ее исходный код. Чаще всего добавляется выражение для отладки, чтобы посмотреть, какие аргументы были переданы.

Декоратор — это функция, которая принимает одну функцию в качестве аргумента и возвращает другую функцию. Давайте рассмотрим, какие приемы из нашего арсенала мы можем использовать:

*args и **kwargs;

• внутренние функции;

функции в качестве аргументов.

Функция document_it() определяет декоратор, который:

выведет имя функции и значения переданных в нее аргументов;

• запустит функцию с полученными аргументами;

• выведет результат;

вернет модифицированную функцию, готовую для использования.

Код будет выглядеть так:

>>> def document_it(func):

...     def new_function(*args, **kwargs):

...         print('Running function:', func.__name__)

...         print('Positional arguments:', args)

...         print('Keyword arguments:', kwargs)

...         result = func(*args, **kwargs)

...         print('Result:', result)

...         return result

...     return new_function

Независимо от того, какую функцию func вы передадите document_it(), вы получите новую функцию с дополнительными выражениями, которые добавляет document_it(). Декоратор не обязательно должен запускать код функции func, но функция document_it() вызовет часть func, чтобы вы получили результат работы функции func, а также дополнительные данные:

>>> def add_ints(a, b):

...    return a + b

...

>>> add_ints(3, 5)

8

>>> cooler_add_ints = document_it(add_ints)  # создание декоратора вручную

>>> cooler_add_ints(3, 5)

Running function: add_ints

Positional arguments: (3, 5)

Keyword arguments: {}

Result: 8

8

В качестве альтернативы созданию декоратора вручную (этот процесс мы только что рассмотрели) можно добавить конструкцию @имя_декоратора перед функцией, которую нужно декорировать:

>>> @document_it

... def add_ints(a, b):

...     return a + b

...

>>> add_ints(3, 5)

Running function add_ints

Positional arguments: (3, 5)

Keyword arguments: {}

Result: 8

8

У каждой функции может быть больше одного декоратора. Напишем еще один с именем square_it(), который возводит результат в квадрат:

>>> def square_it(func):

...     def new_function(*args, **kwargs):

...         result = func(*args, **kwargs)

...         return result * result

...     return new_function

...

Декоратор, размещенный ближе всего к функции (прямо над def), будет выполнен первым, а затем выполнится тот, что находится сразу над ним. Любой порядок вызова вернет один и тот же конечный результат, но вы можете увидеть все промежуточные этапы:

>>> @document_it

... @square_it

... def add_ints(a, b):

...     return a + b

...

>>> add_ints(3, 5)

Running function: new_function

Positional arguments: (3, 5)

Keyword arguments: {}

Result: 64

64

Попробуем изменить порядок декораторов:

>>> @square_it

... @document_it

... def add_ints(a, b):

...     return a + b

...

>>> add_ints(3, 5)

Running function: add_ints

Positional arguments: (3, 5)

Keyword arguments: {}

Result: 8

64

Пространства имен и область определения

С тем, кто в искусстве больше преуспел…

Уильям Шекспир

Имя может ссылаться на несколько разных объектов в зависимости от того, где оно используется. Программы на Python имеют различные пространства имен — разделы, внутри которых определенное имя уникально и не связано с таким же именем в других пространствах имен.

Каждая функция определяет собственное пространство имен. Если вы определите переменную с именем х в основной программе и другую переменную х в отдельной функции, они будут ссылаться на разные значения. Но эту стену можно пробить: если нужно, вы можете получить доступ к именам в других пространствах имен разными способами.

В основной программе определяется глобальное пространство имен, поэтому переменные, находящиеся в нем, являются глобальными.

Значение глобальной переменной можно получить внутри функции:

>>> animal = 'fruitbat'

>>> def print_global():

...     print('inside print_global:', animal)

...

>>> print('at the top level:', animal)

at the top level: fruitbat

>>> print_global()

inside print_global: fruitbat

Но если вы попробуете получить значение глобальной переменной и изменить его внутри функции, то получите ошибку:

>>> def change_and_print_global():

...     print('inside change_and_print_global:', animal)

...     animal = 'wombat'

...     print('after the change:', animal)

...

>>> change_and_print_global()

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

  File "<stdin>", line 2, in change_and_print_global

UnboundLocalError: local variable 'animal' referenced before assignment

Если вы просто измените его, то изменится другая переменная, которая также называется animal, но находится внутри функции:

>>> def change_local():

...     animal = 'wombat'

...     print('inside change_local:', animal, id(animal))

...

>>> change_local()

inside change_local: wombat 4330406160

>>> animal

'fruitbat'

>>> id(animal)

4330390832

Что же произошло? Сначала мы присвоили строку 'fruitbat' глобальной переменной с именем animal. Функция change_local() также имеет переменную с именем animal, но та находится в ее локальном пространстве имен.

Я использовал функцию id(), чтобы вывести на экран уникальное значение каждого объекта и доказать, что переменная animal, расположенная внутри функции change_local(), — это не переменная animal, расположенная на основном уровне программы.

Чтобы получить доступ не к локальной переменной внутри функции, а к глобальной переменой, нужно явно использовать ключевое слово global (вы знали, что я это скажу: явное лучше, чем неявное):

>>> animal = 'fruitbat'

>>> def change_and_print_global():

...     global animal

...     animal = 'wombat'

...     print('inside change_and_print_global:', animal)

...

>>> animal

'fruitbat'

>>> change_and_print_global()

inside change_and_print_global: wombat

>>> animal

'wombat'

Если вы не используете ключевое слово global внутри функции, Python задействует локальное пространство имен и переменная, таким образом, будет локальной. Она пропадет после того, как функция завершит работу.

Python предоставляет две функции для доступа к содержимому ваших пространств имен:

locals() — возвращает словарь, содержащий имена локального пространства имен;

globals() — возвращает словарь, содержащий имена глобального пространства имен.

Вот как они используются:

>>> animal = 'fruitbat'

>>> def change_local():

...     animal = 'wombat'  # локальная переменная

...     print('locals:', locals())

...

>>> animal

'fruitbat'

>>> change_local()

locals: {'animal': 'wombat'}

>>> print('globals:', globals()) # немного переформатировано для представления

globals: {'animal': 'fruitbat',

'__doc__': None,

'change_local': <function change_it at 0x1006c0170>,

'__package__': None,

'__name__': '__main__',

'__loader__': <class '_frozen_importlib.BuiltinImporter'>,

'__builtins__': <module 'builtins'>}

>>> animal

'fruitbat'

Локальное пространство имен внутри функции change_local() содержало только локальную переменную animal. Глобальное пространство имен содержало отдельную глобальную переменную animal и многое другое.

Использование символов _ и __ в именах

Имена, которые начинаются с двух нижних подчеркиваний (__) и заканчиваются ими, зарезервированы для использования внутри Python, поэтому вам не следует применять их к своим собственным переменным. Подобный шаблон именования был выбран потому, что разработчики, скорее всего, не стали бы им пользоваться для создания имен своих переменных.

Например, имя функции находится в системной переменной function.__name__, а имя ее строки документации — в function.__doc__:

>>> def amazing():

...     '''This is the amazing function.

...     Want to see it again?'''

...     print('This function is named:', amazing.__name__)

...     print('And its docstring is:', amazing.__doc__)

...

>>> amazing()

This function is named: amazing

And its docstring is: This is the amazing function.

    Want to see it again?

Как вы видели ранее в содержимом globals, основной программе присвоено специальное имя __main__.

Рекурсия

До сих пор мы видели функции, которые выполняют какие-то определенные действия и, возможно, вызывают другие функции. Но что, если функция вызовет саму себя? Это называется рекурсией. Так же как не нужны бесконечные циклы while или for, не нужна и бесконечная рекурсия. Стоит ли нам волноваться за целостность пространственно-временного континуума?

Python снова спасает Вселенную, генерируя исключение, если вы зайдете слишком далеко:

>>> def dive():

...     return dive()

...

>>> dive()

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

  File "<stdin>", line 2, in dive

  File "<stdin>", line 2, in dive

  File "<stdin>", line 2, in dive

  [Previous line repeated 996 more times]

RecursionError: maximum recursion depth exceeded

Рекурсия может быть полезна, если вы работаете с чем-то вроде списков, содержащих списки, которые содержат списки.

Предположим, вы хотите «уплотнить» все подсписки независимо от того, насколько глубоко они вложены. Для этого отлично подойдет функция-генератор:

>>> def flatten(lol):

...     for item in lol:

...         if isinstance(item, list):

...             for subitem in flatten(item):

...                 yield subitem

...         else:

...             yield item

...

>>> lol = [1, 2, [3,4,5], [6,[7,8,9], []]]

>>> flatten(lol)

<generator object flatten at 0x10509a750>

>>> list(flatten(lol))

[1, 2, 3, 4, 5, 6, 7, 8, 9]

В Python 3.3 появилось выражение yieldfrom, которое позволяет генератору передавать часть работы другому генератору. Мы можем использовать его для того, чтобы упростить функцию flatten():

>>> def flatten(lol):

...     for item in lol:

...         if isinstance(item, list):

...             yield from flatten(item)

...         else:

...             yield item

...

>>> lol = [1, 2, [3,4,5], [6,[7,8,9], []]]

>>> list(flatten(lol))

[1, 2, 3, 4, 5, 6, 7, 8, 9]

Асинхронные функции

Ключевые слова async и await были добавлены в Python 3.5 для того, чтобы можно было определять и запускать асинхронные функции. Их можно описать так:

они относительно новые;

• они значительно отличаются друг от друга, чтобы их сложнее было понять;

они станут более важными и более изученными со временем.

По этим причинам я перенес рассмотрение вопросов, связанных с асинхронностью, в приложение В. Сейчас вам нужно знать, что, если вы в объявлении функции перед словом def видите слово async, это значит, что функция асинхронная. Аналогично, если перед вызовом функции вы видите слово await, эта функция также является асинхронной. Основное различие между асинхронными и обычными функциями заключается в том, что асинхронные могут «передавать управление» вместо того, чтобы заставлять основной поток выполнения ждать до конца их выполнения.

Исключения

В некоторых языках программирования ошибки отображаются с помощью специальных возвращаемых значений. В Python, если дело пахнет жареным, используется исключение: код, который выполняется, когда происходит связанная с ним ошибка.

Какие-то из исключений вы уже видели, пытаясь получить доступ к позиции, не входящей в список/кортеж, или к словарю с несуществующим ключом. Когда вы выполняете код, который при определенных условиях может не сработать, вам также понадобятся обработчики исключений для перехвата любых потенциальных ошибок.

Чтобы пользователь знал о происходящем, рекомендуется добавлять обработчики исключений везде, где исключение может быть сгенерировано. Не всегда у вас получится исправить ошибку, но вы хотя бы будете знать, при каких обстоятельствах она появилась, и аккуратно завершите программу. Если исключение сгенерировалось в функции и там не обработалось, оно будет всплывать до тех пор, пока его не поймает соответствующий обработчик в одной из вызывающих функций. Если вы не предоставите собственный обработчик исключений, Python выведет сообщение об ошибке и некоторую информацию о том, где произошла ошибка, а затем завершит программу, как показано в следующем фрагменте кода:

>>> short_list = [1, 2, 3]

>>> position = 5

>>> short_list[position]

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

IndexError: list index out of range

Обрабатываем ошибки с помощью операторов try и except

Делай или не делай. Не надо пытаться.

Йода

Вместо того чтобы полагаться на волю случая, размещайте свой код в блоке try и используйте блок except для обработки ошибки:

>>> short_list = [1, 2, 3]

>>> position = 5

>>> try:

...     short_list[position]

... except:

...     print('Need a position between 0 and', len(short_list)-1, ' but got',

...            position)

...

Need a position between 0 and 2 but got 5

Запускается код внутри блока try. Если произошла ошибка, генерируется исключение и выполняется код, расположенный внутри блока except. Если ошибок не произошло, блок except пропускается.

Отсутствие аргументов в блоке except, как в предыдущем примере, позволяет ловить исключения любого типа. Если предполагается, что могут возникнуть несколько типов исключений, лучшим решением будет предоставить отдельный обработчик для каждого из них. Никто не заставляет это делать — можно использовать блок except без аргументов, но тогда обработка будет достаточно поверхностной (что-то вроде вывода на экран строки Произошлаошибка). Разрешается использовать любое количество обработчиков исключений.

Иногда вам будут нужны и другие подробности исключения, а не только его тип. Вы можете получить объект исключения целиком в переменной имя, если используете такую форму:

except тип_исключения as имя

В следующем примере выполняется проверка на IndexError, поскольку именно это исключение вызывается, когда вы предоставляете недействительную позицию последовательности. Исключение IndexError сохраняется в переменной err, а любое другое исключение — в переменной other. В примере на экран выводится все, что хранится в переменной other, для демонстрации того, что вы получаете в этом объекте:

>>> short_list = [1, 2, 3]

>>> while True:

...     value = input('Position [q to quit]? ')

...     if value == 'q':

...         break

...     try:

...         position = int(value)

...         print(short_list[position])

...     except IndexError as err:

...         print('Bad index:', position)

...     except Exception as other:

...         print('Something else broke:', other)

...

Position [q to quit]? 1

2

Position [q to quit]? 0

1

Position [q to quit]? 2

3

Position [q to quit]? 3

Bad index: 3

Position [q to quit]? 2

3

Position [q to quit]? two

Something else broke: invalid literal for int() with base 10: 'two'

Position [q to quit]? q

Ввод позиции 3, как и ожидалось, генерирует исключение IndexError. Ввод слова two не понравился функции int(), которую мы обработали во втором, универсальном коде except.

Создаем собственные исключения

В предыдущем подразделе обсуждалась обработка исключений, но все исключения (такие как IndexError) заранее определены в Python или его стандартных библиотеках. Вы можете использовать любые из этих исключений, но можете также и определять собственные типы исключений, чтобы обрабатывать особые ситуации, которые могут возникнуть в ваших программах.

109149.png

Для этого потребуется определить новый тип объекта с помощью класса. Заниматься классами мы начнем в главе 10, поэтому, если вы с ними не знакомы, можете обратиться к соответствующему разделу.

Любое исключение является классом, частным случаем класса Exception. Создадим исключение, которое называется UppercaseException и которое будет вызываться при нахождении слова, записанного буквами в верхнем регистре:

>>> class UppercaseException(Exception):

...     pass

...

>>> words = ['eeenie', 'meenie', 'miny', 'MO']

>>> for word in words:

...     if word.isupper():

...         raise UppercaseException(word)

...

Traceback (most recent call last):

  File "<stdin>", line 3, in <module>

__main__.UppercaseException: MO

Мы даже не определяли какое-то поведение для UppercaseException (обратите внимание — мы просто использовали pass), позволив его родительскому классу Exception самостоятельно разобраться, что именно выводить на экран при генерации исключения.

Вы можете получить доступ к самому объекту исключения и вывести его на экран:

>>> try:

...     raise OopsException('panic')

... except OopsException as exc:

...     print(exc)

...

panic

Читайте далее

Объекты! В книге, посвященной объектно-ориентированному языку, мы когда-нибудь обязаны были до них добраться.

Упражнения

9.1. Определите функцию good(), которая возвращает следующий список: ['Harry','Ron','Hermione'].

9.2. Определите функцию генератора get_odds(), которая возвращает нечетные числа из диапазона range(10). Используйте цикл for, чтобы найти и вывести третье возвращенное значение.

9.3. Определите декоратор test, который выводит строку 'start' при вызове функции и строку 'end', когда функция завершает свою работу.

9.4. Определите исключение OopsException. Сгенерируйте его и посмотрите, что произойдет. Затем напишите код, позволяющий поймать это исключение и вывести строку 'Caughtanoops'.

Имена Args и Kwargs очень хорошо подошли бы пиратским попугаям.

А также, начиная с версии Python 3.5, при объединении словарей с помощью конструкции {**a, **b}, которую вы видели в главе 8.

Как в подростковых фильмах ужасов, когда герои узнают: «Звонок идет изнутри дома!»

Как в старом врачебном анекдоте: «Мне больно, когда я делаю вот так». — «Значит, больше так не делайте».

Это как сказать: «Я хочу, чтоб мне давали монетку каждый раз, когда я хочу, чтобы мне дали монетку».

Это еще один популярный вопрос на собеседованиях. Давайте соберем всю коллекцию!

Интересно, это выражение характерно только для северного полушария? Говорят ли австралийцы и новозеландцы«дело пахнет вареным», когда все идет не так, как хотелось бы?

Назад: Глава 8. Словари и множества
Дальше: Глава 10. Ой-ой-ой: объекты и классы

dofermerdat
этот. породы кроликов с фотографиями мясныефермер ру форум рассадакрупнейшие сельскохозяйственные районы зарубежной европыкрс мясо