14.闭包

发布时间 2023-04-08 20:42:51作者: 这人穷的很

闭包

引入

想想看怎样用程序实现下面的功能呢?

在一个聊天软件中显示是谁发送了这条信息,即:一条信息标记了是谁发送的

今天我们要研究的知识点是闭包,实现上述功能的方式可能有多种,但是闭包会更简单。

问题解决

普通方式

def say(user_name, content):
    print("(%s):%s" % (user_name, content))


user_name1 = "安娜"
user_name2 = "双双"

say(user_name1, "今天吃了么?")
say(user_name2, "吃了~")

say(user_name1, "吃了啥?")
say(user_name2, "半只牛~")

say(user_name1, "为啥不给我吃?")
say(user_name2, "我一个人刚刚好~~")

say(user_name1, "友谊的小船说翻就翻!")
say(user_name2, "我会游泳~~~")

运行效果如下:

(安娜):今天吃了么?
(双双):吃了~
(安娜):吃了啥?
(双双):半只牛~
(安娜):为啥不给我吃?
(双双):我一个人刚刚好~~
(安娜):友谊的小船说翻就翻!
(双双):我会游泳~~~

小总结:

  • 上述代码已经实现了要求,但是每次发送信息时需要将用户名称传递到say函数中,相对比较麻烦

面向对象的方式解决上述问题

class Person(object):
    def __init__(self, name):
        self.user_name = name

    def say(self, content):
        print("(%s):%s" % (self.user_name, content))


p1 = Person("安娜")
p2 = Person("双双")


p1.say("今天吃了么?")
p2.say("吃了~")
p1.say("吃了啥?")
p2.say("半只牛~")
p1.say("为啥不给我吃?")
p2.say("我一个人刚刚好~~")
p1.say("友谊的小船说翻就翻!")
p2.say("我会游泳~~~")

运行结果:

(安娜):今天吃了么?
(双双):吃了~
(安娜):吃了啥?
(双双):半只牛~
(安娜):为啥不给我吃?
(双双):我一个人刚刚好~~
(安娜):友谊的小船说翻就翻!
(双双):我会游泳~~~

小总结:

  • 通过面向对象的方式能够实现上述要求,但是发现使用了类以及对象,总体感觉还是较为复杂,再者说继承的object类中有很多默认的方法,既然这个程序不需要,显然会造成一定的浪费

使用闭包解决上述问题

def person(name):
    def say(content):
        print("(%s):%s" % (name, content))

    return say

p1 = person("安娜")
p2 = person("双双")

p1("今天吃了么?")
p2("吃了~")
p1("吃了啥?")
p2("半只牛~")
p1("为啥不给我吃?")
p2("我一个人刚刚好~~")
p1("友谊的小船说翻就翻!")
p2("我会游泳~~~")
函数引用
# 定义函数可以理解为:
# 定义了一个全局变量,其变量名字是函数的名字,即test
# 这个test变量指向了一个代码块,这个代码块是函数
# 其实就是说test保存了一个代码块的地址,即引用
def test():
    print("--- in test func----")

test()  # 这是调用函数

ret = test # 用另外一个变量 复制了 test这个引用,导致ret变量也指向那个 函数代码块

# 下面输出的2个地址信息是相同的
print(id(ret))
print(id(test))

# 通过引用调用函数
ret()

运行结果:

--- in test func----
140212571149040
140212571149040
--- in test func----
闭包的概念

闭包(closure) 定义非常抽象,很难看懂

下面尝试从概念上去理解一下闭包:

  • 在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。 —— 维基百科https://zh.wikipedia.org/wiki/闭包_(计算机科学)

用比较容易懂的人话说:就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。可以这样理解,闭包就是能够读取其他函数内部变量的函数

看如下案例,便于理解什么是闭包:

def make_printer(msg):  # 可以认为是 外部函数
    def printer():  # 可以认为是 内部函数
        print(msg)
    return printer  # 返回的内部函数的引用

printer = make_printer('Good!')
printer()

运行结果:

Good
闭包案例

代码示例:

def test(number):
    def test_in(number_in):
        print("in test_in 函数, number_in is %d" % number_in)
        return number + number_in
    return test_in


# 给test函数赋值,这个20就是给参数number
ret = test(20)

# 注意这里的100其实给参数number_in
print(ret(100))

# 注意这里的200其实给参数number_in
print(ret(200))

运行结果:

in test_in 函数, number_in is 100
120
in test_in 函数, number_in is 200
220
使用闭包需要注意的问题

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。因此可以手动解除对匿名函数的引用,以便释放内存。

使用闭包修改外部函数中的变量
def counter(start=0):
    def add_one():
        nonlocal start  # nonlocal 关键字用于在嵌套函数内部使用变量,其中变量不应属于内部函数。
        start += 1
        return start
    return add_one

c1 = counter(5)  # 创建一个闭包
print(c1())
print(c1())

c2 = counter(50)  # 创建另外一个闭包
print(c2())
print(c2())

print(c1())
print(c1())

print(c2())
print(c2())

运行结果:

6
7
51
52
8
9
53
54
多个闭包

如上面的代码中,调用了2次counter,也就意味着创建了2个闭包,并且每个闭包之间没有任何关系。

大家是否有种感觉,好像闭包与对象有些类似。确实是这样的,对象其实可通俗的理解为数据(属性) + 功能(方法),而闭包也可以理解为数据 + 功能,只不过此时数据是外部函数中的那些局部变量或者形参,而功能则是内部函数。对象适合完成较为复杂的功能,而闭包则更轻量

闭包总结
  1. 闭包定义是在函数内再嵌套函数
  2. 闭包是可以访问另一个函数局部作用域中变量的函数
  3. 闭包可以读取另外一个函数内部的变量
  4. 闭包可以让参数和变量不会被垃圾回收机制回收,始终保持在内存中(而普通的函数调用结束后 会被Python解释器自动释放局部变量)