Вкладена функція в Python


83

Яку користь чи наслідки ми могли б отримати з кодом 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/…
Aly,

2
Ваше посилання (яке вказує на HEAD) тепер не є точним. Спробуйте: bazaar.launchpad.net/~openerp/openobject-server/trunk/annotate/…
Крейг

Ви маєте рацію Крейг. Дякую.
Хосам Алі

Відповіді:


111

Зазвичай ви робите це, щоб зробити закриття :

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). Кращим прикладом для закриття є декоратори.
Jochen Ritzel

4
Дякую, але, як ви можете бачити у фрагменті, який я опублікував, тут справа не в цьому: вкладена функція просто викликається у зовнішній функції.
Хосам Алі,

57

Окрім генераторів функцій, де створення внутрішніх функцій є майже визначенням генератора функцій, я створюю вкладені функції для покращення читабельності. Якщо у мене є крихітна функція, яка буде викликана лише зовнішньою функцією, тоді я вбудовую визначення, щоб вам не потрібно було пропускати, щоб визначити, що ця функція робить. Я завжди можу перемістити внутрішній метод за межі методу інкапсуляції, якщо виявлю необхідність повторного використання функції пізніше.

Приклад іграшки:

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
Єдина проблема полягає в тому, що це іноді може ускладнити модульний тест, оскільки ви не можете отримати доступ до внутрішньої функції.
Челленджер, 5

1
Вони виглядають так мило!
звуження

FWIY @ Challenger5, якщо внутрішня функція аналогічна функціям приватного класу, вони все одно не перевіряються. Я почав робити це, щоб зробити методи більш читабельними, зберігаючи визначення близько до розглянутого методу.
Мітчелл Каррі

26

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

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

Я не можу уявити жодної вагомої причини для такого коду.

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

Наприклад, це має трохи більше сенсу:

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 взяв рядок, щоб вказати, якого оператора використовувати для аргументів ...
Skilldrick

1
@ArtOfWarfare бажано просто використовувати модулі. Заняття державні.
aehlke

6

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


1

Ви впевнені, що код був саме таким? Звичайна причина робити щось подібне полягає у створенні часткової - функції із запеченими параметрами. Виклик зовнішньої функції повертає виклик, який не потребує параметрів, і тому його можна зберігати та використовувати десь неможливо передавати параметри. Однак код, який ви опублікували, цього не зробить - він негайно викликає функцію і повертає результат, а не викличний. Може бути корисно опублікувати фактичний код, який ви бачили.


1
Я додав посилання на вихідний код у коментарі до свого запитання. Як бачите, мій - спрощений приклад, але все ще майже той самий.
Хосам Алі,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.