【发布时间】:2011-01-02 07:00:23
【问题描述】:
我之前问过nested functions 是如何工作的,但不幸的是我还是不太明白。为了更好地理解它,有人可以展示一些嵌套函数的真实、实际使用示例吗?
非常感谢
【问题讨论】:
-
是不是内部函数保留了你没有得到的封闭范围内的变量值?
我之前问过nested functions 是如何工作的,但不幸的是我还是不太明白。为了更好地理解它,有人可以展示一些嵌套函数的真实、实际使用示例吗?
非常感谢
【问题讨论】:
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 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
【讨论】:
你的问题让我很好奇,所以我查看了一些真实世界的代码: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)在s中查找ESCAPE的所有匹配项,并将每一项替换为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 期望你传递一个事件处理函数,所以我们定义一个并传递它。
【讨论】:
好的,除了装饰器:假设您有一个应用程序,您需要根据不时变化的子字符串对字符串列表进行排序。现在sorted 函数采用key= 参数,它是一个参数的函数:要排序的项目(在本例中为字符串)。那么如何告诉这个函数对哪些子字符串进行排序呢?闭包或嵌套函数非常适合:
def sort_key_factory(start, stop):
def sort_key(string):
return string[start: stop]
return sort_key
简单吧?您可以通过将 start 和 stop 封装在一个元组或一个 slice 对象中,然后将这些序列或可迭代对象传递给 sort_key_factory 来对此进行扩展。
【讨论】:
在使用将其他函数作为输入的函数时,它们很有用。假设您在一个函数中,并且想要根据项目在 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)
您可以在此处定义 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 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]
编辑:实际上,生成器会是一个更好的解决方案,但该示例显示了如何利用嵌套函数。
【讨论】:
这实际上是另一个需要学习的主题,但是如果您查看“将函数用作装饰器”中的内容,您会看到一些嵌套函数的示例。
【讨论】: