29期第五周笔记

Week 5

本周学习主要内容包括函数作用域,闭包,匿名函数,递归函数,生成器函数等

函数返回值(return)

  • 用return语句返回“返回值”
  • 所有函数都有返回值,没有return语句则为隐式调用return None
  • return语句并不一定是函数的语句块的最后一条语句
  • 一个函数可以有多个return语句,但只有一条可以被执行;如果没有一条被执行,隐式调用return None
  • 如果有必要可以显式调用return None,简写为return
  • 如果函数执行了return语句,函数就会返回,当前执行的return语句后的其他语句则不会执行
  • 返回值的作用:结束函数调用,返回“返回值”

函数不能同时返回多个值
多个数值会被python隐式封装成元组,仍然只返回一个值
可以使用解构提取返回值 x,y,z = showlist()

函数作用域

  • 作用域:一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域。
  • 函数作用域:函数是一个封装,他会开辟一个作用域,x变量被限制在这个作用域中,所以函数外部x变量不可见!
  • 每个函数都会开辟一个作用域

作用域分类

  1. 全局作用域:(global)
  • 在整个程序运行环境中都可见
  • 全局作用域中的变量称为全局变量
  1. 局部作用域:(local)
  • 在函数、类等内部可见,也叫本地作用域(local)
  • 局部作用域中的变量为局部变量,使用范围不能超过其所在局部作用域

一般来讲,外部作用域变量在函数内部可见;
反之,函数内部的局部变量,函数外部不可见。

函数嵌套:

在一个函数中定义另一个函数时,内部函数不能在外部直接使用,会有NameError异常,因为他在函数外部不可见。内部函数在此处相当于一个标识符,是外部函数内部定义的变量而已。

嵌套结构的作用域:
  • 外层变量在内部作用域可见
  • 内层作用域中如果定义了一个变量值(o = 97),相当于在当前内层函数中重新定义了一个新变量,但这个新变量不能覆盖外部作用域中的变量o;但对内层函数来说只能可见自己作用域中定义的变量o了。
赋值语句的问题:
x = 300
def foo():
    x+=1 #赋值语句
    print(x)

x
>>> 300

foo() # 未赋值就调用
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-51-c19b6d9633cf> in <module>
----> 1 foo()

<ipython-input-49-78729a9a61fb> in foo()
      1 x = 300
      2 def foo():
----> 3     x+=1
      4     print(x)

UnboundLocalError: local variable 'x' referenced before assignment

原因:

  • x += 1 ==> x = x+1
  • 只要在函数中出现x=… 这样的变量赋值语句,且此变量不加任何语句修饰,那么此变量一定是当前函数的局部变量,在此函数中所有x都是用该x。
  • 赋值即定义!!
  • 因此此处x += 1相当于使用了局部变量x,但这个x还没有完成赋值就被进行+1操作了,因此会产生UnboundLocalError。

global语句

x = 600 #全局变量
def foo():
    global x #这个标识符被修饰了,他还是本地变量吗?--不是,被global声明成了全局变量
    x += 1 #x = x+1 为x赋值,x = x就是本地变量,但如果对x使用了global
    print(x)
  • 使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x
  • 全局作用域中必须有x的定义
  • 即使在foo中又写了x = 10,也不会在foo这个局部作用域中定义局部变量x
  • 使用了global,foo中x就不是局部变量了,而是全局变量
  • 尽量不使用global,如果函数需要使用外部全局变量,尽量使用函数的形参定义,通过调用传实参解决

闭包

  • 自由变量:未在本地作用域中定义的变量(比如定义在内层函数外的外层函数作用域中的变量)
  • 闭包:在嵌套函数中,指***内层函数引用到了外层函数的自由变量***,即形成闭包现象
def counter():
    c = [0]
    def inc():
        c[0] += 1 #报错吗?为什么?
        return c[0]
    return inc

#报错吗?
#有闭包吗?
foo = counter() #foo就是inc,inc引用地址交给了foo,
#但counter已经调用结束,按理说内部局部变量要消亡
foo()
  • counter内部产生两个局部变量c和inc
  • foo获得了counter()执行的结果,inc指向的引用地址
  • counter() 函数执行完,局部变量inc和c标识符都消亡了,(但c和inc指向的对象并没有消亡!)
  • foo():由于外层函数已经执行结束,但内层函数对象并没有消亡,不知道什么时候调用;但内层函数用到了外层函数的自由变量c
  • foo指向的函数对象要使用counter中的c,c指向的列表[0]不消亡,由这个不消亡的内存函数对象来保存这个列表,这就是闭包。

non-local语句:

nonlocal:将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中定义,但不能是全局作用域中定义

def counter(): #python 3中比较简单实现闭包的方式
    c = 0
    def inc(): 
        nonlocal c #non-local 不是我本地的,但是外部的,是我当前函数外层函数中的某一层上的c变量
        #但绝不能是全局的
        c+=1 
        return c
    return inc
def func():
    nonlocal d #此处向外找d,d就是全局了
    d = 100
--------------------------------------------------------------------
      File "<ipython-input-127-d3851b2f7792>", line 2
    nonlocal d
    ^
SyntaxError: no binding for nonlocal 'd' found

默认值作用域

a = list(range(2))
print(id(a),a)
a += [10]
print(id(a),a) #地址没变
a = a +[20]
print(id(a),a) #地址变了
--------------------------------------------
1621904477576 [0, 1]
1621904477576 [0, 1, 10]
1621904446600 [0, 1, 10, 20]
a = tuple(range(2))
print(id(a),a)
a += (10,) #对于不可变类型元组来说,将 +=  --> = +
print(id(a),a) #地址变了
a = a + (20,)
print(id(a),a) #地址变了
--------------------------------------------
1621903123528 (0, 1)
1621904541000 (0, 1, 10)
1621904615368 (0, 1, 10, 20)

变量名解析原则LEGB

  • Local
  • Enclosing,嵌套函数的外部函数的命名空间
  • Global,全局作用域,即一个模块的命名空间,模块被import时创建,解释器退出时消亡
  • Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡

函数的销毁

  • 定义一个函数就是生成一个函数对象,函数名指向的就是函数对象
  • 可以用del语句删除,使其引用计数-1
  • 可以使用同名标识符覆盖原有定义,本质上也是使其引用计数-1
  • python程序结束时,所有对象销毁
  • 函数也是对象,是否销毁还是看引用计数是否减为0

匿名函数

  • 定义: 隐藏名字没有名称的函数

Lambda表达式

  • 使用lambda关键字定义匿名函数,格式为lambda[参数列表]: 表达式
  • 参数列表不需要小括号,无参就不写参数
  • 冒号用来分割参数列表和表达式部分
  • 不需要使用return,表达式的值,就是匿名函数的返回值,表达式中不能出现等号
  • lambda表达式(匿名函数)只能写在一行上,也叫单行函数

递归函数

函数执行流程

  • 函数活动和栈有关
  • 栈是后进先出的数据结构
  • 栈是从底向顶端生长,栈中插入数据称为压栈、入栈,从栈顶弹出数据称为出栈
  • 每个栈帧对应一个未运行完的函数,栈帧中保存了该函数的返回地址和局部变量
  • 函数每次调用都会创建一个独立的栈帧(Stack Frame)入栈
  • 哪怕是同一个函数两次调用,但每次调用都是独立的,这两次调用没什么关系不准确

递归(Recursion)

  • 函数直接或间接调用自身就是递归
  • 递归需要有边界条件,递归前进段,递归返回段
  • 递归一定要有边界条件
  • 边界条件不满足时递归前进
  • 边界条件满足时递归返回

递归一定要有退出条件,递归调用一定要执行到这个退出条件,没有退出条件的递归调用,就是无限调用
递归调用的深度不宜过深
Python对递归的深度作出限制以保护解释器:
#RecursionError递归异常,maximum recursion depth exceeded,递归层次太深
#栈超界,Python中为了防止栈溢出,提供了调用深度问题,默认1000(IPython 3000)
#import sys
sys.getrecursionlimit()

递归效率

不如循环,可以进行改进,将循环改成递归调用,用递归调用次数来模拟循环次数。但递归函数有深度限制,函数调用开销较大。

间接递归

  • 函数通过别的函数调用了自己,同样是递归。
  • 只要是递归调用,不论直接间接都要注意边界返回问题;但间接调用有时候不明显,代码调用复杂时很难发现出现了递归调用,会很危险。
  • 靠代码规范来避免。

总结:

  1. 递归表达自然符合逻辑思维
  2. 递归相对运行效率低,每一次调用函数都要开辟栈帧
  3. 递归有深度限制,如果层次太深函数连续压栈会导致溢出
  4. 有限次数递归可以用递归调用,或者用循环代替;循环代码稍复杂,但只要不是死循环,可以多次迭代直至算出结果
  5. 绝大多数递归都可以用循环实现
  6. 即使递归代码简洁,但能不用就不用

插入排序

  • 每一趟都要把待排序数放到有序区中合适的插入位置

核心算法

  • 结果可为升序或降序
  • 扩大有序去区,减小无序区
  • 增加一个哨兵位,其中放置每一趟待比较值
  • 将哨兵位数值与有序区数值从右到左依次比较,找到哨兵位数值合适的插入点

算法实现

  1. 增加哨兵位:
  • 为了方便,采用列表头部索引0的位置放置哨兵位
  • 每一次从有序区最右端后的下一个数,即无序区最左端的数放到哨兵位
  1. 比较与挪动:
  • 从有序区最右端开始,从右到左依次与哨兵比较
  • 比较数比哨兵大,则右移一下,换下一个左边的数比较
  • 直到找不到大于哨兵的比较数,把哨兵插入到这个数右侧的空位即可

总结:

  1. 最好情况正好是升序排列,比较迭代n-1次
  2. 最差情况正好是降序排列,比较迭代n(n-1)/2次,数据挪动非常多
  3. 使用两层嵌套循环,时间复杂度O(n^2)
  4. 稳定排序算法
  5. 使用在小规模数据比较
  6. 如果操作耗时大,可以用二分查找来提高效率,即二分查找插入排序

生成器函数

  • 包含yield语句的生成器函数调用后,生成生成器对象的时候,生成器函数的函数体不会立即执行。
  • next(generator)会从函数的当前位置向后执行到之后碰到的第一个yield语句,会弹出值并暂停函数执行
  • 再次调用next函数和上一条一样的处理过程
  • 再继续调用next函数,生成器函数如果结束执行(显式或隐式调用了return语句)会抛出Stopiteration异常

生成器应用:

  1. 无限循环
  2. 斐波那契数列
  3. 生成器交互:
  • 调用send方法,就可以把send的实参传给yield语句做结果,这个结果可以在等式右边 他变量
  • send和next一样可以推动生成器启动并执行
  1. 协程Coroutine
  • 生成器的高级用法
  • 比线程进程轻量级,在用户空间调度函数的一种实现
  • Python3 asyncio就是协程实现
  • 协程调度器:两个生成器A、B,next(A)之后,A执行到yield语句暂停,然后去执行next(B)
  • 协程是一种非抢占式调度

重要定义:

  • 非线性结构,n个(n>=0)元素的集合
  • n=0时为空树
  • 树中只有一个特殊的没有前驱的元素称为树的根root
  • 除此之外树中所有元素都有且只能有一个前驱元素,可以有0个或多个后继

递归定义:树T是n个元素的集合;n=0时为空树
有且只有一个特殊元素根,剩余元素都可以被划分为m个互不相交的集合T1、T2、T3……,而每一个集合都是树,称为T的子树subtree,子树也有自己的根

  • 结点:树中的数据元素
  • 结点的度degree:结点拥有的子树数目称为度,记作d(v)
  • 叶子结点:结点的度为0,称为叶子结点leaf、终端结点、末端结点
  • 分支结点:结点的度不为0,称为非终端结点或分支结点
  • 分支:结点之间的关系
  • 内部结点:除根结点外的分支结点,不包括叶子结点
  • 树的度是树内各节点的度的最大值。例:D结点度最大为3,则该树的度数为3。
  • 孩子结点:结点的子树的根结点称为该结点的孩子
  • 双亲结点:一个结点是他各子树的根结点的双亲
  • 兄弟结点:具有相同双亲结点的结点
  • 子孙结点:从根结点到该结点所经分支上的所有结点
  • 节点层次(level):根结点为第一层,跟的孩子为第二层,以此类推记作L(v)
  • 树的深度(depth):树的层次的最大值
  • 有序树:结点的子树有顺序,不能交换
  • 无序树:结点的子树无序,可交换
  • 路径:树中的k个结点n1、n2、n3……满足ni是n(i+1)的双亲,称为n1到nk的一条路径
  • 路径长度:路径上的节点数-1,也是分支数
  • 森林:m(m>2)棵不相交的树的集合

二叉树:

  • 每个结点最多两棵子树
  • 二叉树不存在度数大于2的结点
  • 他是有序树,左子树右子树是有顺序的,不能交换次序
  • 即使某个结点只有一棵子树,也要确定他是左子树还是右子树

5种形态:
空二叉树
只有一个根结点
根结点只有左子树
根结点只有右子树
根结点有左子树和右子树

斜树

左斜树:所有结点都只有左子树;
右斜树:所有结点都只有右子树。

满二叉树
  • 一棵二叉树的所有分支结点都有左子树和右子树,并且所有叶子结点只存在在最下面一层
  • 同样深度二叉树中,满二叉树结点最多
  • k为深度(1<=k<=n),则结点总数为2^k-1
完全二叉树
  • 若二叉树深度为k二叉树的层数从1到k-1层的结点都达到了最大个数,在第k层的所有结点都集中在最左边,这就是完全二叉树
  • 完全二叉树由满二叉树引出
  • 满二叉树
性质:
  1. 在二叉树的第i层,至多有2^(i-1)个结点(i>=1)
  2. 深度为k的二叉树,至多有2^(i-1)个结点(i>=1)
  3. 对任何一棵二叉树T,如果其终端结点数为n0,度数为2的结点为n2,则有n0 = n2 + 1, 即叶子结点数-1就等于度数为2的结点数

高阶函数

一等公民(First-Class Object)

  • 函数在Python中是一等公民
  • 函数也是对象,是可调用对象
  • 函数可以作为普通变量,也可以作为函数的参数,返回值

高阶函数(High-order Function)

  • 数学概念y = f(g(x)
  • 至少能满足下面一个条件:

接受一个或多个函数作为参数
输出一个函数

sorted函数原理

def sort(iterable,*,key=None,reverse=False): #[1,2,3]
    newlist = []
    #下面是算法,从源列表iterable中遍历元素,每一个元素逐个插入到newlist中合适位置
    #生成一个升序或降序的newlist
    #如果可以,先实现reverse
    #如果可以,在实现key

    for x in iterable: # x 2
        #在newlist中,从第一个元素开始,一定要保证有序,可以认为newlist就是有序区
        cx = key(x)
        for i,y in enumerate(newlist): #[1] 0,1
            cy = key(y)
            comp = cx>cy if reverse else cx<cy #if x<y 升序 #if x>y: #降序
            
            if comp:
                newlist.insert(i,x) #在当前i位置插入y => [2,1]
                break
        else:
            newlist.append(x) #第一个元素 newlist[1]
            
    return newlist #有序的列表
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值