• 函数基础


    一.函数的定义调用

    函数的使用必须遵循先定义,后调用的原则

    函数 定义 阶段: 只检测函数体的语法,不执行函数体代码
    函数 调用 阶段: 执行函数体代码

    Ps:代码从上而下执行,遇到def语句,函数体代码不会执行.但在运行前会进行 预编译.

    def 函数名(参数1,参数2,...):
      """
      函数功能描述消息
      :param 参数1:描述
      :param 参数2:描述
      :return: 返回值
      """
      pass
    

    函数返回值
    函数中不写return或return不带任何返回值,该函数返回值都是None
    不写return默认会在函数的最后一行添加 return None


    二.函数参数传递

    敲重点, 根据 python在作用域里对变量的赋值操作规则 , 函数的参数就是局部变量!!
    既然是局部变量,就需要定义, 即函数的参数传递 可看作是一个赋值操作 a = ? 重点看?的指向是啥..

    赋值操作

    在理清函数参数传递之前,要先了解两个重要的概念!! 非常重要! 知其然要知其所以然..

    1> python在作用域里对变量的赋值操作规则:
    若这个变量在该作用域存在(已经定义), 则对其绑定新的对象; 若不存在,则将这次赋值视为对这个变量的定义..
    2> python对象的赋值都是引用(内存地址)传递 被赋值 = (右侧的变量们)被引用
    "="赋值操作右侧的变量需要在某一个作用域里被找到,不然会报该变量未定义..
    Ps:变量的找寻遵循一个规则 依次从 局部 - 全局 - 内置 命名空间找.. 后面会有详细阐述..
    在此处,直接理解成 此变量在函数里没有,就去函数外找..

    在这里,简述 赋值即引用 带来的坑.. 需求:循环去除列表中的3

    """
    分析: 问题出在,B操作会同步改变A操作循环的值,i的取值 num[0]、num[1]、num[2]..这样的顺序来的
    		当循环到num[2]时,去除了第一个3,此时列表为[1,2,3,3,4];
    		继续循环,轮到nums[3],可此时列表中nums[2]的值为3却被跳过了..
    """
    nums = [1, 2, 3, 3, 3, 4]
    for i in nums:	# A
        if i == 3:
            nums.remove(i)	# B
    print(nums, len(nums))  # [1, 2, 3, 4] 4
    
    # --- 正解 
    nums = [1, 2, 3, 3, 3, 4]
    for i in nums[:]:
        if i == 3:
            nums.remove(i)
    print(nums, len(nums))  # [1, 2, 4] 3
    
    值传递

    函数参数 值传递(拷贝) 针对不可变对象(tuple\str\bool\数字\不可变集合)

    """-- 分析
    1> 参数a接收到的是值的类型是数字,数字是一个不可变对象 
    2> So,会将接收到的值进行一份拷贝! -- 值传递(拷贝)
    	 因为func函数内不存在变量a,所以会在func函数里新定义了一个局部变量a,将拷贝的值赋值给局部变量a..
    	 注意: func函数新定义的局部变量a与全局变量a无任何瓜葛!!
    """
    def func(a):		
        a = a + '3'
        return a
    
    a = '5'
    res = func(a)
    print(a,res)	# '5' '53'
    
    引用传递

    函数参数 引用传递(内存地址) 针对可变对象(list\dict\set)

    """-- 初步分析
    1> 参数a接收到的是值的类型是列表,列表是一个不可变对象 
    2> So,会对接收到的值进行引用 --  引用传递(内存地址)
    	 因为func函数内不存在变量a,所以会在func函数里新定义了一个局部变量a,将接收到的引用给局部变量a..
    	 注意: func函数新定义的局部变量a与全局变量a维护/指向的是同一个内存地址!!
    """
    def func(a):
        a += [4]	# -- 列表+=操作原地改变 等同于 a.extend([4])
        return a
    
    a = [1,2,3]
    res = func(a)
    print(a,res)	# [1, 2, 3, 4] [1, 2, 3, 4]
    
    # --- --- ---
    
    def func(a):
        a = a + [4]		# -- 在参数传递的时候,已经定义了局部变量a. 此时[1,2,3]的引用计数为2
    									#    根据python在作用域里对变量的赋值操作规则 该赋值操作是在对局部变量a重新赋值
    									#    重新赋值后,局部变量a重新绑定了一个新的对象[1,2,3,4]. 导致[1,2,3]的引用计数减1
    									#    id(左边a) != id(右边a) 它俩不是同一个对象
        return a
    
    a = [1,2,3]
    res = func(a)
    print(a,res)	# [1, 2, 3] [1, 2, 3, 4]
    
    UnboundLocalError

    官方解释: When a name is not found at all, a NameError exception is raised.
    If the name refers to a local variable that has not been bound, a UnboundLocalError exception is raised. UnboundLocalError is a subclass of NameError.

    翻译成人话: 若引用了某个变量,此变量在各个作用域里都找不到(详见4.3.1LEGB),就会报错 NameError ;
    若引用的变量是局部变量,但还未完成绑定,就会报错 UnboundLocalError ..
    UnboundLocalErrorNameError 的一个子类..

    """
    代码第二行报错: local variable 'a' referenced before assignment
    翻译过来: 局部变量'a'在赋值前被引用
    分析: func()函数没有参数,即不存在函数传递过程中定义局部变量
    		 函数体内有对a的赋值操作,根据python在作用域里对变量的赋值操作规则,因为func函数内没有局部变量a,则该赋值操作是在定义局部变量a,而在赋值操作的右侧引用了还未被定义完成的局部变量a
    
    Ps:有很长一段时间我是这样理解这个报错的.开始回溯我的理解.理解出错的地方在于(-- --)包含起来的这段文字.
    (-- 解释器发现右侧的a变量在局部作用域里还未绑定好,就引用全局变量a-- ),而"="左侧的a是局部变量,在一个赋值操作中,某一变量不可能既充当局部变量,又充当全局变量.. 进而产生了冲突.报错提示不是这样的,显然这样理解有偏差!!
    正确的过程应该是,解释器执行到a = a + 4,这个语句,发现它是赋值操作,准备将a变量名写入局部命名空间里,到这一步a变量已经定性为一个局部变量啦,但是此a变量还未完成绑定,正准备将"="右侧的内容与a变量进行绑定时,发现右侧的内容中又引用了还未绑定好的局部变量a..
    Python解释器一看这骚操作,很生气,大骂一声搁这套娃呢?果断扔出一张红牌,直接报错..
    """
    def func():
        a += 4	# a+=[4]报错 a.extend([4])不会报错
        return a
    
    a = 1	# a=[] 当a是一个列表时
    res = func()
    print(a,res)
    

    三.函数形参和实参

    在上文我们知道 函数的参数传递 可看作是一个赋值操作 a = ? 定义的是函数的局部变量
    具体一点, 参数分为形参和实参:
    形参 -- 本质就是变量名; 实参 -- 本质就是变量的值

    定义函数时 -- 行参

    定义函数时 位置形参-默认形参-不定长位置形参-命名关键字形参-不定长关键字形参

    形参 特性
    位置形参 必须被传值,多一个不行少一个也不行!!
    默认形参 意味着调用阶段可以不用为其赋值(可以不用指定对应的实参)!!
    不定长位置形参 *args 会将溢出的位置实参全部接收,然后以元祖的形式赋值给*后面的变量args
    命名关键字形参 * (*或者*args) 后面的形参对应的实参必须按照key=value的形式进行传值
    不定长关键字形参 **kwargs 会将溢出的关键字实参全部接收,
    然后以字典的形式赋值给**后面的变量kwargs
    调用函数时 -- 实参

    调用函数时 位置实参 关键字实参

    1> 两种实参可以混用, 但位置实参必须在关键字实参前面!且不能对一个形参重复赋值!
    2> 实参可以拆包!
    实参中带*,*会将该变量的值循环取出,打散成位置实参.
    即以后但凡碰到实参中带*的,它就是位置实参,应该立刻打散成位置实参来看
    实参中带**,**会将该变量的值循环取出,打散成关键字实参.
    即以后但凡碰到实参中带**的,它就是关键字实参,应该立刻打散成关键字实参去看

    def func3(a1, a2, a3, a4=10, *args, a5=20, a6, **kwargs):
        # 		11  22  33  44  20  10  (55, 66, 77)  {'a10': 123}
      	print(a1, a2, a3, a4, a5, a6, args, kwargs)
        
    # func3(11, *[22, 33, 44, 55, 66, 77], **{'a6': 10, 'a10': 123})
    func3(11, 22, 33, 44, 55, 66, 77, a6=10, a10=123)
    
    
    def func(*p):
    		return sum(p)  
    func(1,2,3,4)	# 10
    
    
    def func(**p):
      	return ''.join(sorted(p))   
    func(x=1,z=2,y=3)	# 'xyz'
    
    默认形参陷阱

    记住两点:
    除非程序结束,否则该共享空间不会被释放; 类似于全局变量...
    默认形参的值通常应该定义为不可变类型.

    1> 若函数有默认形参,在函数初次调用时,会为其开辟一块共享空间;
    2> 往后每一次调用此函数创建的空间都会共享这一块 “共享空间” 里的默认形参
    3> 除非程序结束,否则该共享空间不会被释放!!!
    Ps: 也有人会说, "默认形参的值只在定义阶段赋值一次,即默认参数的值在函数定义阶段就已经固定死了". 这种说法反复揣摩后,是有一定的道理的, 但不好理解.. 说法不够严谨,不必深究...

    def func(a=[555]):
        a.extend([1, 2, 3]) # -- 等同于 a+=[1,2,3]
        
        return a,id(a)
    
    # -- 函数第一次调用时共享空间为[555];第二次调用时没有用共享空间;第三次调用时共享空间为[555,1,2,3];
    #		 可以观察到,第一次id(a)和第三次id(a)是一样的..
    #    证明第二次调用虽然没有用共享空间会导致[555]的引用计数为0,但该共享空间没有被释放!!
    print(func())
    print(func([]))
    print(func()) 
    """结果
    ([555, 1, 2, 3], 140573741203776)
    ([1, 2, 3], 140692741938688)
    ([555, 1, 2, 3, 1, 2, 3], 140573741203776)
    """
    
    
    # --- --- ---
    # 依照python在作用域里对变量的赋值操作规则
    # 注意哦,函数体内对此默认形参重新赋值,是为此变量绑定了一个新的对象!!!
    def func(a=[555]):
      	print(a, id(a))
        a = a + [1, 2, 3] # -- 局部变量a重新赋值,其id发生变化
        
        return a,id(a)
    
    # -- 可以观察到函数第一次和第二次调用的共享空间都为[555],其id也没有发生变化!
    #		 证明 a = a + [1, 2, 3] 该行代码虽然导致[555]的引用计数为0,但该共享空间没有被释放!
    print(func())  # [555, 1, 2, 3]
    print(func())  # [555, 1, 2, 3]
    """结果
    [555] 140263832453056
    ([555, 1, 2, 3], 140263832467520)
    [555] 140263832453056
    ([555, 1, 2, 3], 140263832428928)
    """
    
    
    # --- --- --- 正解
    def func0(a,b=None):
      	if b is None:
        		b = []
            
            
    # --- --- --- 换个思考角度理解 默认形参陷阱 
    """
    默认形参的共享空间可以简单理解成 一个全局变量
    """
    def func(a=[555]):pass	func()	func()
    b = [555]		def func(a=b):pass	func()	func()
    b = [555]		def func(a):pass	func(b)	func(b)
    

    四.函数作用域

    4.1 函数对象

    4.1.1 函数是第一类对象

    函数是第一类对象,可以当作数据进行传递
    1> 被引用;
    2> 当作参数传递;
    3> 返回值是函数;
    4> 作为容器类型的元素

    def foo():
        print('foo')
    
    def bar():
        print('bar')
    
    dic = {
        'foo': foo,
        'bar': bar,
    }
    while True:
        choice = input('>>: ').strip()
        if choice in dic:
            dic[choice]()
    
    4.1.2 函数嵌套定义

    需求: 将圆相关的计算(面积、周长)集中在一起.

    from math import pi
    
    def circle(radius, action='area'):
        def area():
            return pi * (radius**2)
        def perimeter():
            return 2 * pi * radius
    
        if action == 'area':
            return area()
        elif action == 'perimeter':
            return perimeter()
    
    print(circle(10, 'perimeter'))
    
    # -- Ps: max(max(2,3),8)
    
    4.1.3 打破层级限制

    函数对象可以将定义在函数内的函数返回到全局中使用,从而打破函数的层级限制

    def f1():
      def inner():
        print('from inner')
      return inner
    
    f = f1() # -- 拿到inner函数的内存地址.
    

    4.2 命名空间/变量名

    要改变惯性思维,从命名空间的角度去看待变量!!

    4.2.1 命名空间概念

    在一个复杂的程序中,会定义成千上万个变量(函数名、类名都是变量), 为了便于追踪这些变量,让它们互不干扰,命名空间就应运而生啦!!

    命名空间 namespaces: 这几种说法都正确
    存放名字与值绑定关系的地方, 一般简说为存放变量名的地方.
    命名空间是键值对的集合! 变量名与值是一一对应的关系..
    命名空间是变量名到对象内存地址的映射.
    eg:x=1 开辟一块空间,1 存放在内存中; x:id(1) 放在命名空间中

    变量存储的角度, 在定义变量时, 变量名与值内存地址的关联关系 存放于栈区stack, 变量值 存放于堆区heap.

    4.2.2 命名空间分类

    各个命名空间是独立的, 没有任何关系的, 所以一个命名空间中不能有重名
    但不同的命名空间是可以重名而没有任何影响

    就好比一个文件夹中可以包含多个文件夹, 每个文件夹不能重名, 但不同文件夹中的文件可以重名...

    print(len) # 
    x=1
    if 3>2:
      	z=5
    def func():
      	y=2
    func()
    
    # -- 内置命名空间中的对象可以通过 dir(__builtins__) 命令查看.
    
    类别 解释
    内置命名空间 built-in names 存放python解释器自带的名字 len
    全局命名空间 global names 存放模块中定义的名字(该模块中除开内置的和局部的,都是全局的)
    x func z
    局部命名空间 local names 存放函数调用时函数中定义的名字 y

    Ps: 严谨一点, 解释器还为程序使用 import  语句加载的任何模块创建一个全局命名空间
    注意: 全局和局部命名空间的实现是字典, 但内置命名空间不是!

    4.2.3 命名空间生命周期

    指的是命名空间中 变量名!! 不是内存中的变量值的生命周期!! 

    内置: 在解释器启动时直接创建加载,直到解释器关闭时失效.
    全局: 在文件执行时生效,在文件执行完毕时失效.
    局部: 在文件执行过程中,如果调用了某个函数才会临时生效,在函数执行完毕后失效.

    局部命名空间的声明周期是自其建立开始,到它们各自的函数执行完毕终止.. 当这些命名空间的函数终止时, Python可能不会立即回收分配给这些命名空间的内存, 但是对其中对象的所有引用都将失效..

    4.2.4 变量加载查询顺序

    变量加载进命名空间的顺序: 内置 --> 全局 --> 局部
    变量在命名空间里查找的顺序: 从当前位置的局部命名空间 --> 全局 --> 内置

    通俗点解释下:
    python是解释型语言,边翻译边执行,在代码从而下的执行过程中,不断有变量被加载进命名空间.. 值得一提的是,当遇到def语句,会先将函数名字加载进全局命名空间,但函数体代码不会执行,会暂时跳过; 函数内部的变量要等到函数调用的时候才会加载进局部命名空间...
    变量在命名空间里查找的顺序(简单理解就是作用域).. 举个例子,函数里使用了x变量,但此变量不在该函数的局部命名空间里,就会去全局命名空间里找... 当然实际情况会更复杂.后文会详细阐述作用域.

    """变量加载进命名空间
    第几行代码		全局							内置
    1					 func	   
    4					 func 	x:1
    5					 func		x:1				 y:2
    6					 func		x:10			 y:2
    
    记住一句话,作用域关系是在函数定义阶段就固定死了,与函数的调用位置无关!!
    """
    def func():
        y = 2
        print(x)
    x = 1
    func()	# 1
    x = 10
    func()	# 10 -- 在调用函数之前,全局命名空间里的x的值被改为10啦
    
    # ---  --- ---
    
    x = 10
    a = lambda y: x + y
    x = 20
    b = lambda y: x + y
    print(a(10))  # 30
    print(b(10))  # 30
    

    4.3 作用域

    说白了, 作用域就是变量在命名空间里查找的顺序.. 作用域定义了命名空间里的变量的在多大的范围起作用!!

    作用域关系是在函数定义阶段就固定死了,与函数的调用位置无关!!
    但凡调用函数都需要跑到定义阶段去找作用域关系!一层套一层的..在定义阶段就套好啦.
    实则说的就是变量加载进命名空间以及变量在命名空间查询的顺序.. 不是什么新的知识,换了个说法而已.

    小声提醒下: 只有模块module,类class以及函数def、lambda才会引入新的作用域(即开辟新的命名空间), 其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的..

    4.3.1 LEGB

    在表达式中引用变量时,Python解释器将按照如下顺序遍历各作用域,以解析该引用:
    局部作用域/局部命名空间 -- 当前函数的范围 L Local
    内嵌作用域/内嵌命名空间 -- 函数嵌套里外围函数的范围 E Enclosing
    全局作用域/全局命名空间 -- 当前py模块的范围(不包含import的py模块) G Global
    内置作用域/内置命名空间 -- 包含len及str等函数的那个scope B Built-in

    若在这些地方都找不到名称相符的变量,就会抛出NameError异常..
    Ps: LEGB规则在Python文献中会经常看到, 但Python官方文档中并没有实际出现这个术语..Hhh

    def f1():
        x = 1	# -- 提一嘴,f1函数结束后,x变量还能被函数嵌套中的内部函数inner访问到..Why?因为x是自由变量!
    					#    后续的闭包部分会详细阐述!!
        def inner():
            print(x)	# -- 作用域关系是在函数定义阶段就固定死了,与函数的调用位置无关!!
        return inner
      
    f = f1()
    
    def bar():
        x = 111
        f() # -- 与调用位置无关
        
    bar() # 1
    
    4.3.2 globals()、locals()

    python提供了两个内置函数globals()和locals(),前者返回全局命名空间的字典,后者返回局部命名空间字典..

    >>> type(globals()),type(locals())
    (<class 'dict'>, <class 'dict'>)
    

    注意:globals()返回全局命名空间的实际引用; 随便怎么折腾都是在操作全局命名空间, 类似于 a = b

    """
    可以通过引用对象的变量名x,以常规的方式访问该对象..亦可以通过全局命名空间字典间接访问它
    """
    >>> x = 'foo'
    >>> 'x' in globals()			# -- 注意,变量名是以字符串的形式作为键的
    True
    >>> x
    'foo'
    >>> globals()['x']
    'foo'
    >>> x is globals()['x']		# -- x的值与"x"键所对应vlaue值的内存地址相同
    True
    
    """
    可以使用globals()函数在全局命名空间中创建和修改全局变量
    """
    >>> y
    NameError: name 'z' is not defined	# -- 全局命名空间中没有变量y
    >>> glo = globals()				# -- 注意:glo是全局命名空间的引用
    >>> glo = 100	# -- 创建	等同于 globals()['y'] = 100
    >>> y
    100
    >>> glo['y'] = 123	# -- 修改
    >>> y
    123
    
    """
    globals结合format的应用
    """
    >>> x = 1
    >>> y = 2
    >>> "{x},{y}".format(**globals())
    '1,2'
    

    locals()返回的不是对局部命名空间的引用!! 那到底返回的是什么呢?
    查阅资料,很多博客说是拷贝、副本.. 云里雾里的. 自己做了实验后, 发现这样的描述都不够严谨..

    先说结论: locals()函数返回的是对局部命名空间的一个"拷贝" (打了引号哦) ,但此拷贝有点特殊,它具备浅拷贝的一些特点,同时当我们再次调用locals()函数时,它会 **同步更新 **"拷贝"的值..

    """
    什么是拷贝、副本、视图?
    参考链接: https://blog.csdn.net/Reborn214/article/details/124539097
    		简单来说,副本、视图是python numpy数组中的专业名词,但其具备的特性跟拷贝差不多.
    		numpy中引用 = python中引用; numpy中视图 = python中浅拷贝; numpy中副本 = python中深拷贝
    		值得一提的是,引用一般发生在赋值操作(python赋值都是引用"内存地址"传递)
    		
    还要注意一个坑!!
    id():返回对象的“标识值”.该值是一个整数,在此对象的生命周期中保证是唯一且恒定的.
    		 两个生命期不重叠的对象可能具有相同的id()值..
    举个例子
    	分析 -- 没有将a[:]赋值给变量进行引用,当执行完a[:]后,这个对象就释放啦.但这块内存可能并没有来得及释放.
    				 当我们新执行a[:]的时候,用的将会是同一个内存,没有申请新的内存.
    				 这会让我们觉得两个a[:]与a[:]是同一个对象
    	>>> a = [1,2,3]
    	>>> id(a[:])
    	140417051683648
    	>>> id(a[:])
    	140417051684544
    	>>> id(a[:])
    	140417051684544
    	>>> id(a[:]) is id(a[:])	# -- 让其处于同一生命周期,每次浅拷贝都会生成不同的对象,结果肯定为False
    	False
    """
    def func():
      	# -- 验证了id()的坑.
        print(id(locals()))  # 140204424022656
        print(id(locals()))  # 140204424022656
        print(id(locals()) is id(locals()))  # False
    
        m = [1, 2, 3]
        n = 66
        loc = locals()  # -- loc是局部命名空间的“拷贝” loc是对此"拷贝"的引用!!
        print(loc)  # {'m': [1, 2, 3], 'n': 66}
    
        """来,简化一下
        import copy
        scope = {'m': [1, 2, 3], 'n': 66}		  # -- 指代局部命名空间
    		loc = copy.copy(scope)								# -- 指代局部命名空间的浅拷贝
    		loc['m'].append(4)
    		scope.append(5)
    		scope['n'] = 88
    		scope['x'] = 20
    		loc['n'] = 77
    		print(scope)		# -- {'m': [1, 2, 3, 4, 5], 'n': 88, 'x': 20}
    		print(loc)			# -- {'m': [1, 2, 3, 4, 5], 'n': 77}
        """
        loc['m'].append(4)	# -- 试图通过"拷贝"过来的字典修改局部命名空间里的可变类型的变量m 成功
        m.append(5)  # -- 在局部命令空间里修改了m变量对应的值
        n = 88  # -- 在局部命令空间里修改了n变量对应的值
        x = 20  # -- 在局部命名空间里添加了x变量
        # -- 可以发现此"拷贝",具备浅拷贝的特性
        print(loc)  # {'m': [1, 2, 3, 4, 5], 'n': 66}
        loc['n'] = 77		# -- 试图通过"拷贝"过来的字典修改局部命名空间里的可变类型的变量m 失败
        print(locals())  # {'m': [1, 2, 3, 4, 5], 'n': 88, 'loc': {...}, 'x': 20}
        
        # -- 再次调用locals()后,"拷贝"同步更新了.
        print(loc)  # {'m': [1, 2, 3, 4, 5], 'n': 88, 'loc': {...}, 'x': 20}
    
    func()
    
    4.3.3 global、nonlocal

    回顾下Python在作用域里对变量的赋值操作规则:
    若这个变量在该作用域存在(已经定义), 则对其绑定新的对象; 若不存在,则将这次赋值视为对这个变量的定义..

    global -- 不管是在函数嵌套的哪一层,对x变量的赋值操作都是修改全局作用域里的那个x变量!!
    nonlocal -- 如果在闭包内给x变量赋值,那么修改的其实是闭包外那个作用域里的x变量..
    nonlocal的唯一限制在于,不能延伸到模块级别,这是为了防止它污染全局变量..
    Ps: 提一嘴闭包,后续会详细阐述. 函数嵌套,内部函数引用外部函数的参数或变量,就构成了闭包..

    """
    ★ --global
    		在局部若想修改全局的不可变类型,需要借助global声明
    		在局部若想修改全局的可变类型,不需要借助任何声明,可以直接修改
    		
    		若全局中没有x变量,global语句和赋值的组合(不管处于嵌套的哪一层)可以间接在局部中创建x全局变量
    """
    x = []
    
    def func():
        global m
        m = 10
        globals()['n'] = 20		# -- n变量在全局中存在的话,此赋值操作就是在修改;通常不会这么做,完全没必要
        x.append(1)
    
    func()
    func()
    print(x)  	 # [1,1]
    print(m, n)  # 10,20
    
    
    """
    ★ --nonlocal
    """
    x = 1
    
    def f1():					# E f1的参数对f2而言
        x = 111 			# E 此处的x对f2而言
        def f2(): 		# E f2的参数对f3而言
            x = 222 	# E	此处的x对f3而言
            def f3():
                nonlocal x 	# -- 若f1函数嵌套里没有x 则报错找不到x变量
                x = 333 		# -- 改的是最近的x = 222的值
            f3()
            print(x)  # 333
            # {'f3': .f2..f3 at 0x7fc60c71c9d0>, 'x': 333}
            print(locals()) # f2的局部作用域
        f2()
        print(x)  # 111
        # {'x': 111, 'f2': .f2 at 0x7fc60c71c790>}
        print(locals()) # f1的局部作用域
        
    f1()
    print(x)  # 1
    

  • 相关阅读:
    汽车研发项目进度管理的挑战与优化策略
    仅30行代码,实现一个搜索引擎(1.0版)
    Java ThreadPoolExecutor 线程池
    统一数据返回格式的实现
    [思维]Sum Plus Product 2022杭电多校第9场 1010
    sudo rosdep init问题
    Caused by: java.net.UnknownHostException: nacos
    MATLAB中find_system用法
    antd vue 表单中getFieldDecorator、getFieldValue、setFieldValue用法
    ttkefu在线客服在日常生活中的便利性体验
  • 原文地址:https://www.cnblogs.com/DC0307/p/16697346.html