Если функция A требуется только функцией B, следует ли определять A внутри B? - PullRequest
134 голосов
/ 28 января 2011

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

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

Или я должен избегать этого?

Ответы [ 12 ]

131 голосов
/ 28 января 2011
>>> 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

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

44 голосов
/ 28 января 2011

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

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

Обновление:

Вот доказательство того, что их вложение медленнее (с использованием 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).Кроме того, вложенная функция фактически вызывается в этой версии, в отличие от вашей.

26 голосов
/ 07 июня 2014

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

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

Вот пример использования встроенного 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 является примером закрытия.

Из документации по питону , это примерно эквивалентно:

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

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

14 голосов
/ 17 августа 2017

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

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

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

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

Когда то, что вы на самом деле хотите, является дингдангом закрытием .

10 голосов
/ 28 января 2011

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

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

7 голосов
/ 06 октября 2014

Я нашел этот вопрос, потому что хотел задать вопрос, почему это влияет на производительность, если кто-то использует вложенные функции.Я выполнил тесты для следующих функций, используя 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 .

4 голосов
/ 14 августа 2016

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

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

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

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

Не выполняйте преждевременную оптимизацию, просто используя «внутренние функции BAD» во всем написанном вами коде Python.Пожалуйста.

4 голосов
/ 18 декабря 2012

Это всего лишь принцип об API экспозиции.

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

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

  1. внутренняя функция ТОЛЬКО используется внешней функцией.* Инсайдерская функция
  2. имеет хорошее имя, объясняющее ее назначение, потому что код говорит.
  3. код не может быть непосредственно понят вашим коллегам (или другим читателям кода).

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

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

1 голос
/ 08 сентября 2015

Ответ mdlp у меня не сработал.

Это сделал:

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

print some_function()
1 голос
/ 30 мая 2014

сделать что-то вроде:

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

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

РЕДАКТИРОВАТЬ: Первоначально я сказал, что вы не должны определять функцию внутри другого, но было отмечено, что это иногда полезно делать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...