函数作用域

作用域

一个标识符的课件范围,这就是标识符的作用域,一般常说的是变量的作用域

def foo():
   x = 100
print(x) # 可以访问到吗

上例中x不可以访问到,会抛出异常(NameError: name 'x' is not defined),原因在于函数是一个封装,它会开辟一个作用域,x变量被限制在这个作用域中,所以在函数外部x变量不可见

注意:每一个函数都会开辟一个作用域

作用域分类

  • 全局作用域

    • 在整个程序运行环境中都可见

    • 全局作用域中的变量称为全局变量global

  • 局部作用域

    • 在函数、类等内部可见

    • 局部作用域中的变量称为局部变量,其使用范围不能超过其所在局部作用域

    • 也称为本地作用域local

# 局部变量
def fn1():
   x = 1 # 局部作用域,x为局部变量,使用范围在fn1内
def fn2():
   print(x) # x能打印吗?可见吗?为什么?
print(x) # x能打印吗?可见吗?为什么?
# 全局变量
x = 5 # 全局变量,也在函数外定义
def foo():
   print(x) # 可见吗?为什么?
foo()
  • 一般来讲外部作用域变量可以在函数内部可见,可以使用

  • 反过来,函数内部的局部变量,不能在函数外部看到

函数嵌套

在一个函数中定义了另外一个函数

def outer():
   def inner():
       print("inner")
   inner()
   print("outer")
outer() # 可以吗?
inner() # 可以吗?

内部函数inner不能在外部直接使用,会抛NameError异常,因为它在函数外部不可见。

其实,inner不过就是一个标识符,就是一个函数outer内部定义的变量而已。

嵌套结构的作用域

对比下面嵌套结构,代码执行的效果

def outer1():
   o = 65
   def inner():
       print('inner', o, chr(o))
   inner()
   print('outer', o, chr(o))
outer1() # 执行后,打印什么
def outer2():
   o = 65
   def inner():
       o = 97
       print('inner', o, chr(o))
   inner()
   print('outer', o, chr(o))
outer2() # 执行后,打印什么

从执行的结果来看:

  • 外层变量在内部作用域可见

  • 内层作用域inner中,如果定义了 o = 97 ,相当于在当前函数inner作用域中重新定义了一个新的

  • 变量o,但是,这个o并不能覆盖掉外部作用域outer2中的变量。只不过对于inner函数来说,其只能可见自己作用域中定义的变量o了

内建函数 函数签名 说明
chr chr(i) 通过unicode编码返回对应字符
ord ord(c) 获得字符对应的unicode
print(ord('中'), hex(ord('中')), '中'.encode(),'中'.encode('gbk'))
chr(20013) # '中'
chr(97)

一个赋值语句的问题

再看下面左右2个函数

左边函数 右边函数
正常执行,函数外部的变量在函数内部可见 执行错误吗,为什么?难道函数内部又不可见了?y = x + 1可以正确执行,可是为什么x += 1却不能正确执行?

仔细观察函数2返回的错误指向x += 1,原因是什么呢?

x = 5
def foo():
   x += 1
foo() # 报错如下

原因分析:

  • x += 1 其实是 x = x + 1

  • 只要有"x="出现,这就是赋值语句。相当于在foo内部定义一个局部变量x,那么foo内部所有x都是这个局部变量x了

  • x = x + 1 相当于使用了局部变量x,但是这个x还没有完成赋值,就被右边拿来做加1操作了

x = 5
def foo():   # 函数被解释器解释,foo指向函数对象,同时解释器会理解x是什么作用域
   print(x) # x 在函数解析时就被解释器判定为局部变量
   x += 1   # x = x + 1
foo() # 调用时

如何解决这个常见问题?

global语句

x = 5
def foo():
   global x # 全局变量
   x += 1
   print(x)
foo()
  • 使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x

  • 全局作用域中必须有x的定义

如果全局作用域中没有x定义会怎样?

注意,下面试验如果在ipython、jupyter中做,上下文运行环境中有可能有x的定义,稍微不注意,就测试不出效果

# 有错吗?
def foo():
   global x
   x += 1
   print(x)
foo()
# 有错吗?
def foo():
   global x
   x = 10
   x += 1
   print(x)
foo()
print(x) # 可以吗

使用global关键字定义的变量,虽然在foo函数中声明,但是这将告诉当前foo函数作用域,这个x变量将使用外部全局作用域中的x。

即使是在foo中又写了 x = 10 ,也不会在foo这个局部作用域中定义局部变量x了。

使用了global,foo中的x不再是局部变量了,它是全局变量。

总结

  • x+=1 这种是特殊形式产生的错误的原因?先引用后赋值,而python动态语言是赋值才算定义,才能被引用。解决办法,在这条语句前增加x=0之类的赋值语句,或者使用global 告诉内部作用域,去全局作用域查找变量定义

  • 内部作用域使用 x = 10 之类的赋值语句会重新定义局部作用域使用的变量x,但是,一旦这个作用域中使用 global 声明x为全局的,那么x=5相当于在为全局作用域的变量x赋值

global使用原则

  • 外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离

  • 如果函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用传实参解决

  • 一句话:不用global。学习它就是为了深入理解变量作用域

闭包

自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量

闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是JavaScript

def counter():
   c = [0]
   def inc():
       c[0] += 1 # 报错吗? 为什么 # line 4
       return c[0]
   return inc
foo = counter()                    # line 8
print(foo(), foo())                # line 9
c = 100
print(foo())                       # line 11

上面代码有几个问题:

  • 第4行会报错吗?为什么

  • 第9行打印什么结果?

  • 第11行打印什么结果?

代码分析

  • 第8行会执行counter函数并返回inc对应的函数对象,注意这个函数对象并不释放,因为有foo记着

  • 第4行会报错吗?为什么

    • 不会报错,c已经在counter函数中定义过了。而且inc中的使用方式是为c的元素修改值,而不是重新定义c变量
  • 第9行打印什么结果?

    • 打印 1 2
  • 第11行打印什么结果?

    • 打印 3

    • 第9行的c和counter中的c不一样,而inc引用的是自由变量正是counter中的变量c

这是Python2中实现闭包的方式,Python3还可以使用nonlocal关键字

再看下面这段代码,会报错吗?使用global能解决吗?

def counter():
   count = 0
   def inc():
       count += 1
       return count
   return inc
foo = counter()
print(foo(), foo())

上例一定出错,使用gobal可以解决

def counter():
   global count
   count = 0
   def inc():
       global count
       count += 1
       return count
   return inc
foo = counter()
print(foo(), foo())
count = 100
print(foo()) # 打印几?

上例使用global解决,这是全局变量的实现,而不是闭包了。

如果要对这个普通变量使用闭包,Python3中可以使用nonlocal关键字。

nonlocal语句

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

def counter():
   count = 0
   def inc():
       nonlocal count # 声明变量count不是本地变量
       count += 1
       return count
   return inc
foo = counter()
print(foo(), foo())

count 是外层函数的局部变量,被内部函数引用。

内部函数使用nonlocal关键字声明count变量在上级作用域而非本地作用域中定义。

代码中内层函数引用外部局部作用域中的自由变量,形成闭包。

上例是错误的,nonlocal 声明变量 a 不在当前作用域,但是往外就是全局作用域了,所以错误。

函数的销毁

定义一个函数就是生成一个函数对象,函数名指向的就是函数对象。

可以使用del语句删除函数,使其引用计数减1。

可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减1。

Python程序结束时,所有对象销毁。

函数也是对象,也不例外,是否销毁,还是看引用计数是否减为0。

变量名解析原则LEGB

  • Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡

  • Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间

  • Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡

  • Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如print(open),print和open都是内置的变量

所以一个名词的查找顺序就是LEGB

07.python函数作用域global、nonlocal、LEGB的更多相关文章

  1. 【学习笔记】--- 老男孩学Python,day10, 函数, 动态参数 命名空间\作用域 global nonlocal

    1. 动态参数 位置参数的动态参数: *args 关键字参数的动态参数 : **kwargs 顺序:位置---*args---默认值---**kwargs 在形参上*聚合, **聚合 在实参上*打散, ...

  2. python函数作用域LEGB

    我们的在学习Python函数的时候,经常会遇到很多定义域的问题,全部变量,内部变量,内部嵌入的函数,等等,Python是如何查找的呢?以及Python又是按照什么顺序来查找的呢?这里做一个顺序的说明 ...

  3. python-函数-动态传参,作用域的问题,函数嵌套,global nonlocal

    ⼀. 函数参数--动态传参 之前我们说过了传参, 如果我们需要给⼀个函数传参, ⽽参数⼜是不确定的. 或者我给⼀个 函数传很多参数, 我的形参就要写很多, 很⿇烦, 怎么办呢. 我们可以考虑使⽤动态参 ...

  4. Python函数作用域的查找顺序

    函数作用域的LEGB顺序 1.什么是LEGB? L:local 函数内部作用域 E:enclosing 函数内部与内嵌函数之间 G:global 全局作用域 B:build-in 内置作用域 2.它们 ...

  5. python函数作用域,嵌套函数,闭包

    函数作用域                                                                                                ...

  6. Day 10 动态参数&名称空间,局部全部.函数嵌套&global nonlocal关键字.

    一.动态参数#形参 有3种动态参数#*args 动态参数,不定长参数def func (*args): print(args,type(args))func(1,2,"alex", ...

  7. python函数作用域

    python中函数作用域 在python中,一个函数就是一个作用域 name = 'xiaoyafei' def change_name(): name = '肖亚飞' print('在change_ ...

  8. python函数作用域,闭包,装饰器

    第一:函数作用域: L:local 函数内部作用域 E:enclosing       函数内部与内嵌函数之间(闭包) G:global            全局作用域 B:build_in    ...

  9. Python函数作用域和匿名函数

    匿名函数的定义 全局变量和局部变量的概念 global(全局变量)和 nonlocal(局部变量) 闭包.递归.回调 匿名函数 匿名函数  lambda 语法规则:lambda   参数 : 表达式 ...

随机推荐

  1. 测试工具_webbench

    目录 一.简介 二.例子 一.简介 Webbench是知名的网站压力测试工具,能测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况. webbench的标准测试可以向我们展示服务器的 ...

  2. Ubuntu Server服务器上架设Git Server服务器

    1.设置公钥 ubuntu:/home/git$ ssh-keygen -t rsa #生成密钥 这里会提示输入密码,我们不输入直接回车即可. 然后用刚生成公钥/home/git/.ssh/id_rs ...

  3. ios越狱使用AltDeploy签名

    1.总结 截至目前: 越狱这么多年,用过非自己签名的越狱app, 也用过自己签名的越狱app. A .非自己签名, 别人签名好了越狱app, 下载安装,即可.笔者前期都是用的这样的方式. B.自己编译 ...

  4. c++11之字符串格式化

    1.关于 我知道的,C++20中引入了相当方便的字符串格式化,有兴趣的朋友,可以看下fmt库,截至目前,它实现了c++20中引入的字符串格式化绝大部分功能. 2.format 既然c++11中没有方便 ...

  5. 【剑指Offer】05. 替换空格 解题报告 (Python & C++ & Java)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人微信公众号:负雪明烛 目录 题目描述 解题方法 方法一:新建可变长度的容器 方法二:原 ...

  6. 【LeetCode】922. Sort Array By Parity II 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 使用奇偶数组 排序 奇偶数位置变量 日期 题目地址: ...

  7. 【LeetCode】540. Single Element in a Sorted Array 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 解题方法 方法一:异或 方法二:判断相邻元素是否相等 方法三:二分查找 ...

  8. 【LeetCode】525. Contiguous Array 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 累积和 日期 题目地址:https://leetco ...

  9. 【LeetCode】703. Kth Largest Element in a Stream 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 小根堆 日期 题目地址:https://leetco ...

  10. hdu-5568SUM (dp)

    sequence2 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total S ...