Является ли вложенная функция хорошим подходом, когда требуется только одна функция?

Предположим, что function A требуется только function 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(arg)

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


person nukl    schedule 28.01.2011    source источник
comment
Вам не нужно определять функцию внутри другой, если только вы не делаете что-то ДЕЙСТВИТЕЛЬНО крутое. Однако, пожалуйста, поясните, что вы пытаетесь сделать, чтобы мы могли дать более полезный ответ.   -  person inspectorG4dget    schedule 28.01.2011
comment
Вы понимаете, что второй пример отличается, потому что вы не звоните method_b? (@inspector: строго говоря, вам это нужно, но это очень полезно, когда вы немного занимаетесь функциональным программированием, в частности, закрытием).   -  person    schedule 28.01.2011
comment
@delnan: Я думаю, вы имели в виду, что вам не нужно, строго говоря, но ...   -  person martineau    schedule 29.01.2011
comment
Как упоминал @delnan, это обычное дело в случае замыканий, поэтому я не думаю, что это можно назвать фанком; однако, если замыкания не являются необходимыми (что я предполагаю, что они не являются в данном случае), размещение одной функции внутри другой не кажется необходимым, эффективным или аккуратным. Если вам не нужны закрытия, я бы остановился на первом шаблоне.   -  person Trevor    schedule 04.12.2013
comment
Сценарии использования внутренних функций прекрасно описаны в ссылке: https://realpython.com/blog/python/inner-functions-what-are-they-good-for/. Если ваше использование не подходит ни для одного из случаев, лучше этого избегайте.   -  person    schedule 07.06.2016
comment
Отвечает ли это на ваш вопрос? Как составить цепочку декораторов функций?   -  person Henry Henrinson    schedule 19.02.2020


Ответы (12)


>>> 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

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

person user225312    schedule 28.01.2011
comment
почему бы просто не сделать def sum (x, y): return x + y? - person mango; 03.06.2014
comment
@mango: Это просто игрушечный пример, чтобы передать концепцию - в реальном использовании то, что делает do_it(), по-видимому, будет немного сложнее, чем то, что можно обработать с помощью некоторой арифметики в одном операторе return. - person martineau; 02.09.2014
comment
@mango ответил на ваш вопрос чуть более полезным примером. stackoverflow.com/a/24090940/2125392 - person CivFan; 22.11.2014
comment
Это не отвечает на вопрос. - person Hunsu; 30.06.2015
comment
Хороший ответ, но где вопрос? Не здесь. OP спросил, можно ли сделать это, если method_b используется только method_a. Больше он ничего не просил. Без закрытия. - person Mayou36; 26.07.2017

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

person martineau    schedule 28.01.2011
comment
На самом деле он не полностью компилирует внутреннюю функцию каждый раз, когда вызывается внешняя функция, хотя он должен создавать объект функции, что занимает немного времени. С другой стороны, имя функции становится локальным, а не глобальным, поэтому функцию вызывать быстрее. В моих испытаниях по времени большую часть времени это мыть; он может быть даже быстрее с внутренней функцией, если вы вызовете ее много раз. - person kindall; 29.01.2011
comment
@kindall: Не похоже, что быстрее использовать вложенную функцию - см. тестовый код в моем обновленном ответе - так что, по крайней мере, в этом случае то, что вы сказали, не соответствует действительности. - person martineau; 29.01.2011
comment
Да, вам понадобится несколько вызовов внутренней функции. Если вы вызываете его в цикле или несколько раз, преимущество наличия локального имени для функции начнет перевешивать затраты на создание функции. В моих испытаниях это происходит, когда вы вызываете внутреннюю функцию примерно 3-4 раза. Конечно, вы можете получить такое же преимущество (почти без таких затрат), определив локальное имя для функции, например method_b = self._method_b, а затем вызовите method_b, чтобы избежать повторного поиска атрибутов. (Бывает, что в последнее время я МНОГО прикидываю. :) - person kindall; 29.01.2011
comment
@kindall: Да, это правда. Я изменил свой временной тест, так что он сделал 30 вызовов вторичной функции, и результаты изменились. Я удалю свой ответ после того, как вы его увидите. Спасибо за просветление. - person martineau; 29.01.2011
comment
Нет, не удаляйте его; Думаю, здесь есть хорошая информация. Я только что обнаружил, что если вы используете декоратор для внутренней функции, декоратор вызывается каждый раз, когда вы вызываете внешнюю функцию. Теперь ЭТО может повлиять на ваше время! - person kindall; 29.01.2011
comment
@kindall: Интересно. Точно так же я независимо заметил, что добавление setattr(Test, 'method_b2', method_b2) сразу после его определения, чтобы сделать его реальным методом, вместе с изменением вызова на self.method_b2(arg), потребовало в 2 раза больше времени для выполнения 6 вызовов. - person martineau; 29.01.2011
comment
Просто хотел объяснить свой -1, потому что это, тем не менее, информативный ответ: я дал ему -1, потому что он фокусирует внимание на тривиальной разнице в производительности (в большинстве сценариев создание объекта кода займет лишь часть времени выполнения функции ). В этих случаях важно учитывать, может ли наличие встроенной функции улучшить читаемость кода и удобство обслуживания, что, как я думаю, часто происходит, потому что вам не нужно искать / прокручивать файлы, чтобы найти соответствующий код. - person Blixt; 04.09.2015
comment
@Blixt: я думаю, что ваша логика ошибочна (и голосование против несправедливо), потому что крайне маловероятно, что другой метод того же класса будет очень далеко от другого в том же классе, даже если он не вложен (и крайне маловероятно, что он будет в другой файл). Кроме того, самое первое предложение в моем ответе: «Вы не особо выиграете от этого», что указывает на тривиальную разницу. - person martineau; 07.11.2015
comment
-1 по той же причине, что и @Blixt. Приятно знать, что нет разницы в реальном времени, но вопрос в том, хорошая это идея или нет. И вы не особо выиграете, сделав это в общем: вы получите, например, инкапсуляцию. Метод_a не зависит от другой функции. Ответ неплохой, не поймите неправильно, просто частично отвечает на вопрос (и в основном на менее актуальную часть). - person Mayou36; 26.07.2017
comment
@ Mayou36: Вы, конечно, имеете право на собственное мнение. Мой ответ намеренно расплывчатый, потому что из-за небольшой разницы в скорости единственные другие факторы, которые следует учитывать, в основном субъективны, например, относящиеся к важности инкапсуляции, удобочитаемости и т. Д. - person martineau; 26.07.2017
comment
Что ж, я бы сказал, что если речь идет о мнении, это (или подразумевает) ответ. Вы бы сказали, что нет лучшей практики? Потому что есть много вещей (особенно в Python, желательно только один способ ...), которые должны или не должны выполняться из-за передовых практик и соглашений. Скорость может быть фактором, я согласен, и вы хорошо показали, что это явно не главная проблема. Но по-прежнему отсутствует часть рекомендаций по питону. Вам не кажется, что было бы неплохо узнать об этом? - person Mayou36; 27.07.2017
comment
Технически вопросы, ответы на которые в основном основаны на мнении, здесь не по теме. Во-вторых, на все передовые практики нет однозначных ответов. Я согласен с этим в зависимости от обстоятельств, если кто-то не может показать вескую причину, чтобы сделать это тем или иным способом. - person martineau; 27.07.2017

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

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

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

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

person CivFan    schedule 06.06.2014
comment
-1 да, их обычно используют в качестве укупорочных средств. А теперь перечитайте вопрос. Он в основном спросил, можно ли использовать концепцию, которую он показывает, для случая b. Сказать ему, что это часто используется для случая а, - неплохой ответ, но неправильный для этого вопроса. Его интересует, хорошо ли это делать, например, для инкапсуляции. - person Mayou36; 26.07.2017
comment
@ Mayou36 Честно говоря, вопрос довольно открытый - вместо того, чтобы пытаться ответить на все возможные случаи, я подумал, что лучше сосредоточиться на одном. Тоже вопрос не очень ясный. Например, во втором примере это подразумевает закрытие, хотя, вероятно, имелось в виду не это. - person CivFan; 26.07.2017
comment
@ Mayou36 Он в основном спросил, можно ли использовать концепцию, которую он показывает, для случая b. Нет, вопрос заключается в том, следует ли его использовать. OP уже знает, что его можно использовать. - person CivFan; 26.07.2017
comment
ну, если method_b требуется и вызывается только из method_a, должен ли я объявить method_b внутри method_a? довольно ясно и не имеет ничего общего с закрытием, не так ли? Да, я согласен, я использовал can вместо Should. Но это не имеет отношения к закрытию ... Я просто удивлен, что так много ответов о закрытии и о том, как их использовать, когда OP задал совершенно другой вопрос. - person Mayou36; 27.07.2017
comment
@ Mayou36 Это может помочь перефразировать вопрос, как вы его видите, и открыть другой вопрос, чтобы ответить на него. - person CivFan; 27.07.2017

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

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

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

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

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

person CivFan    schedule 17.08.2017

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

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

person vz0    schedule 28.01.2011
comment
Это возможно, но я согласен, для этого нужна веская причина. Было бы правильнее использовать предыдущее подчеркивание для функции, которая предназначена для использования только в вашем классе. - person chmullig; 28.01.2011
comment
Да, внутри класса, но как насчет только внутри функции? Инкапсуляция иерархическая. - person Paul Draper; 13.04.2013
comment
@PaulDraper Инкапсуляция иерархическая - Нет! Кто так говорит? Инкапсуляция - это гораздо более широкий принцип, чем просто наследование. - person Mayou36; 26.07.2017

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

person miracle173    schedule 05.10.2014
comment
Сравнение, которое вы делаете, бесполезно. Это все равно, что добавить в функцию фиктивные операторы и сказать, что она работает медленнее. Пример Мартино фактически использует инкапсулированные функции, и я не замечаю никакой разницы в производительности, запустив его пример. - person kon psych; 19.02.2015
comment
-1 пожалуйста перечитайте вопрос. Хотя вы можете сказать, что это, возможно, немного медленнее, он спросил, стоит ли это делать, не в основном из-за производительности, а из общей практики. - person Mayou36; 26.07.2017

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

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

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

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

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

person Community    schedule 13.08.2016
comment
Как показывают другие ответы, на самом деле у вас нет проблем с производительностью. - person Mayou36; 26.07.2017

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

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

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

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

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

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

person chao787    schedule 18.12.2012

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

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

в противном случае some_data будет функцией.

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

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

person mikelikespie    schedule 28.01.2011

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

A) Использование функций без глобальных переменных

def calculate_salary(employee, list_with_all_employees):
    x = _calculate_tax(list_with_all_employees)

    # some other calculations done to x
    pass

    y = # something 

    return y

def _calculate_tax(list_with_all_employees):
    return 1.23456 # return something

Б) Использование функций с глобальными объектами

_list_with_all_employees = None

def calculate_salary(employee, list_with_all_employees):

    global _list_with_all_employees
    _list_with_all_employees = list_with_all_employees

    x = _calculate_tax()

    # some other calculations done to x
    pass

    y = # something

    return y

def _calculate_tax():
    return 1.23456 # return something based on the _list_with_all_employees var

C) Использование функций внутри другой функции

def calculate_salary(employee, list_with_all_employees):

    def _calculate_tax():
        return 1.23456 # return something based on the list_with_a--Lemployees var

    x = _calculate_tax()

    # some other calculations done to x
    pass
    y = # something 

    return y

Решение C) позволяет использовать переменные в области внешней функции без необходимости объявлять их во внутренней функции. Может быть полезно в некоторых ситуациях.

person Elmex80s    schedule 20.06.2018

Сделайте что-нибудь вроде:

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

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

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

person mdlp0716    schedule 30.05.2014
comment
Я ценю те усилия, которые вы приложили к своим ответам, но ваш ответ был прямым и точным. Хороший. - person C0NFUS3D; 05.07.2015
comment
Почему? Почему бы тебе этого не сделать? Разве это не отличная инкапсуляция? Я упускаю какие-либо аргументы. -1 - person Mayou36; 26.07.2017
comment
@ Mayou36 Я не знал, что такое инкапсуляция на момент написания моего комментария, и не знаю, что это такое сейчас. Я просто подумал, что это нехорошо. Можете ли вы объяснить, почему было бы полезно определить функцию внутри другой, а не просто определять ее вне ее? - person mdlp0716; 14.09.2017
comment
Да, я могу. Вы можете найти концепцию инкапсуляции, но вкратце: скрыть ненужную информацию и предоставить пользователю только то, что ему необходимо знать. Это означает, что определение some_other_function снаружи просто добавляет что-то еще в пространство имен, которое на самом деле тесно связано с первой функцией. Или подумайте о переменных: зачем вам локальные переменные и глобальные переменные? Если возможно, определение всех переменных внутри функции лучше, чем использование глобальных переменных для переменных, используемых только внутри этой одной функции. В конце концов, все дело в уменьшении сложности. - person Mayou36; 18.09.2017

Функция в функции python

def Greater(a,b):
    if a>b:
        return a
    return b

def Greater_new(a,b,c,d):
    return Greater(Greater(a,b),Greater(c,d))

print("Greater Number is :-",Greater_new(212,33,11,999))
person user12069064    schedule 15.09.2019