07.python函数作用域global、nonlocal、LEGB

时间:2022-04-29 23:33:06

函数作用域

作用域

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

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个函数

07.python函数作用域global、nonlocal、LEGB

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

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

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

07.python函数作用域global、nonlocal、LEGB

原因分析:

  • 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变量在上级作用域而非本地作用域中定义。

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

07.python函数作用域global、nonlocal、LEGB

上例是错误的,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

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   参数 : 表达式 ...

  10. 【Python成长之路】python 基础篇 -- global/nonlocal关键字使用

    1 课程起源 有一次在工作中编写python工具时,遇到一个 问题:从配置文件读取变量A后,无法在内存中把A的值改变成新的内容.为了解决"更新内存中变量"的这个问题,查找了一些帖子 ...

随机推荐

  1. snmp getTable demo :iftable ipAddresstable

    package org.huangxf.snmp.test; import java.io.IOException; import java.util.List; import org.snmp4j. ...

  2. A9两款芯片管脚数目

        Exynos 4412   bga786; S5P4418  bga513 

  3. 【转载】CentOS LVM磁盘扩容

    转自:http://blog.sina.com.cn/s/blog_8882a6260101cpfs.html EXSI5.1主机有一个linux虚拟机,系统是centos运行httpd服务,因为是多 ...

  4. 推荐一些C#相关的网站、资源和书籍(转载)

    原文地址:http://blog.csdn.net/chinacsharper/article/details/17514923 一.网站 1.http://msdn.microsoft.com/zh ...

  5. 怎么在linux ubuntu 上的nginx 绑定域名

    前一篇介绍了,如果在ubuntu上运行nodejs,毕竟访问的时候都是用ip地址+端口号,但是上production 环境都需要域名的,IP地址当然不行 读过上一篇的朋友知道了,如果在upstart ...

  6. NIO通讯框架之Mina

          在两三年前,阿堂在技术博客(http://blog.sina.com.cn/heyitang)上曾经写过"JAVA新I/O学习系列笔记(1)"和"JAVA新I ...

  7. python中numpy.sum()函数

    讲解清晰,转载自:https://blog.csdn.net/rifengxxc/article/details/75008427 众所周知,sum不传参的时候,是所有元素的总和.这里就不说了. 1 ...

  8. linux将指令加入开机启动或加入环境变量

    以mongodb运行指令为例,/usr/local/webserver/mongodb/bin/mongo 1,linux将指令加入环境变量PATH 简单说PATH就是一组路径的字符串变量,当你输入的 ...

  9. ans menu list

    ans menu list 1. 系统配置 a) 基本设置 i. NTP ii. 配置模式 iii. 主机信息 b) 高可用性 i. 节点 ii. 路由监视器 iii. 故障转移接口群 c) 设备标识 ...

  10. occal [问题解决]ORA-01427: 单行子查询返回多个行

    有人问题我一个问题,情况如下:他要用根据divide_act_channel_day的new_amount字段去更新divide_stat的new_amount字段.两张表关联的条件:day=log_ ...