下载APP
登录
关闭
讲堂
算法训练营
Python 进阶训练营
企业服务
极客商城
客户端下载
兑换中心
渠道合作
推荐作者
当前播放: 28 | 闭包的定义
00:00 / 00:00
标清
  • 标清
1.0x
  • 2.0x
  • 1.5x
  • 1.25x
  • 1.0x
  • 0.5x
网页全屏
全屏
00:00
付费课程,可试看

零基础学Python

共71讲 · 71课时·约540分钟
16024
免费
01 | Python语言的特点
免费
02 | Python的发展历史与版本
免费
03 | Python的安装
免费
04 | Python程序的书写规则
免费
05 | 基础数据类型
免费
06 | 变量的定义和常用操作
07 | 序列的概念
08 | 字符串的定义和使用
09 | 字符串的常用操作
10 | 元组的定义和常用操作
11 | 列表的定义和常用操作
12 | 条件语句
13 | for循环
14 | while循环
15 | for循环语句中的if嵌套
16 | while循环语句中的if嵌套
17 | 字典的定义和常用操作
18 | 列表推导式与字典推导式
19 | 文件的内建函数
20 | 文件的常用操作
21 | 异常的检测和处理
22 | 函数的定义和常用操作
23 | 函数的可变长参数
24 | 函数的变量作用域
25 | 函数的迭代器与生成器
26 | Lambda表达式
27 | Python内建函数
28 | 闭包的定义
29 | 闭包的使用
30 | 装饰器的定义
31 | 装饰器的使用
32 | 自定义上下文管理器
33 | 模块的定义
34 | PEP8编码规范
35 | 类与实例
36 | 如何增加类的属性和方法
37 | 类的继承
38 | 类的使用-自定义with语句
39 | 多线程编程的定义
40 | 经典的生产者和消费者问题
41 | Python标准库的定义
42 | 正则表达式库re
43 | 正则表达式的元字符
44 | 正则表达式分组功能实例
45 | 正则表达式库函数match与s...
46 | 正则表达式库替换函数sub...
47 | 日期与时间函数库
48 | 数学相关库
49 | 使用命令行对文件和文件夹...
50 | 文件与目录操作库
51 | 机器学习的一般流程与NumP...
52 | NumPy的数组与数据类型
53 | NumPy数组和标量的计算
54 | NumPy数组的索引和切片
55 | pandas安装与Series结构
56 | Series的基本操作
57 | Dataframe的基本操作
58 | 层次化索引
59 | Matplotlib的安装与绘图
60 | 机器学习分类的原理
61 | Tensorflow的安装
62 | 根据特征值分类的模型和代...
63 | 网页数据的采集与urllib库
64 | 网页常见的两种请求方式ge...
65 | HTTP头部信息的模拟
66 | requests库的基本使用
67 | 结合正则表达式爬取图片链...
68 | Beautiful Soup的安装和...
69 | 使用爬虫爬取新闻网站
70 | 使用爬虫爬取图片链接并下...
71 | 如何分析源代码并设计合理...
本节摘要

课程源码、课件及课后作业地址:

https://github.com/wilsonyin123/geekbangpython

精选留言(10)

  • 2018-06-19
    闭包计数器为什么要用列表? 用整数变量为啥传不进嵌套函数呢?
    好像定义整数变量后,只要在嵌套函数里定义一下noblocal就可以了,这又是为啥……

    作者回复: 这个问题非常好!首先我们先从nonlocal说起,python在使用变量的时候要遵循一个LEGB规则,
    什么又是LEGB呢,就是如果你使用了一个函数内的变量做运算,python会从函数里面找这个变量的定义,如果找不到函数里面的定义就会报错了--即你看到的未定义先使用的错误了。
    这种实现方式比js要好,避免在你忘记声明局部变量的时候,误使用了全局变量。
    那变量都有几个作用域(影响范围)呢?一共是四个,分别是局部(local)-闭包(enclosing)-全局(global)-模块(builtin),LEGB就是取的他们的首字母;
    如果要引用的变量的定义没在内部函数里面,而是在闭包里面就可以通过nonlocal声明一下,python就会从外层函数里面找这个变量的定义了,如果使用了global关键字修饰内部函数的变量,运算时就会从全局变量里面找变量的定义了。

    上面说的是变量作用域,再来说下我为什么使用列表,这里使用列表的目的就是为了达到 nonlocal变量的功能,因为对列表的操作是直接操作内存的位置,对变量的操作是重新分配了一块新的内存;
    所以在内部函数直接使用列表的名称也是操作外部函数定义的列表。这里需要通过python底层执行过程观察:
    from dis import dis
    # dis模块可以反汇编python函数的字节码
    def counter2(first2=0):
        CNT2 = first2

        def add_one():
            # nonlocal CNT2
            CNT2 += 1
            return CNT2
        return add_one

    dis(counter2(3))


    如果将nonlocal注释掉会显示反汇编结果为
    LOAD_FAST 0 (CNT2)
    如果不注释nonlocal或使用列表会显示反汇编结果为
    LOAD_DEREF 0 (CNT2)

    LOAD_FAST 的含义是加载了本地变量
    LOAD_DEREF 的含义是加载了引用的变量
    LOAD_GLOBAL 的含义是加载了全局变量

    这里涉及到python作用域和底层的一些原理,希望你能通过尝试更多的示例进行学习总结

    11
  • 2018-07-02
    老师,我觉得你说的有些问题,如果闭包里面引用的变量的定义没有在闭包中,那么他也会去外层函数中去查找,直到查找到全局作用域,代码如下

        cnt2 = 1
        def counter():
            def add_one():
                a = cnt2 + 1
                print(a)
                return a
            return add_one
        
        add_one = counter()
        add_one()
        #output 2

    上述代码中输入2,说明在闭包中他是可以访问了全局的变量的,即使不用加nonlocal, 那么关于这段代码如下

        def counter():
            count = 1
            def add_one():
                count += 1 # error
                return count
            return add_one

    那么上述代码报错原因,我也上网搜了一下,一个我可以理解的解释是,在闭包中count += 1,实际上是重新声明了一个count变量,覆盖了外层函数的count变量,那么此时这个count变量也没有被赋值,却参与到了运算当中,那么就会报错了,我也用老师说的反汇编测试过

    老师,我这样理解的不知道对不对,请老师指点下,我用的python版本是3.7的
    展开

    作者回复: 很细心,第一个问题我在解释的时候不严谨,如果函数内只引用全局变量,但不修改时可以不使用global关键字,一旦对全局变量进行修改必须声明,如:在函数内使用cnt2 += 1 就会报错了,这种情况下强制使用 global cnt2 显式声明才可以正常使用
    def add_one():
        global cnt2 # 声明
        cnt2 += 1 # 对全局变量做修改
        a = cnt2 + 1
        print(a)
        return a

    第二个问题的理解是对的,能够正确理解变量作用域对工作中有非常大的帮助,继续加油!

    3
  • 2019-08-27
    为什么留言前两条在说一些看起来跟本课无关的术语,这些同学是学了其他语言过来的么- -他们提的问题我没想到也看不懂
    1
  • 2019-10-04
    cnt[0] += 1;列表可以和 int 类型做加法运算吗??

    作者回复: 要看 cnt[0] 是什么类型

  • 2019-09-02
    def counter():
        cnt=[0]
        def add_one():
            cnt[0]+=1
            return cnt[0]
        return add_one
    # counter()
    num1=counter()
    print(counter()())
    print(counter()())

    print(num1())
    print(num1())

    为什么num1()可以累加而counter()()的结果一直是1?
    展开

    作者回复: print(counter()()) 形成了匿名函数的调用,因为没有引用,执行完成被python GC机制回收,所以没有+1

  • 2019-04-19
    老师您好,我对def counter(FIRST = 0):
                            cnt = [FIRST]
    这一部分有点小疑惑,为什么传参FIRST = 0之后,如果counter()函数有传值就使用FIRST值,没有传值就默认为0呢?
    这个FIRST = 0, 不是指将0赋值给FIRST,从此之后FIRST都等于0吗?

    烦请老师解答,谢谢。
    展开

    作者回复: 这里涉及函数执行的顺序问题,使用FIRST = 0作为参数,在定义函数时FIRST就得到了 0, 如果没有传值,那么FIRST就取得0了, 如果给函数传值,FIRST就会取得传递的值,覆盖掉0,FIRST就是新的值了

  • 2019-03-15
    老师,Python 是不可以返回表达式吗?下面这段代码是正确的,可以运行,但我改写一下就报错了

    # 正确的代码
    def counter():
        l = [0]

        def add_one():
            l[0] += 1
            return l[0]

        return add_one

    改写为如下:

    # 错误的代码

    def counter():
        l = [0]

        def add_one():
            return l[0] += 1

        return add_one

    报错如下:

      File "/Users/lxc/PycharmProjects/test/28.py", line 5
        return l[0] += 1
                     ^
    SyntaxError: invalid syntax
    展开

    作者回复: 函数的return 语句只能返回一个值,这个值可以是任意的类型,如果有需要返回多个值可以通过把他们组合成列表、元组、字典等方式返回

  • 2018-11-17
    老师,您好。可以这样理解吗:
    num1 = counter()
    这条语句只是初始化了外部函数的列表及返回内部函数的引用,此时内部函数并没有执行
    之后num1()
    这条语句直接执行内部函数的语句并返回最终的结果值
    如果在外部函数中使用整型变量,会随着外部函数的调用结束整型变量的内存会被释放,从而造成内部变量无法引用其值;如果使用列表,则内存不会随着外部函数的调用结束而释放,内部函数可以继续引用其值。那么有个疑问:列表的内存是何时被释放的?整个程序执行结束吗?
    展开

    作者回复: 我简单说下,python如何释放内存呢?它使用了一个引用计数器的概念,引用不为0内存就不会释放。怎么监测到有多少个引用呢?
    from sys import getrefcount
    getrefcount(CNT)
    getrefcount(counter)

    自己尝试一下看能得到你想要的答案吗?

  • 2018-09-27
    看了留言的解释和网上搜索到情况,整理一份理解:

    1. list (因为是内建函数,所以他)的作用域默认是全局。无论哪里进行了操作、处理,都是本身数据变更了。
    2-1. 整型等数据,需要先定义,然后才能使用;且默认的作用域是当前的局部。
    2-2. 如果想用整型进行“计数器”的实现,需要声明成 global 类型的变量。而且需要在函数外面就定义出来,不然就无法重置。
    (尝试后成功的 code 如下:)

    aCounter = 0
    def counter():
    ---def add_one():
    ------global aCounter
    ------aCounter += 1
    ------return aCounter
    ---return add_one
    展开

    作者回复: 正确,另外书写规范建议用4个空格来缩进代码(手机手打一样无奈中)

  • 2018-07-21
    闭包最初理解起来有点困难,特别是对写多了OOP代码的人。因为无法理解为啥要这么绕的去使用一个局部变量。
    但是,一旦理解了“作用域”这个概念。
    就能明白,无论是在面向过程开发中使用闭包,还是在面向对象编程中使用类。
    都是为了在某一作用域下共享资源。

    作者回复: 对的,因为面对初学者,我没有太理论化的解释作用域的问题。更多关于作用域的解释可以参考搜索引擎和我的其他留言回复。