Ранее я спросил, как работают вложенные функции, но, к сожалению, я до сих пор не совсем понял. Чтобы понять это лучше, может кто-нибудь, пожалуйста, показать некоторые реальные примеры практического использования вложенных функций?
Большое спасибо
Ранее я спросил, как работают вложенные функции, но, к сожалению, я до сих пор не совсем понял. Чтобы понять это лучше, может кто-нибудь, пожалуйста, показать некоторые реальные примеры практического использования вложенных функций?
Большое спасибо
Ваш вопрос вызвал у меня любопытство, поэтому я посмотрел в каком-то реальном коде: стандартная библиотека 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
ожидает, что вы передадите функцию обработчика событий, поэтому мы определяем один и передаем его.
Decorators - очень популярное использование для вложенных функций. Вот пример декоратора, который печатает выражение до и после любого вызова декорированной функции.
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]
EDIT: На практике генераторы будут лучшим решением для этого, но пример показывает, как использовать вложенные функции.
Мне нужно было использовать только вложенные функции при создании декораторов. Вложенная функция в основном способ добавления некоторого поведения к функции, не зная, к какой функции добавляется поведение.
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)
Игнорируйте все mumbo jumbo в функции "printCall" прямо сейчас и сосредоточьте только функцию sayTHello и ниже. Что мы делаем здесь, мы хотим распечатать, как вызывалась функция sayHello каждый раз, когда она вызывается, не зная или не изменяя, что делает функция sayHello. Поэтому мы переопределяем функцию sayHello, передавая ее "printCall", которая возвращает NEW-функцию, которая выполняет функцию "sayHello" и печатает, как была вызвана функция sayHello. Это концепция декораторов.
Помещение "@printCall" над определением sayHello выполняет одно и то же:
@printCall
def sayHello(name):
print "Hello, my name is %s" % (name)
if __name__ == "__main__":
name = "Nimbuz"
sayHello(name)
Они полезны при использовании функций, которые принимают другие функции в качестве входных данных. Скажите, что у вас есть функция и вы хотите отсортировать список элементов на основе значения элементов в файле 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 локальную переменную.
Другой вариант использования - это обратные вызовы.
Еще один (очень простой) пример. Функция, которая возвращает другую функцию. Обратите внимание, как внутренняя функция (которая возвращается) может использовать переменные из области внешней функции.
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.
На самом деле это еще одна тема для изучения, но если вы посмотрите на статью "Использование функций в качестве декораторов", вы увидите несколько примеров вложенных функций.