Если функция A требуется только функцией B, то A определяется внутри B?

Простой пример. Два метода, один вызванный из другого:

def method_a(arg):
    some_data = method_b(arg)

def method_b(arg):
    return some_data

В Python мы можем объявить def внутри другого def. Итак, если method_b требуется и вызывается только из method_a, следует ли объявить method_b внутри method_a? например:

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b

Или мне следует избегать этого?

Ответ 1

>>> def sum(x, y):
...     def do_it():
...             return x + y
...     return do_it
... 
>>> a = sum(1, 3)
>>> a
<function do_it at 0xb772b304>
>>> a()
4

Это то, что вы искали? Он назывался closure.

Ответ 2

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

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

Update:

Здесь доказательство того, что вложенность их медленнее (с использованием Python 3.6.1), хотя, по общему признанию, не так много в этом тривиальном случае:

setup = """
class Test(object):
    def separate(self, arg):
        some_data = self._method_b(arg)

    def _method_b(self, arg):
        return arg+1

    def nested(self, arg):

        def method_b2(self, arg):
            return arg+1

        some_data = method_b2(self, arg)

obj = Test()
"""
from timeit import Timer
print(min(Timer(stmt='obj.separate(42)', setup=setup).repeat()))  # -> 0.24479823284461724
print(min(Timer(stmt='obj.nested(42)', setup=setup).repeat()))    # -> 0.26553459700452575

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

Ответ 3

Функция внутри функции обычно используется для закрытия.

(Существует много споров над что именно делает закрыть закрытие.)

Здесь приведен пример использования встроенного sum(). Он определяет start один раз и использует его с тех пор:

def sum_partial(start):
    def sum_start(iterable):
        return sum(iterable, start)
    return sum_start

При использовании:

>>> sum_with_1 = sum_partial(1)
>>> sum_with_3 = sum_partial(3)
>>> 
>>> sum_with_1
<function sum_start at 0x7f3726e70b90>
>>> sum_with_3
<function sum_start at 0x7f3726e70c08>
>>> sum_with_1((1,2,3))
7
>>> sum_with_3((1,2,3))
9

Встроенное закрытие python

functools.partial является примером закрытия.

В документах python это примерно эквивалентно:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

(Kudos to @user225312 ниже для ответа. Я считаю, что этот пример легче понять, и, надеюсь, поможет ответить на комментарий @mango.)

Ответ 4

На самом деле хорошо объявить одну функцию внутри другой. Это особенно полезно для создания декораторов.

Однако, как правило, если функция сложна (более 10 строк), было бы лучше объявить ее на уровне модуля.

Ответ 5

Я нашел этот вопрос, потому что я хотел задать вопрос, почему есть влияние производительности, если вы используете вложенные функции. Я провел тесты для следующих функций, используя Python 3.2.5 на ноутбуке Windows с четырехъядерным процессором Intel i5-2530M с тактовой частотой 2,5 ГГц.

def square0(x):
    return x*x

def square1(x):
    def dummy(y):
        return y*y
    return x*x

def square2(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    return x*x

def square5(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    def dummy3(y):
        return y*y
    def dummy4(y):
        return y*y
    def dummy5(y):
        return y*y
    return x*x

Я измерил следующие 20 раз, также для square1, square2 и square5:

s=0
for i in range(10**6):
    s+=square0(i)

и получили следующие результаты

>>> 
m = mean, s = standard deviation, m0 = mean of first testcase
[m-3s,m+3s] is a 0.997 confidence interval if normal distributed

square? m     s       m/m0  [m-3s ,m+3s ]
square0 0.387 0.01515 1.000 [0.342,0.433]
square1 0.460 0.01422 1.188 [0.417,0.503]
square2 0.552 0.01803 1.425 [0.498,0.606]
square5 0.766 0.01654 1.979 [0.717,0.816]
>>> 

square0 не имеет вложенной функции, square1 имеет одну вложенную функцию, square2 имеет две вложенные функции и square5 имеет пять вложенных функций. Вложенные функции объявляются, но не вызываются.

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

Файл Python для всего теста, который генерирует этот вывод, можно найти в ideone.

Ответ 6

Как правило, нет, не определяйте функции внутри функций.

Если у вас есть действительно веская причина. Который у вас нет.

Почему бы и нет?

Что является действительно хорошей причиной для определения функций внутри функций?

Когда вы действительно хотите, это dingdang закрытие.

Ответ 7

Это просто принцип API-интерфейсов.

Использование python. Хорошая идея, чтобы избежать воздействия API в космосе (модуль или класс), функция является хорошим местом инкапсуляции.

Это может быть хорошей идеей. когда вы обеспечиваете

  • внутренняя функция ТОЛЬКО, используемая внешней функцией.
  • Функция insider имеет хорошее имя, чтобы объяснить его цель, потому что код говорит.
  • код не может напрямую понять ваши коллеги (или другой код-ридер).

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

Просто с моего опыта, Может быть, неправильно понял ваш вопрос.

Ответ 8

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

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

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

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

Не делайте преждевременную оптимизацию только при использовании "внутренних функций BAD" во всех написанных питонов. Пожалуйста.

Ответ 9

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

...
some_data = method_b() # not some_data = method_b

иначе, some_data будет функцией.

Наличие на уровне модуля позволит другим функциям использовать method_b(), и если вы используете что-то вроде Sphinx (и autodoc) для документации, это также позволит вам документировать method_b.

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

Ответ 10

Сделайте что-то вроде:

def some_function():
    some_other_function()
def some_other_function():
    return 42 

если вы должны запустить some_function(), тогда он запустит some_other_function() и вернет 42.

EDIT: Я изначально заявил, что вы не должны определять функцию внутри другого, но было указано, что иногда это бывает практично.

Ответ 11

Ответ на

mdlp не помог мне.

Это:

def some_function():
    return some_other_function()
def some_other_function():
    return 42

print some_function()