Вложенная функция в Python

86

Какие преимущества или последствия мы могли бы получить с таким кодом Python:

class some_class(parent_class):
    def doOp(self, x, y):
        def add(x, y):
            return x + y
        return add(x, y)

Я нашел это в проекте с открытым исходным кодом, делая что-то полезное внутри вложенной функции, но ничего не делая за ее пределами, кроме ее вызова. (Фактический код можно найти здесь .) Почему кто-то может его так кодировать? Есть ли какое-то преимущество или побочный эффект для написания кода внутри вложенной функции, а не во внешней нормальной функции?

Хосам Али
источник
4
Это настоящий код, который вы нашли, или просто построенный вами упрощенный пример?
MAK,
Это упрощенный пример. Актуальный код можно найти здесь: bazaar.launchpad.net/%7Eopenerp/openobject-server/trunk/…
Хосам Али
2
Ваша ссылка (которая указывает на HEAD) теперь неточная. Попробуйте: bazaar.launchpad.net/~openerp/openobject-server/trunk/annotate/…
Крейг Маккуин,
Ты прав, Крейг. Спасибо.
Хосам Али,

Ответы:

113

Обычно вы делаете это для закрытия :

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

plus5 = make_adder(5)
print(plus5(12))  # prints 17

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

Адам Розенфилд
источник
5
Для этого я бы предпочел партиалы: plus5 = functools.partial(operator.add, 5). Декораторы были бы лучшим примером для замыканий.
Йохен Ритцель
4
Спасибо, но, как вы можете видеть в опубликованном мною фрагменте, это не так: вложенная функция просто вызывается во внешней функции.
Хосам Али,
58

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

Пример игрушки:

import sys

def Foo():
    def e(s):
        sys.stderr.write('ERROR: ')
        sys.stderr.write(s)
        sys.stderr.write('\n')
    e('I regret to inform you')
    e('that a shameful thing has happened.')
    e('Thus, I must issue this desultory message')
    e('across numerous lines.')
Foo()
Росс Роджерс
источник
4
Единственная проблема заключается в том, что иногда это может затруднить модульное тестирование, потому что вы не можете получить доступ к внутренней функции.
Challenger5
1
Они такие милые!
taper
1
FWIY @ Challenger5, если внутренняя функция аналогична функциям частного класса, они все равно не проходят модульное тестирование. Я начал делать это, чтобы сделать методы более удобочитаемыми, сохраняя определение близкое к рассматриваемому методу.
Mitchell Currie
27

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

def helper(feature, resultBuffer):
  resultBuffer.print(feature)
  resultBuffer.printLine()
  resultBuffer.flush()

def save(item, resultBuffer):

  helper(item.description, resultBuffer)
  helper(item.size, resultBuffer)
  helper(item.type, resultBuffer)

можно записать следующим образом, что, возможно, лучше читается

def save(item, resultBuffer):

  def helper(feature):
    resultBuffer.print(feature)
    resultBuffer.printLine()
    resultBuffer.flush()

  helper(item.description)
  helper(item.size)
  helper(item.type)
Куба
источник
8

Я не могу представить себе какой-либо веской причины для такого кода.

Может быть, была причина для внутренней функции в старых версиях, как и в других Ops.

Например, это имеет немного больше смысла:

class some_class(parent_class):
    def doOp(self, op, x, y):
        def add(x, y):
            return x + y
        def sub(x,y):
            return x - y
        return locals()[op](x,y)

some_class().doOp('add', 1,2)

но тогда внутренняя функция должна быть ("частным") методом класса:

class some_class(object):
    def _add(self, x, y):
        return x + y
    def doOp(self, x, y):
        return self._add(x,y)
Йохен Ритцель
источник
Да, возможно, doOp взял строку, чтобы указать, какой оператор использовать для аргументов ...
Скиллдрик,
1
@ArtOfWarfare предпочтительнее просто использовать модули. Занятия для гос.
aehlke
6

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

Авгученко
источник
1

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

Дэниел Розман
источник
1
Я добавил ссылку на исходный код в комментарии к своему вопросу. Как видите, мой пример - упрощенный, но он почти такой же.
Хосам Али,