1. 引例
这里有一个atm()函数,实现了存款和取款的功能,思考一下这样写有什么缺点:
account_amount = 0 # 用户余额
def atm(num, deposit=True):
global account_amount
if deposit:
account_amount += num
print(f"存款+{num},当前余额为:{account_amount}")
else:
if account_amount >= num:
account_amount -= num
print(f"取款-{num},当前余额为:{account_amount}")
else:
print("余额不足")
atm(100)
atm(100)
atm(200,False)
运行结果:
2. 闭包的概念
在引例中使用了一个全局变量account_amount来记录用户余额的值。在atm()函数中访问并修改了account_amount的值,从而实现余额的增减。这样处理会导致account_amount允许被其他对象所更改,甚至被其他文件import时也可以修改account_amount的值。这不是我们所期望的。于是引出了闭包的概念。
闭包是指在一个函数内部定义的函数,这个内部函数可以引用其外部函数中的变量,即使外部函数已经执行完毕并退出了。这种内部函数连同它引用的外部函数中的变量一起被称为闭包(closure)。
闭包的关键特性在于它能够记住并访问定义时的作用域中的变量,即使在闭包被调用时,那个作用域已经不存在了。
闭包的特性:
- 函数嵌套:闭包是在一个函数内部定义另一个函数。
- 自由变量:内部函数引用了外部函数的变量。
- 返回值是内部函数:外部函数返回内部函数,这个内部函数即为闭包。
以下是一个简单的闭包示例:
def outer(text):
def inner():
print(text)
return inner
# 创建闭包
closure = outer("Hello, World!")
# 调用闭包
closure() # 输出: Hello, World!
在这个示例中,text的作用域是整个外部函数outer()而不是全局,但是内部函数inner()也可以访问text的值。这样就避免了引例中全局变量被修改的风险。
(简单来说,示例中的account_amount和这里的text在功能上相同,但是作用域不同。使用闭包的方式,减小了变量的作用域,更加的安全)
3. 闭包的实际应用
闭包可以用来创建工厂函数、装饰器、回调函数等。下面是一个利用闭包创建简单计数器的例子:
def make_counter():
count = 0
def counter():
nonlocal count # 声明 count 是外部函数的变量
count += 1
return count
return counter
# 创建两个独立的计数器
counter1 = make_counter()
counter2 = make_counter()
print(counter1()) # 输出: 1
print(counter1()) # 输出: 2
print(counter2()) # 输出: 1
print(counter2()) # 输出: 2
print(counter1()) # 输出: 3
每个计数器函数都有自己独立的 count
变量,这就是闭包的强大之处。
可以注意到,这个例子中有一个nonlocal的关键字,简单来说就是允许内部函数(counter())修改外层的变量。确切地说nonlocal关键字是 Python 中用于在嵌套函数中引用外层(但不是全局作用域)变量的一种方式。它的作用是声明一个变量在外层函数中的作用域,并允许在内层函数中对其进行修改。nonlocal
关键字避免了局部变量的遮蔽问题,使得内层函数可以访问并修改外层函数的变量。
4. 使用闭包的atm()
我想,关于闭包是什么和如何使用,已经讨论清楚了,那么我们使用闭包的思想重写atm()函数。如下:
def account_create(account_amount=0):
def atm(num, deposit=True):
nonlocal account_amount
if deposit:
account_amount += num
print(f"存款+{num},当前余额为:{account_amount}")
else:
if account_amount >= num:
account_amount -= num
print(f"取款-{num},当前余额为:{account_amount}")
else:
print("余额不不足")
return atm
atm = account_create()
atm(200)
atm(300)
atm(400, False)
运行结果: