发布于:2021-01-19 16:43:18
0
282
0
让我们看看编写内部函数的三个常见原因。
注意:在Python中,函数是“一等公民”。这意味着它们与任何其他对象(整数、字符串、列表、模块等)相当。您可以动态地创建或销毁它们、将它们传递给其他函数、将它们作为值返回等等。
本教程使用Python 3.4.1版。
封装
您使用内部函数来保护它们不受函数外部发生的任何事情的影响,这意味着它们被隐藏在全局范围之外。
这里有一个简单的例子突出了这个概念:
def outer(num1):
def inner_increment(num1): # Hidden from outer code
return num1 + 1
num2 = inner_increment(num1)
print(num1, num2)
inner_increment(10)
# outer(10)
尝试调用inner_increment()
:
Traceback (most recent call last):
File "inner.py", line 7, ininner_increment()
NameError: name 'inner_increment' is not defined
现在注释掉inner_increment()
调用并取消外部函数调用的注释,outer(10)
,作为参数传入:
10 11
注意:请记住这只是一个示例。尽管这段代码确实达到了预期的效果,但是最好使用前导下划线将inner_increment()
变成顶级的“私有”函数:_inner_increment()。
下面的递归示例是嵌套函数的一个稍微好一点的用例:
def factorial(number):
# Error handling
if not isinstance(number, int):
raise TypeError("Sorry. 'number' must be an integer.")
if not number >= 0:
raise ValueError("Sorry. 'number' must be zero or positive.")
def inner_factorial(number):
if number <= 1:
return 1
return number*inner_factorial(number-1)
return inner_factorial(number)
# Call the outer function.
print(factorial(4))
也测试一下。使用此设计模式的一个主要优点是,通过在外部函数中执行所有参数检查,可以安全地跳过内部函数中的错误检查。
保持干燥
也许您有一个巨大的函数,它在许多地方执行相同的代码块。例如,您可能编写了一个处理文件的函数,并且希望接受打开的文件对象或文件名:
def process(file_name):
def do_stuff(file_process):
for line in file_process:
print(line)
if isinstance(file_name, str):
with open(file_name, 'r') as f:
do_stuff(f)
else:
do_stuff(file_name)
注意:同样,通常只将do_stuff()
设为私有顶级函数,但如果您希望将其作为内部函数隐藏,则可以。一个实际的例子怎么样?假设您想知道纽约市WiFi热点的数量。是的,这个城市有原始数据告诉我们。访问站点并下载CSV:
def process(file_name):
def do_stuff(file_process):
wifi_locations = {}
for line in file_process:
values = line.split(',')
# Build the dict and increment values.
wifi_locations[values[1]] = wifi_locations.get(values[1], 0) + 1
max_key = 0
for name, key in wifi_locations.items():
all_locations = sum(wifi_locations.values())
if key > max_key:
max_key = key
business = name
print(f'There are {all_locations} WiFi hotspots in NYC, '
f'and {business} has the most with {max_key}.')
if isinstance(file_name, str):
with open(file_name, 'r') as f:
do_stuff(f)
else:
do_stuff(file_name)
运行函数:
>>> process('NAME_OF_THE.csv')
There are 1251 WiFi hotspots in NYC, and Starbucks has the most with 212.
闭包和工厂函数
现在我们来讨论使用内部函数的最重要原因。到目前为止,我们看到的所有内部函数示例都是普通函数,只是碰巧嵌套在另一个函数中。换句话说,我们可以用另一种方式定义这些函数(如前所述)。没有具体的理由说明为什么需要嵌套它们。
但是当涉及到闭包时,情况并非如此:必须使用嵌套函数。
什么是闭包?
闭包只会使内部函数在调用时记住其环境的状态。初学者通常认为闭包是内部函数,但它实际上是由内部函数引起的。闭包“关闭”堆栈上的局部变量,在堆栈创建完成后,这个问题仍然存在。
一个例子
这里有一个例子:
def generate_power(number):
"""
Examples of use:
>>> raise_two = generate_power(2)
>>> raise_three = generate_power(3)
>>> print(raise_two(7))
128
>>> print(raise_three(5))
243
"""
# Define the inner function ...
def nth_power(power):
return number ** power
# ... that is returned by the factory function.
return nth_power
示例中发生了什么
让我们看看这个例子中发生了什么:
generate_power()是工厂函数,仅表示每次调用它都会创建一个新函数,然后返回新创建的函数。因此,raise_two()和raise_three()是新创建的功能。
这个新的内部函数有什么作用?它只接受一个参数power,然后返回number**power。
内部函数从哪里获得价值number?这就是闭包起作用的地方:从外部函数(工厂函数)nth_power()获取值power。让我们逐步完成此过程:
调用外部函数:generate_power(2)。
Build nth_power(),它接受一个参数power。
拍摄的状态快照nth_power(),其中包括number=2。
将该快照传递到中generate_power()。
返回nth_power()。
换句话说,闭包“初始化”其中的数字栏nth_power(),然后将其返回。现在,无论何时调用该新返回的函数,它都将始终看到其自己的私有快照,其中包括number=2。
结论
闭包和工厂函数的使用是内部函数的最常见和最强大的用法。在大多数情况下,当您看到修饰的函数时,修饰器是一个工厂函数,它将一个函数作为参数并返回一个新函数,该新函数在闭包内部包括旧函数。停止。深吸一口气。喝杯咖啡。再读一遍。
换句话说,装饰器只是用于实现generate_power()示例中概述的过程的语法糖。
我给您留下最后一个示例:
def generate_power(exponent):
def decorator(f):
def inner(*args):
result = f(*args)
return exponent**result
return inner
return decorator
@generate_power(2)
def raise_two(n):
return n
print(raise_two(7))
@generate_power(3)
def raise_three(n):
return n
print(raise_two(5))
如果您的代码编辑器允许,并排查看generate_power(exponent)
和generate_power(number)
以说明所讨论的概念。(例如,Sublime Text具有列视图。)
如果尚未对这两个函数进行编码,请打开“代码编辑器”并开始编码。对于新程序员来说,编码是一项实践活动:就像骑自行车一样,你只需要自己动手,自己动手。所以回到手头的任务!
键入代码后,您现在可以清楚地看到,它的相似之处在于产生相同的结果,但也存在差异。对于那些从未使用过decorators的人来说,如果你冒险沿着这条路走下去,注意这些差异将是理解它们的第一步。
作者介绍