Ранее я спрашивал, как работают вложенные функции, но, к сожалению, я все еще не совсем понимаю. Чтобы лучше понять, может ли кто-нибудь показать несколько реальных практических примеров использования вложенных функций?
Большое спасибо
Ранее я спрашивал, как работают вложенные функции, но, к сожалению, я все еще не совсем понимаю. Чтобы лучше понять, может ли кто-нибудь показать несколько реальных практических примеров использования вложенных функций?
Большое спасибо
Ваш вопрос вызвал у меня любопытство, поэтому я заглянул в некоторый реальный код: стандартную библиотеку 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 ожидает, что вы передадите функцию-обработчик событий, поэтому мы определяем ее и передаем.
Декораторы очень популярны для вложенных функций. Вот пример декоратора, который печатает инструкцию до и после любого вызова декорированной функции.
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__
Вложенные функции избегают загромождения других частей программы другими функциями и переменными, которые имеют смысл только локально.
Функцию, возвращающую числа Фибоначчи, можно определить следующим образом:
>>> 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]
РЕДАКТИРОВАТЬ: На практике генераторы были бы лучшим решением для этого, но в примере показано, как использовать вложенные функции.
Они полезны при использовании функций, которые принимают на вход другие функции. Допустим, вы находитесь в функции и хотите отсортировать список элементов на основе значения элементов в 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.
Другой вариант использования - обратные вызовы.
При создании декораторов мне пришлось использовать только вложенные функции. Вложенная функция - это, по сути, способ добавления некоторого поведения к функции, не зная, к какой функции вы добавляете поведение.
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)
Еще один (очень простой) пример. Функция, возвращающая другую функцию. Обратите внимание, как внутренняя функция (которая возвращается) может использовать переменные из области внешней функции.
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
На самом деле это еще одна тема для изучения, но если вы посмотрите материал «Использование функций в качестве декораторов», вы увидите несколько примеров вложенных функций.
Хорошо, кроме декораторов: скажем, у вас есть приложение, в котором вам нужно отсортировать список строк на основе подстрок, которые время от времени меняются. Теперь функция sorted принимает аргумент key=, который является функцией одного аргумента: элементов (в данном случае строк) для сортировки. Итак, как указать этой функции, по каким подстрокам нужно выполнить сортировку? Для этого идеально подходят закрывающая или вложенная функция:
def sort_key_factory(start, stop):
def sort_key(string):
return string[start: stop]
return sort_key
Просто а? Вы можете расширить это, инкапсулируя начало и конец в кортеж или объект среза, а затем передав их последовательность или итерацию в sort_key_factory.