Реальные примеры вложенных функций

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

Большое спасибо


person eozzy    schedule 06.01.2010    source источник
comment
Дело в том, что внутренняя функция сохраняет значения переменных в охватывающей области, которые вы не получаете?   -  person Skilldrick    schedule 07.01.2010


Ответы (8)


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

Одна очень простая причина для использования вложенной функции состоит в том, что определяемая вами функция не должна быть глобальной, потому что ее использует только включающая функция. Типичный пример из стандарта Python quopri.py библиотечный модуль:

def encode(input, output, quotetabs, header = 0):
    ...
    def write(s, output=output, lineEnd='\n'):
        # RFC 1521 requires that the line ending in a space or tab must have
        # that trailing character encoded.
        if s and s[-1:] in ' \t':
            output.write(s[:-1] + quote(s[-1]) + lineEnd)
        elif s == '.':
            output.write(quote(s) + lineEnd)
        else:
            output.write(s + lineEnd)

    ...  # 35 more lines of code that call write in several places

Здесь был некоторый общий код в функции encode, поэтому автор просто выделил его в функцию write.


Еще одно распространенное использование вложенных функций - re.sub. Вот код из json / encode.py стандартный библиотечный модуль:

def encode_basestring(s):
    """Return a JSON representation of a Python string

    """
    def replace(match):
        return ESCAPE_DCT[match.group(0)]
    return '"' + ESCAPE.sub(replace, s) + '"'

Здесь ESCAPE - регулярное выражение, а ESCAPE.sub(replace, s) находит все совпадения с ESCAPE в s и заменяет каждое на replace(match).


Фактически, любой API, например re.sub, который принимает функцию в качестве параметра, может привести к ситуациям, когда вложенные функции удобны. Например, в turtle.py есть какой-то глупый демонстрационный код, который делает это:

    def baba(xdummy, ydummy):
        clearscreen()
        bye()

    ...
    tri.write("  Click me!", font = ("Courier", 12, "bold") )
    tri.onclick(baba, 1)

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

person Jason Orendorff    schedule 07.01.2010
comment
это хорошие примеры, но все они могут быть написаны как глобальные функции без потери функциональности. вложенные функции - это больше, чем синтаксический сахар! вы должны привести несколько примеров, где они необходимы - person Claudiu; 05.06.2010
comment
Также стоит отметить, что вложенные функции увеличивают накладные расходы. Каждый раз, когда вызывается внешняя функция, Python создает новый экземпляр внутренней функции. Во многих случаях это не имеет значения, но иногда имеет значение. - person Mr Fooz; 13.05.2016

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

def entry_exit(f):
    def new_f(*args, **kwargs):
        print "Entering", f.__name__
        f(*args, **kwargs)
        print "Exited", f.__name__
    return new_f

@entry_exit
def func1():
    print "inside func1()"

@entry_exit
def func2():
    print "inside func2()"

func1()
func2()
print func1.__name__
person Mr Fooz    schedule 06.01.2010

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

Функцию, возвращающую числа Фибоначчи, можно определить следующим образом:

>>> def fib(n):
        def rec():
            return fib(n-1) + fib(n-2)

        if n == 0:
            return 0
        elif n == 1:
            return 1
        else:
            return rec()

>>> map(fib, range(10))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

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

person jbochi    schedule 06.01.2010
comment
Вероятно, стоит отметить, что каждый раз, когда вызывается fib, создается новый объект функции rec. В случаях очистки кода это часто может быть нежелательно. Для таких вещей, как декораторы, это в принципе необходимо. - person Mr Fooz; 07.01.2010

Они полезны при использовании функций, которые принимают на вход другие функции. Допустим, вы находитесь в функции и хотите отсортировать список элементов на основе значения элементов в dict:

def f(items):
    vals = {}
    for i in items: vals[i] = random.randint(0,100)
    def key(i): return vals[i] 
    items.sort(key=key)

Вы можете прямо здесь определить ключ и использовать в нем локальную переменную vals.

Другой вариант использования - обратные вызовы.

person Claudiu    schedule 07.01.2010

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

from functools import wraps
from types import InstanceType



def printCall(func):
   def getArgKwargStrings(*args, **kwargs):
      argsString = "".join(["%s, " % (arg) for arg in args])
      kwargsString = "".join(["%s=%s, " % (key, value) for key, value in kwargs.items()])
      if not len(kwargs):
         if len(argsString):
            argsString = argsString[:-2]
      else:
         kwargsString = kwargsString[:-2]
      return argsString, kwargsString

   @wraps(func)
   def wrapper(*args, **kwargs):
      ret = None
      if args and isinstance(args[0], InstanceType) and getattr(args[0], func.__name__, None):
         instance, args = args[0], args[1:]
         argsString, kwargsString = getArgKwargStrings(*args, **kwargs)
         ret = func(instance, *args, **kwargs)
         print "Called %s.%s(%s%s)" % (instance.__class__.__name__, func.__name__, argsString, kwargsString)
         print "Returned %s" % str(ret)
      else:
         argsString, kwargsString = getArgKwargStrings(*args, **kwargs)
         ret = func(*args, **kwargs)
         print "Called %s(%s%s)" % (func.__name__, argsString, kwargsString)
         print "Returned %s" % str(ret)
      return ret
   return wrapper


def sayHello(name):
   print "Hello, my name is %s" % (name)

if __name__ == "__main__":
   sayHelloAndPrintDebug = printCall(sayHello)
   name = "Nimbuz"
   sayHelloAndPrintDebug(name)

Прямо сейчас игнорируйте всю бессвязную болтовню в функции printCall и сосредоточьтесь только на функции sayHello и ниже. Здесь мы хотим распечатать, как вызывалась функция "sayHello" каждый раз, когда она вызывается, не зная и не изменяя, что делает функция "sayHello". Итак, мы переопределяем функцию "sayHello", передав ее в "printCall", которая возвращает НОВУЮ функцию, которая делает то же, что и функция "sayHello", И печатает, как была вызвана функция "sayHello". Это концепция декораторов.

Помещение "@printCall" над определением sayHello дает то же самое:

@printCall
def sayHello(name):
   print "Hello, my name is %s" % (name)

if __name__ == "__main__":
   name = "Nimbuz"
   sayHello(name)
person manifest    schedule 06.01.2010
comment
Я понял, что getArgKwargStrings также является вложенной функцией. Он вложен, потому что он нужен только из функции printCall и не требует доступа в противном случае. - person manifest; 07.01.2010
comment
Как г-н Фуз упомянул выше в другом примере, вложенная функция getArgKwargStrings внутри printCall будет определяться каждый раз, когда вызывается функция printCall, что может быть или не быть желательным. - person manifest; 07.01.2010

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

def create_adder(x):
   def _adder(y):
       return x + y
   return _adder

add2 = create_adder(2)
add100 = create_adder(100)

>>> add2(50)
52
>>> add100(50)
150
person codeape    schedule 07.01.2010

Декораторы Python

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

person Skilldrick    schedule 06.01.2010

Хорошо, кроме декораторов: скажем, у вас есть приложение, в котором вам нужно отсортировать список строк на основе подстрок, которые время от времени меняются. Теперь функция sorted принимает аргумент key=, который является функцией одного аргумента: элементов (в данном случае строк) для сортировки. Итак, как указать этой функции, по каким подстрокам нужно выполнить сортировку? Для этого идеально подходят закрывающая или вложенная функция:

def sort_key_factory(start, stop):
    def sort_key(string):
        return string[start: stop]
    return sort_key

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

person Don O'Donnell    schedule 07.01.2010