首页 > 编程语言 >Python 2-06 闭包

Python 2-06 闭包

时间:2023-05-22 11:03:48浏览次数:34  
标签:闭包 function 06 函数 Python msg print return def


闭包 Closures

嵌套函数(nested function),内函数引用了外函数的临时变量,并且外函数返回内函数的引用,这样就构成了一个闭包。

def outer():
    x, y, z = 10, 'abc', [1,2]
    
    def inner():
        print(x,y)
    return inner

f = outer()
print(f.__closure__)
# cell tuple 包含了内函数对外函数的变量 x, y 的引用,但不含 z。
# (<cell at 0x...: int object at 0x...>, <cell at 0x...: str object at 0x...>)

1、Nonlocal variable in a nested function

nested function: 嵌套函数 一个函数定义在另一个函数内部  
nested function 可以访问 enclosing scope(即包裹着它的更大的空间)中的变量,这些变量相对于它称之为 nonlocal variable。
Nonlocal variables 是只读的,如果想修改它们,必须使用 nonlocal (非局部的)关键字。
# This is the outer enclosing function (封闭函数)
def print_msg(msg):
	# This is the nested function
    def printer():
        print(msg)

    printer() 

print_msg("Hello") # We execute the function Output: Hello

We can see that the nested function printer() was able to access the non-local variable msg of the enclosing function.

2、Defining a Closure Function

在上面的例子中,如果外部函数 print_msg() 返回值是其内部函数 printer 呢?

def print_msg(msg):
    def printer():
        print(msg)

    return printer  # this got changed
# Now let's try calling this function.
# Output: Hello
another = print_msg("Hello")
another()

我们给 print_msg() 函数传入了参数 “Hello”,该函数返回了一个函数 printer,并赋值给了 another,那么 another 就成了一个函数,可以用 another() 来调用。
此时虽然 print_msg() 已经执行完毕,但再执行 another() 时,“Hello” 这条消息仍然有效,“printer” 这个内部***“变量”*** 也仍然有效,不会因为“离开”了其执行空间而被“销毁”导致无迹可寻。
这种技巧就叫做 Closure。

更进一步:

>>> del print_msg
>>> another()
Hello
>>> print_msg("Hello") # NameError: name 'print_msg' is not defined

即使 print_msg 这个函数体被垃圾回收了,another 以及刚才的内部变量 msg 仍然有效。

3、Closure 的条件

下面三个条件都必须满足,才构成一个 Closure:

  • 有一个嵌套函数(nested function)
  • nested function 必须引用 nonlocal 变量的值 (refer to a value defined in the enclosing
    function.)
  • The enclosing function must return the nested function. 返回嵌套函数中的内函数

4、Closure 的用处

  • Closure 可以减少 global value 的使用,并做到一定程度的 data hiding,提供一个 object
    oriented solution(面向对象的解决方案)
  • 当一个类只包含一个方法,使用 Closure 会更加 elegant(优雅的)。

例如:multiplier 乘法器

def make_multiplier_of(n):
    e = 2
    def multiplier(x):
        return e * x * n
    return multiplier

# Multiplier of 3
times3 = make_multiplier_of(3)

# Multiplier of 5
times5 = make_multiplier_of(5)

# Output: 54
print(times3(9))

# Output: 30
print(times5(3))

# Output: 120
print(times5(times3(2)))

所有的函数都包含一个 closure 属性,如果该函数是一个 normal function,则该属性为 None;如果该函数是一个 closure function,则该属性为 a tuple of cell objects。

>>> make_multiplier_of.__closure__
>>> times3.__closure__
(<cell at 0x0000000002D155B8: int object at 0x000000001E39B6E0>,)

引用了几个 nonlocal 变量就对应几个 cell

>>> times3.__closure__[0].cell_contents # 3
>>> times3.__closure__[1].cell_contents # 2

Python 闭包之经典的 for 循环问题

# 希望一次返回 3 个函数,分别计算 1x1,2x2,3x3
# 可以用列表接收这 3 个函数,然后返回列表。

def count():
    fs = []
    for i in range(1, 4):
        def f():
            return i*i
        fs.append(f)

    return fs

f1, f2, f3 = count() # 将 fs 中的三个 f 分别传递给 f1,f2,f3
print(f1(),f2(),f3())  # 9 9 9
print(f1.__closure__)

f1(), f2(), f3() 结果不应该是 1, 4, 9 吗,实际结果为什么都是 9 呢?

原因就是当 count() 函数返回了 3 个函数时,这 3 个函数所引用的变量 i 的值已经变成了 3。由于 f1、f2、f3 并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时,i 已经变为 3 了。

如何修改?

方法一
为变量 i 创建一个封闭的作用域,将 i 传递到封闭的作用域内,即将 i 传递给 j。

def count():
    fs = []
    for i in range(1, 4):
        def f(j):
            def g():
                return j*j
            return g
        r = f(i)
        fs.append(r)
    return fs

方法二
思路比较巧妙,用到了默认参数 j 在函数定义时可以获取到 i 的值,虽然没有用到闭包,但是和方法一有异曲同工之处。

def count():
    fs = []
    for i in range(1, 4):
        def f(j=i): # 已经不是闭包函数了
            return j*j
        fs.append(f)
    return fs


标签:闭包,function,06,函数,Python,msg,print,return,def
From: https://blog.51cto.com/u_1439909/6321634

相关文章

  • Python 1-24 练习五 综合练习
    1、无重复字符的最长子串给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。#substr向右扩展一个元素,如果该元素已经在substr中,则需要将其及其前面的元素去掉。#可通过substr.index(c)定位元素或substr.split(c)[1]分割成子串#发现有重复字符时,可......
  • Python 2-05 高阶函数
    一、函数式编程函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。而函数式编程(请注意多了一个“式”字)——FunctionalProgrammi......
  • Python 3-11 异常处理
    异常处理一、错误和异常1、句法错误句法错误又称解析错误:>>>whileTrueprint('Helloworld')File"<stdin>",line1whileTrueprint('Helloworld')^SyntaxError:invalidsyntax解析器会复现出现句法错误的代码行,并用小“箭头”指向行里检测到的第一......
  • Python 05 Selenium 等待
    等待WebDriver通常可以说有一个阻塞API。因为它是一个指示浏览器做什么的进程外库,而且web平台本质上是异步的,所以WebDriver不跟踪DOM的实时活动状态。大多数由于使用Selenium和WebDriver而产生的间歇性问题都与浏览器和用户指令之间的竞争条件有关。例如,用户指示浏览......
  • Python 1-11 练习一
    Python1-11练习一一、已知字符串s=“aAsmr3idd4bgs7Dlsf9eAF”,要求如下1、请将s字符串的大写改为小写,小写改为大写。#使用字符串的内置方法a.swapcase():s='aAsmr3idd4bgs7Dlsf9eAF't=s.swapcase()print(t)2、请将s字符串的数字取出,并输出成一个新的字符串。s=......
  • Python 1-10 字符串操作
    Python1-10字符串操作1、字符串拼接>>>s='hello'*2>>>s='hello'+'world'>>>s='hello''world'>>>......
  • Python 02 Xpath
    XpathXpath(XMLPathLanguage)是在XML文档中选择节点的语言一、XPath路径表达式1、XPath节点在XPath中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。XML文档是被作为节点树来对待的,树的根被称为文档节点或者根节点。2、XPath节点关系父(Pa......
  • Python 1-09 字符串
    Python1-09字符串一、Python字符串在Python3中,字符串是由Unicode码点组成的不可变序列。x="Python"y=x+"Cat"xisy#False字符串是用单引号"或双引号""括起来的一串字符,使用三引号创建多行字符串。在Python中单字符也是用字符串表示。>>>var1='HelloWor......
  • PCR406-ASEMI代理长电原厂单向可控硅PCR406
    编辑:llPCR406-ASEMI代理长电原厂单向可控硅PCR406型号:PCR406品牌:长电\CJ封装:SOT-23特性:可控硅正向电流:0.6A反向耐压:400V触发电压:0.62~0.8V引脚数量:3芯片个数:1芯片尺寸:浪涌电流:40A特点:单向可控硅工作温度:-40℃~125℃PCR406应用范围:万能开关器,继电器与灯控制,小型马......
  • python随机爬取五个电影演员一生生涯中出演的电影名称
    为了随机爬取电影演员的电影,需要使用Python中的网络爬虫技术和相关的第三方库,如requests和BeautifulSoup。以下是一个简单的示例程序:importrandomimportrequestsfrombs4importBeautifulSoup#输入要爬取的演员的姓名actor_name=input('请输入要爬取的演员的姓名:')#......