1 函数嵌套

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

函数的嵌套调用
def max2(a,b): # 判断两个变量的最大值
return a if a > b else b def 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中才能调用内部定义的函数func3
func2() 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 += 1
return x
inner()
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 y
return inner()
print(outer())

2.1 global关键字

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

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

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

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

针对global的总结:

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

3 闭包

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

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

def outer():
c = [1]
def inner():
c[0] = 1
return c
return 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 = 100
def inner():
nonlocal c # 声明不是本地的c(引用上级目录的c)
c += 200 # 对c进行修改
return c
print('内',c) # 100
c = 1000
return inner a = 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-函数销毁的更多相关文章

  1. 函数嵌套>作用域>闭包函数

    一:函数对象 函数是第一类对象,即表示函数可以当做数据传递 可以被引用:把函数内存地址赋值给一个变量名,仍然遵循函数的调用规则. 可以被当做参数传递:传递的是函数的运行的结果#可以当做返回值 把函数作 ...

  2. python全栈开发_day11_作用域,函数嵌套和闭包

    一:作用域 1)什么是作用域 作用域是规定一个变量可以作用的范围,运行和销毁的范围 2)作用域的分类 1.内置作用域built_in:随着解释器的运行而产生,解释器运行的终止而销毁. 2.全局作用域g ...

  3. python基础—函数嵌套与闭包

    python基础-函数嵌套与闭包 1.名称空间与作用域 1 名称空间分为: 1 内置名称空间   内置在解释器中的名称 2 全局名称空间   顶头写的名称 3 局部名称空间 2 找一个名称的查找顺序: ...

  4. js中的函数嵌套和闭包

    小编已经有一段时间没有更新文章了,最近一直在考虑接下来要更新什么内容.接下来,小编会围绕以下三个方面更新文章.实际项目中遇到的问题和解决方案.Vue源码解析.代码重构.关于数据可视化.小编也会按照这个 ...

  5. JavaScript的函数和作用域闭包

    1. 函数 1.1 定义函数 function add(x, y){ return x + y; } 上述函数定义如下: 关键字function指出这是一个函数定义: add是函数的名称: (x, y ...

  6. Python开发——函数【装饰器、高阶函数、函数嵌套、闭包】

    装饰器 装饰器本质就是函数,为其他函数添加附加功能. 原则: 不修改被修饰函数的源代码 不修改被修饰函数的调用方法 装饰器知识储备:装饰器 = 高阶函数 + 函数嵌套 + 闭包 案例:求函数运行时间! ...

  7. 《Python》 函数嵌套、闭包和迭代器

    一.函数的嵌套: 1.函数的嵌套调用 def max2(x,y): m = x if x>y else y return m def max4(a,b,c,d): res1 = max2(a,b ...

  8. python之函数嵌套与闭包

    一:函数的嵌套:在函数内部在定义一个函数,一层套一层 def father(name): print("from father %s" %name) def son(): prin ...

  9. python函数基础:嵌套函数、作用域、匿名函数、高阶函数、递归函数

    嵌套函数: 1. 函数内部可以再定义函数 2. 函数只有被调用之后才会执行 看如下代码: age = 18 def func1(): age = 22 print(age) def func2(): ...

随机推荐

  1. Hibernate 中一级缓存和快照区的理解

    刚刚开始的时候觉得这个快照区很难理解,在网上看了很多博客之后,开始明白了.我是结合 ADO.NET 理解的,在ADO.NET 中有一个类, 叫 SqlCommandBuilder,在我看来,他就是 A ...

  2. 【前端学习笔记05】JavaScript数据存储Cookie相关方法封装

    //Cookie设置 //设置新cookie function setCookie(name,value,duration){ var date = new Date(); date.setTime( ...

  3. 第92天:CSS3中颜色和文本属性

    一.颜色的表示方式 1. rgba(255,0,0,0.1)  rgba是代表Red(红色) Green(绿色) Blue(蓝色)和 Alpha透明度.虽然它有的时候被描述为一个颜色空间 新增了RGB ...

  4. BZOJ 2004 公交线路(状压DP+矩阵快速幂)

    注意到每个路线相邻车站的距离不超过K,也就是说我们可以对连续K个车站的状态进行状压. 然后状压DP一下,用矩阵快速幂加速运算即可. #include <stdio.h> #include ...

  5. IDEA使用switch传入String编译不通过

    今天在使用IDEA的时候,用到switch分支语句,传入String参数的时候一直报错,下面是源码报错截图: 看错误提示并没有提到switch支持String类型,不过ava1.7之后就支持Strin ...

  6. P1939 【模板】矩阵加速(数列)

    题目描述 a[1]=a[2]=a[3]=1 a[x]=a[x-3]+a[x-1] (x>3) 求a数列的第n项对1000000007(10^9+7)取余的值. 输入输出格式 输入格式: 第一行一 ...

  7. [BZOJ4870][Shoi2017]组合数问题 dp+矩阵乘

    4870: [Shoi2017]组合数问题 Time Limit: 10 Sec  Memory Limit: 512 MB Description Input 第一行有四个整数 n, p, k, r ...

  8. Qt消息机制和事件

    Qt消息机制和事件 1 事件 事件(event)是由系统或者 Qt 本身在不同的时刻发出的.当用户按下鼠标.敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件.一些事件在对用户操作做出响应 ...

  9. NOIP2017 考前汇总

    时隔一年,相比去年一无所知的自己,学到了不少东西,虽然还是很弱,但也颇有收获[学会了打板QAQ] 现在是2017.11.9   21:10,NOIP2017的前两天晚上,明天就要出发,做最后的总结 N ...

  10. Redis Scan迭代器遍历操作原理(二)

    续上一篇文章 Redis Scan迭代器遍历操作原理(一)–基础 ,这里着重讲一下dictScan函数的原理,其实也就是redis SCAN操作最有价值(也是最难懂的部分). 关于这个算法的源头,来自 ...