10 - 函数嵌套-作用域-闭包-LEGB-函数销毁

2024-04-13 06:38

本文主要是介绍10 - 函数嵌套-作用域-闭包-LEGB-函数销毁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

10 - 函数嵌套-作用域-闭包-LEGB-函数销毁

 

目录

  • 1 函数嵌套
  • 2 作用域
    • 2.1 global关键字
  • 3 闭包
    • 3.1 nonlocal关键字
  • 4 默认值的作用域
  • 5 变量名解析原则LEGB
  • 6 函数的销毁

 

1 函数嵌套

        一个函数中存在另外一个函数(定义/调用),这种方式我们称之为函数嵌套。所以:函数的嵌套主要分为嵌套调用,以及嵌套定义

函数的嵌套调用
def max2(a,b):   # 判断两个变量的最大值return a if  a > b else bdef max4(a,b,c,d):  # 判断四个变量的最大值res1 = max2(a,b)  # 函数的嵌套调用res2 = max2(res1,c)res3 = max(res2,d)print(res3)max4(10,100,21,99)函数的嵌套定义
def func1():print('from func1')def func2():print('from func2')def func3():print('from func3')func3()  # 只有在func2中才能调用内部定义的函数func3func2()func1()

注意:在函数的内部定义函数,只能在函数内部进行调用,在其他地方是无法进行调用,强行调用就会提示NameError异常,所以说函数是有可见范围的,这就涉及到了作用域了

2 作用域

        一个标识符的可见范围,叫做标识符的作用域。一般常说的是变量的作用域。根据作用的范围主要分为全局作用域局部作用域

  • 全局作用域:在整个程序运行环境中都可见
  • 局部作用域:在函数、类的内部可见,并且使用范围不能超过所在的局部作用域(比如在函数内部定义了一个变量x,我在全局使用变量x是不行的。)
x = 1   # 全局变量
def outer():def inner():y = 100   # 局部变量print(x) inner()outer()
print(y)
  • 全局变量x在全局生效,所以内部函数inner是可以打印x的
  • 局部变量y只在inner内部生效,所以在全局print(y) 是无法调用局部变量y的

观察下面的例子:

x = 1
def outer():def inner():x += 1return xinner()
outer()

代码是从上到下执行的,所欲这样写也没什么毛病,但是这里这个例子是无法执行的,为什么呢?

  1. x作为全局变量,在inner内部是可见的
  2. 在定义函数的阶段,Python的函数是作为一个整体一起被解释的
  3. inner函数在解释时,解释器发现在inner内部对x进行了定义(x += 1),那么它就不会在调用全局变量x,而是标识x是局部定义的变量
  4. 而在执行x+=1的时候,inner内部的x还没有被定义,所以会提示x在定义前被执行了。(x += 1 --> x = x + 1 ,预先求 x + 1 时提示的)。

如何解决呢?有两种方法:更换变量名称、声明当前变量非本地变量(global)

x = 1
def outer():def inner():y = x + 1    # 这里定义的y是局部变量,而x来自于全局变量return yreturn inner()
print(outer())

2.1 global关键字

我们通过在函数内部使用global关键字来声明一个变量不是局部变量,而是一个全局变量。

def outer():def inner():global x   # 在函数内部声明一个全局变量,全局不存在时新建全局变量x,全局变量x存在时,则使用全局变量xx = 10   # 修改全局变量x的值inner()outer()
print(x)

虽然全局变量x,在全局没有被定义,但是由于在函数内部使用了global关键字,所以x就变成了全局变量了。使用了global关键字,那么之前的例子就可以进行如下修改了

x = 1
def outer():def inner():global x  # 使用全局变量xx += 1  # 这里的x是全局变量,那么对x的修改必然会作用域全局return xinner()
outer()
print(x) # 2   , 在函数内部把全局变量x给修改了!!!

针对global的总结:

  1. 外部作用域变量在内部作用域是可见的,但是不要在内部函数中直接使用或者修改,因为函数的目的就是为了封装,尽量与外界隔离。
  2. 如果函数需要使用外部全局变量,请尽量使用函数的形参定义,在调用时传递实参来使用
  3. 建议不要使用global

3 闭包

        在很多编程语言中都存在闭包的概念,那什么是闭包呢?闭包其实就是一个概念,出现在嵌套函数中,指的是:内层函数引用到了外层函数的自由变量,就形成了闭包

自由变量:未在本地作用域中定义的变量,比如在嵌套函数的外层定义的变量(非全局变量),对内层来说,这个变量就叫做自由变量。

def outer():c = [1]def inner():c[0] = 1return creturn inner()
a = outer()
print(a)

注意:上面这个例子比较特殊,首先它是一个闭包,在inner函数内引用了外层函数的自由变量C。因为这里的c是一个引用类型,我们可以直接通过c来操作c中的元素,但是没办法对c本身进行修改,即c += [1,3]。看似是列表拼接,但是它会重新对c进行声明,这就引发了之前的问题,内部函数inner没有定义c,所以会报错!所以当c不是引用类型的话,我们就没办法修改了吗?当然不是,可以使用global把c声明为全局变量,但是这就不是闭包了,所以这里就需要使用nonlocal了(python 3 特有)。
疑问?我们都说函数执行完毕后,函数的内部变量将会被回收,这里的outer执行完毕后,那么变量c应该会被回收啊,为什么还能被内层的inner找到呢?这是因为在定义阶段,解释器解释到inner函数时,由于函数是作为一个整体被解析的,所以解释器知道在inner内部引用了外部的变量,所以在执行函数outer时,并不会回收已被内部函数inner引用的自由变量c。

3.1 nonlocal关键字

        使用了nonlocal关键字,将变量标记为不在本地作用域定义,而在上一级局部作用域中定义,但不能是全局作用域中定义。

nonlocal只能用在嵌套函数的内部

def outer():c = 100def inner():nonlocal c  # 声明不是本地的c(引用上级目录的c)c += 200   # 对c进行修改return cprint('内',c)   # 100c = 1000return innera = outer()
print('外',a)  # 1200

4 默认值的作用域

        在Python中,一切皆对象,函数也不列外,当我们给函数定义默认值时,Python会把它存放在函数的属性中,这个属性值就伴随这个函数对象的整个生命周期。

foo.__defaults__属性查看函数的默认值属性

In [77]: import random...:...: def add(x=set(),y=[]):...:     x.add(random.randint(1,10))...:     y.append(1)...:     # print(x)...:     # print(y)...:...: print(add(),id(add))...: print(add.__defaults__)...:...: print(add(),id(add))...: print(add.__defaults__)
None 2721081985904
({1}, [1])
None 2721081985904
({1, 10}, [1, 1])

        仔细查看输出结果,发现函数地址没有变,也就是说函数这个对象没有变,但是我们发现每次它的__default__属性都会发生变化,这是为什么呢?这是因为sed和list的默认值都是引用类型,它们引用的都是函数在定义时定义的默认值中。 虽然函数执行完就释放了内存空间,也是由于引用类型,指向默认空间的指针没了,但是已经在调用时改变了默认值空间的对象中的元素,所以在下一次再次调用时此时默认值空间的元素已经被改变了。所以当函数的默认值为引用类型时,这点要特别的注意了
解决办法:

  • 在定义时使用引用类型时,在函数内部使用前先进行copy。
  • 在定义函数时默认使用None值,在函数内判断如果是None则开辟一个引用类型。

5 变量名解析原则LEGB

        变量的解析原则,也可以理解为变量的查找顺序:

  • L(Local): 本地作用域、局部作用域的local命名空间。函数调用是创建,调用结束消亡
  • E(Enclosing): Python 2.2时引入嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
  • G(Global): 全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
  • B(Build-in): 内置模块的命名空间,生命周期从Python解释器启动时创建到解释器退出时消亡。例如print函数、open函数等。

变量查找的规则为 L > E > G > B,即:先本地后嵌套再全局最后是内置函数中

6 函数的销毁

全局函数:

  • 重新定义同名函数
  • del 语句删除函数名称,函数对象引用计数减1
  • 程序结束时

局部函数:

  • 重新在上级作用域定义同名函数
  • del 语句删除函数名称,函数对象的引用计数减1
  • 上级作用域销毁时

所有巧合的是要么是上天注定要么是一个人偷偷的在努力。

这篇关于10 - 函数嵌套-作用域-闭包-LEGB-函数销毁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/899397

相关文章

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam

Python Counter 函数使用案例

《PythonCounter函数使用案例》Counter是collections模块中的一个类,专门用于对可迭代对象中的元素进行计数,接下来通过本文给大家介绍PythonCounter函数使用案例... 目录一、Counter函数概述二、基本使用案例(一)列表元素计数(二)字符串字符计数(三)元组计数三、C

Python中的filter() 函数的工作原理及应用技巧

《Python中的filter()函数的工作原理及应用技巧》Python的filter()函数用于筛选序列元素,返回迭代器,适合函数式编程,相比列表推导式,内存更优,尤其适用于大数据集,结合lamb... 目录前言一、基本概念基本语法二、使用方式1. 使用 lambda 函数2. 使用普通函数3. 使用 N

MySQL中REPLACE函数与语句举例详解

《MySQL中REPLACE函数与语句举例详解》在MySQL中REPLACE函数是一个用于处理字符串的强大工具,它的主要功能是替换字符串中的某些子字符串,:本文主要介绍MySQL中REPLACE函... 目录一、REPLACE()函数语法:参数说明:功能说明:示例:二、REPLACE INTO语句语法:参数

详解Java中三种状态机实现方式来优雅消灭 if-else 嵌套

《详解Java中三种状态机实现方式来优雅消灭if-else嵌套》这篇文章主要为大家详细介绍了Java中三种状态机实现方式从而优雅消灭if-else嵌套,文中的示例代码讲解详细,感兴趣的小伙伴可以跟... 目录1. 前言2. 复现传统if-else实现的业务场景问题3. 用状态机模式改造3.1 定义状态接口3

python中update()函数的用法和一些例子

《python中update()函数的用法和一些例子》update()方法是字典对象的方法,用于将一个字典中的键值对更新到另一个字典中,:本文主要介绍python中update()函数的用法和一些... 目录前言用法注意事项示例示例 1: 使用另一个字典来更新示例 2: 使用可迭代对象来更新示例 3: 使用

Python lambda函数(匿名函数)、参数类型与递归全解析

《Pythonlambda函数(匿名函数)、参数类型与递归全解析》本文详解Python中lambda匿名函数、灵活参数类型和递归函数三大进阶特性,分别介绍其定义、应用场景及注意事项,助力编写简洁高效... 目录一、lambda 匿名函数:简洁的单行函数1. lambda 的定义与基本用法2. lambda

Python 函数详解:从基础语法到高级使用技巧

《Python函数详解:从基础语法到高级使用技巧》本文基于实例代码,全面讲解Python函数的定义、参数传递、变量作用域及类型标注等知识点,帮助初学者快速掌握函数的使用技巧,感兴趣的朋友跟随小编一起... 目录一、函数的基本概念与作用二、函数的定义与调用1. 无参函数2. 带参函数3. 带返回值的函数4.

MySQL中DATE_FORMAT时间函数的使用小结

《MySQL中DATE_FORMAT时间函数的使用小结》本文主要介绍了MySQL中DATE_FORMAT时间函数的使用小结,用于格式化日期/时间字段,可提取年月、统计月份数据、精确到天,对大家的学习或... 目录前言DATE_FORMAT时间函数总结前言mysql可以使用DATE_FORMAT获取日期字段