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

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

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

На то, чтобы разобраться в концепциях, которые мы будем здесь обсуждать, может потребоваться больше времени, чем вы ожидаете. Не волнуйтесь, это совершенно нормально. Со мной такое бывало. Вам может показаться, что вы безрезультатно бьетесь головой о стену, а потом в голове вдруг «щелкнет» и все встанет на свои места, когда вы будете к этому готовы.

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

def yell(text):
    return text.upper() + '!'

>>> yell('hello')
'HELLO!'

Функции — это объекты

Все данные в программе Python представлены объектами или отношениями между объектами. Строки, списки, модули и функции — все это объекты. В Python нет ничего особенного в функциях.

Поскольку функция yell является объектом в Python, вы можете присвоить ее другой переменной, как и любой другой объект:

>>> bark = yell

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

>>> bark('woof')
'WOOF!'

Объекты функций и их имена — это две разные вещи. Вот еще одно доказательство: исходное имя функции (yell) можно удалить. Поскольку другое имя (bark) все еще указывает на базовую функцию, вы все еще можете вызвать функцию через него:

>>> del yell

>>> yell('hello?')
NameError: "name 'yell' is not defined"

>>> bark('hey')
'HEY!'

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

>>> bark.__name__
'yell'

Хотя функции name по-прежнему "yell", это не влияет на то, как можно получить к ней доступ из кода. Этот идентификатор — всего лишь средство отладки. Переменная, указывающая на функцию, и сама функция — это две разные вещи.

Начиная с Python 3.3 существует также qualname, который служит аналогичной цели и предоставляет строку квалифицированного имени для разграничения имен функций и классов.

Функции можно хранить в структурах данных

Функции можно хранить в структурах данных, как и другие объекты. Например, можно добавить функции в список:

>>> funcs = [bark, str.lower, str.capitalize]
>>> funcs
[<function yell at 0x10ff96510>,
 <method 'lower' of 'str' objects>,
 <method 'capitalize' of 'str' objects>]

Доступ к объектам функций, хранящихся внутри списка, осуществляется так же, как и к любому другому типу объектов:

>>> for f in funcs:
...     print(f, f('hey there'))
<function yell at 0x10ff96510> 'HEY THERE!'
<method 'lower' of 'str' objects> 'hey there'
<method 'capitalize' of 'str' objects> 'Hey there'

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

>>> funcs[0]('heyho')
'HEYHO!'

Функции можно передавать другим функциям

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

def greet(func):
    greeting = func('Hi, I am a Python program')
    print(greeting)

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

>>> greet(yell)
'HI, I AM A PYTHON PROGRAM!'

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

def whisper(text):
    return text.lower() + '...'

>>> greet(whisper)
'hi, i am a python program...'

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

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

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

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

>>> list(map(yell, ['hello', 'hey', 'hi']))
['HELLO!', 'HEY!', 'HI!']

map просмотрел весь список и применил функцию yell к каждому элементу.

Функции могут быть вложенными

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

def speak(text):
    def whisper(t):
        return t.lower() + '...'
    return whisper(text)

>>> speak('Hello, World')
'hello, world...'

Итак, что же здесь происходит? Каждый раз, когда вы вызываете speak, он определяет новую внутреннюю функцию whisper и затем вызывает ее.

И вот в чем загвоздка — whisper не существует вне speak:

>>> whisper('Yo')
NameError: "name 'whisper' is not defined"

>>> speak.whisper
AttributeError: "'function' object has no attribute 'whisper'"

Но что, если вы действительно хотите получить доступ к вложенной функции whisper из внешней функции speak? Ну, функции являются объектами — можно вернуть внутреннюю функцию вызывающей родительской функции.

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

def get_speak_func(volume):
    def whisper(text):
        return text.lower() + '...'
    def yell(text):
        return text.upper() + '!'
    if volume > 0.5:
        return yell
    else:
        return whisper

Обратите внимание, что get_speak_func фактически не вызывает ни одну из своих внутренних функций — она просто выбирает соответствующую функцию на основе аргумента volume, а затем возвращает объект функции:

>>> get_speak_func(0.3)
<function get_speak_func.<locals>.whisper at 0x10ae18>

>>> get_speak_func(0.7)
<function get_speak_func.<locals>.yell at 0x1008c8>

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

>>> speak_func = get_speak_func(0.7)
>>> speak_func('Hello')
'HELLO!'

Подумайте об этом немного. Это означает, что функции могут не только принимать поведение через аргументы, но и возвращать его. Круто же?

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

Функции могут захватывать локальное состояние

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

А теперь пристегнитесь, потому что дальше будет еще безумнее — мы собираемся погрузиться в еще более глубокую область функционального программирования. (У вас ведь был перерыв на кофе, верно?)

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

Чтобы проиллюстрировать эту мысль, я немного перепишу предыдущий пример get_speak_func. Чтобы сделать возвращаемую функцию сразу вызываемой, новая версия сразу принимает аргументы «громкость» и «текст»:

def get_speak_func(text, volume):
    def whisper():
        return text.lower() + '...'
    def yell():
        return text.upper() + '!'
    if volume > 0.5:
        return yell
    else:
        return whisper

>>> get_speak_func('Hello, World', 0.7)()
'HELLO, WORLD!'

Посмотрите внимательно на внутренние функции whisper и yell. Заметили, что у них больше нет параметра text? Но каким-то образом они все еще могут обращаться к параметру text, определенному в родительской функции. На самом деле, они, похоже, захватывают и «запоминают» значение этого аргумента.

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

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

def make_adder(n):
    def add(x):
        return x + n
    return add

>>> plus_3 = make_adder(3)
>>> plus_5 = make_adder(5)

>>> plus_3(4)
7
>>> plus_5(4)
9

В этом примере make_adder служит фабрикой для создания и настройки функций "adder". Обратите внимание, что функции "adder" по-прежнему могут обращаться к аргументу n функции make_adder (вложенная область видимости).

Объекты могут вести себя как функции

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

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

class Adder:
    def __init__(self, n):
         self.n = n
    def __call__(self, x):
        return self.n + x

>>> plus_3 = Adder(3)
>>> plus_3(4)
7

За кулисами «вызов» экземпляра объекта как функции пытается выполнить метод call объекта.

Конечно, не все объекты могут быть вызываемыми. Поэтому существует встроенная функция callable для проверки того, является ли объект вызываемым или нет:

>>> callable(plus_3)
True
>>> callable(yell)
True
>>> callable(False)
False

Основные выводы

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

  • Функции первого класса позволяют абстрагироваться и передавать поведение в программах.

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

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


Как работать с файлами в Python? Поговорим об этом на открытом уроке, в ходе которого научимся с помощью Python читать и записывать данные в текстовые файлы различных форматов: JSON, CSV, XML. Обсудим особенности работы с каждым из этих форматов и изучим подходящие библиотеки. Вспомним, что такое контекстные менеджеры и поговорим, почему их нужно использовать для работы с файлами.

Записаться на бесплатный урок можно на странице онлайн-курса "Python QA Engineer".

Комментарии (3)


  1. Jack444
    00.00.0000 00:00
    -1

    Что появилось раньше, функция или класс? =)


    1. unclegluk
      00.00.0000 00:00

      Машинные коды.


  1. unclegluk
    00.00.0000 00:00

    Статья не вычитана.

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

    Захотелось последовательно. И вот:

    Имя yell по ходу рассказа удалено, но продолжает использоваться, что приводит к ошибке.

    И вот в чем загвоздка — whisper не существует вне speak:

    Существует. Она была ранее определена вне функции speak:

    >>> def whisper(text):
          return text.lower()+'...'
    
    >>> greet(whisper)
    hi, i am a python program...
    >>> list(map(yell,['hello','hey','hi']))
    ['HELLO!', 'HEY!', 'HI!']
    >>> def speak(text):
          def whisper(t):
              return t.lower()+'...'
          return whisper(text)
    
    >>> speak('Hello, World!')
    'hello, world!...'
    >>> whisper('Yo')
    'yo...'

    Может быть стоило использовать разные имена? Потому что дальше тоже есть использование одних и тех же имен для разных нужд, что может привести к путанице в голове у новичка или даже у профессионала, который просто принял работу уволившегося человека. В классах имена — другой разговор, это унификация имен методов: set, get и прочие. В общем же случае в этом нет необходимости и даже вредно.