Python基本语法_变量作用域LEGB

目录

  • 目录
  • 软件系统
  • 变量的作用域
    • 高级语言对数据类型的使用过程
    • 作用域的产生
    • 作用域的类型
      • Llocal局部作用域
      • Eenclosing嵌套作用域
      • Gglobal全局作用域
      • Bbuilt-in内置作用域
    • 变量名解析LEGB法则
  • 实例说明
    • 对变量的引用
    • 对变量的修改
      • global关键字
      • nonlocal关键字
  • 命名空间和作用域的区别

软件系统

  • 系统
    • Ubuntu 14.04
  • 软件
    • Python 2.7.3
    • IPython 4.0.0

变量的作用域

在Python程序中创建、改变、查找变量名时,都是在一个保存变量名的空间中进行,我们称之为命名空间,也被称之为作用域。Python的作用域是静态的,在源代码中变量名被赋值的位置决定了该变量能被访问的范围。即Python变量的作用域由变量所在源代码中的位置决定

高级语言对数据类型的使用过程

一般的高级语言在使用变量时,都会有下面4个过程。当然在不同的语言中也会有着区别。
1. 声明变量:让编辑器知道有这一个变量的存在
2. 定义变量:为不同数据类型的变量分配内存空间
3. 初始化:赋值,填充分配好的内存空间
4. 引用:通过引用对象(变量名)来调用内存对象(内存数据)

作用域的产生

就作用域而言,Python与C有着很大的区别,在Python中并不是所有的语句块中都会产生作用域。只有当变量在Module(模块)、Class(类)、def(函数)中定义的时候,才会有作用域的概念

In [19]: %pycat testScopt.py
#!/usr/bin/env python
def func():
    variable = 100
    print variable
print variable

NameError: name 'variable' is not defined

在作用域中定义的变量,一般只在作用域中有效。
注意:在if-elif-else、for-else、while、try-except\try-finally等关键字的语句块中并不会产成作用域。

In [15]: if True:
   ....:     variable = 100
   ....:     print variable
   ....: print "******"
   ....: print variable
   ....:
100
******
100      #变量variable在if语句块内或外都表示同一个变量

作用域的类型

在Python中,使用一个变量时并不严格要求需要预先声明它,但是在真正使用它之前,它必须被绑定到某个内存对象(被定义、赋值);这种变量名的绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量。

L(local)局部作用域

局部变量:包含在def关键字定义的语句块中,即在函数中定义的变量。每当函数被调用时都会创建一个新的局部作用域。Python中也有递归,即自己调用自己,每次调用都会创建一个新的局部命名空间。在函数内部的变量声明,除非特别的声明为全局变量,否则均默认为局部变量。有些情况需要在函数内部定义全局变量,这时可以使用global关键字来声明变量的作用域为全局。局部变量域就像一个 ,仅仅是暂时的存在,依赖创建该局部作用域的函数是否处于活动的状态。所以,一般建议尽量少定义全局变量,因为全局变量在模块文件运行的过程中会一直存在,占用内存空间。
注意:如果需要在函数内部对全局变量赋值,需要在函数内部通过global语句声明该变量为全局变量。

E(enclosing)嵌套作用域

E也包含在def关键字中,E和L是相对的,E相对于更上层的函数而言也是L。与L的区别在于,对一个函数而言,L是定义在此函数内部的局部作用域,而E是定义在此函数的上一层父级函数的局部作用域。主要是为了实现Python的闭包,而增加的实现。

G(global)全局作用域

即在模块层次中定义的变量,每一个模块都是一个全局作用域。也就是说,在模块文件顶层声明的变量具有全局作用域,从外部开来,模块的全局变量就是一个模块对象的属性。
注意:全局作用域的作用范围仅限于单个模块文件内

B(built-in)内置作用域

系统内固定模块里定义的变量,如预定义在__builtin__ 模块内的变量。

变量名解析LEGB法则

搜索变量名的优先级:局部作用域 > 嵌套作用域 > 全局作用域 > 内置作用域
LEGB法则
当在函数中使用未确定的变量名时,Python会按照优先级依次搜索4个作用域,以此来确定该变量名的意义。首先搜索局部作用域(L),之后是上一层嵌套结构中def或lambda函数的嵌套作用域(E),之后是全局作用域(G),最后是内置作用域(B)。按这个查找原则,在第一处找到的地方停止。如果没有找到,则会出发NameError错误。
一个LEGB的例子

#!/usr/bin/env python
#conding:utf8

globalVar = 100           #G

def test_scope():
    enclosingVar = 200    #E
    def func():
        localVar = 300    #L
print __name__            #B

实例说明

对变量的引用

在不同作用域中可以存在相同的变量名,当出现这种情况的时候,对LEGB法则的理解就显得非常重要了,否则就会有只能知其然而不知其所以然的感觉。
实例1

def func():
    variable = 300
    print variable

variable = 100
func()
print variable

In [11]: %run testScopt.py
300     #优先搜索函数func()内的局部变量
100

实例2

In [29]: %pycat testScopt.py
#!/usr/bin/env python
#conding:utf8
def test_scopt():
    variable = 200
    print variable
    def func():
        print variable   #这里的变量variable在E中绑定了内存对象200,为函数func()引入了一个新的变量
    func()
variable = 100
test_scopt()
print variable

In [30]: %run testScopt.py
200
200    #在函数func()中无法找到变量variable,升级到test_scopt()中寻找
100

例子3

In [9]: %pycat testScopt.py
#!/usr/bin/env python
#conding:utf8

variable = 300
def test_scopt():
    print variable   #variable是test_scopt()的局部变量,但是在打印时并没有绑定内存对象。
    variable = 200

test_scopt()
print variable

UnboundLocalError: local variable 'variable' referenced before assignment

上面的例子会报出错误,因为在执行程序时的预编译能够在test_scopt()中找到局部变量variable(对variable进行了赋值)。在局部作用域找到了变量名,所以不会升级到嵌套作用域去寻找。但是在使用print语句将变量variable打印时,局部变量variable并有没绑定到一个内存对象(没有定义和初始化,即没有赋值)。本质上还是Python调用变量时遵循的LEGB法则和Python解析器的编译原理,决定了这个错误的发生。所以,在调用一个变量之前,需要为该变量赋值(绑定一个内存对象)。
注意:为什么在这个例子中触发的错误是UnboundLocalError而不是NameError:name ‘variable’ is not defined。因为变量variable不在全局作用域。Python中的模块代码在执行之前,并不会经过预编译,但是模块内的函数体代码在运行前会经过预编译,因此不管变量名的绑定发生在作用域的那个位置,都能被编译器知道。Python虽然是一个静态作用域语言,但变量名查找是动态发生的,直到在程序运行时,才会发现作用域方面的问题。
例子4

In [2]: %pycat testScopt.py
#!/usr/bin/env python
#conding:utf8

variable = 300
def test_scopt():
    print variable   #没有在局部作用域找到变量名,会升级到嵌套作用域寻找,并引入一个新的变量到局部作用域(将局部变量variable赋值为300)。
#    variable = 200

test_scopt()
print variable

In [3]: %run testScopt.py
300
300

比较例子3和4能够更好的理解LEGB的过程。

对变量的修改

一个non-L的变量相对于L而言,默认是只读而不能修改的。如果希望在L中修改定义在non-L的变量,为其绑定一个新的值,Python会认为是在当前的L中引入一个新的变量(即便内外两个变量重名,但却有着不同的意义)。即在当前的L中,如果直接使用non-L中的变量,那么这个变量是只读的,不能被修改,否则会在L中引入一个同名的新变量。这是对上述几个例子的另一种方式的理解
注意:而且在L中对新变量的修改不会影响到non-L的。当你希望在L中修改non-L中的变量时,可以使用global、nonlocal关键字。

In [18]: %pycat testScopt.py
#!/usr/bin/env python
#conding:utf8

variable = 100
def test_scopt():
    variable = 200   #在L中引入一个新变量,覆盖non-L中的变量
    print variable
test_scopt()
print variable

In [19]: %run testScopt.py
200
100

global关键字

希望在L中修改G中的变量。

In [57]: %pycat test.py
spam = 99
def tester():
    def nested():
        global spam
        print('current=',spam)
        spam = 200
    return nested
tester()()
print spam

In [58]: %run test.py
('current=', 99)
200

注意:tester()()表示会自动调用函数tester()的返回值,且此返回值必须为可调用类型,即存在__call__方法。返回一个函数,所以也会执行返回的函数体代码。

nonlocal关键字

在L中修改E中的变量。这是Python3.x增加的新特性,在python2.x中还是无法使用。

def outer():
    count = 10
    def inner():
        nonlocal count
        count = 20
        print(count)
    inner()
    print(count)
outer()
#20
#20

NOTE:Python 2 中没有 nonlocal 关键字,但可以使用转换变量名的方式来防止 UnboundLocalError: local variable 'x' referenced before assignment, 的错误。

def outter():
    x = 1
    def inner():
        print("inner is called, x=", x)
        x = 2
    return inner
outter()()

会触发 UnboundLocalError: local variable 'x' referenced before assignment,,因为 Python 解析器会认为在 local 作用域中 x 没有完成赋值就被调用了。

  • Python 3 使用 nonlocal 关键字:
#!/usr/bin/python
def outter():
    x = 1
    def inner():
        nonlocal x
        print("inner is called, x=", x)
        x = 2
    return inner

outter()()
  • Python 2可以转换变量名:
#!/usr/bin/python
def outter():
    x = 1
    def inner():
        y = x
        print("inner is called, x=", y)
        y = 2
    return inner

outter()()

命名空间和作用域的区别

  • 命名空间:是一个包含了该空间中所有变量和变量值的字典,不同的命名空间之间是相互隔离的,所以在不同的命名空间可以创建同名变量,通过句点标识符来调用和区别,EG. JmilkFan.name & fanguiju.name JmilkFan 和 fanguiju 是两个不同的命名空间。Python 内置了两个查询命名空间的字典的内置函数:
    • globals()
>>> x = 3
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, 'x': 3, '__package__': None}
  • locals()
>>> locals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, 'x': 3, '__package__': None}

NOTE: 因为这里的两个例子都是在全局环境中调用,globals()和locals()函数的返回值是一致的。这两个函数有下面点需要注意的地方:
1. 根据调用地方的不同,globals()和locals()函数可被用来返回全局和局部命名空间里的名字。
2. 如果在函数内部调用locals(),返回的是所有能在该函数里访问的命名。
3. 如果在函数内部调用globals(),返回的是所有在该函数里能访问的全局名字。
4. 两个函数的返回类型都是字典。所以能用keys()函数摘取变量名。

  • 作用域:是一个变量能够有效的区域,全局作用域的全局变量在整个模块中有效,局部作用域中的局部变量只在类或函数中有效。创建一个作用域会同时生成一个命名空间,并且作用域包围了其命名空间。作用域是为了实现变量查询的路径,就如上文所述,如何局部作用域中含有于全局作用域同名的变量时,局部作用域会屏蔽掉全局作用域,这是因为变量的查询路径中,局部作用域要先于全局作用域,然后再到相对的命名空间中获取变量的值。
时间: 2024-10-26 05:51:26

Python基本语法_变量作用域LEGB的相关文章

OC点语法和变量作用域

OC点语法和变量作用域 一.点语法 (一)认识点语法 声明一个Person类: 1 #import <Foundation/Foundation.h> 2 3 @interface Person : NSObject 4 { 5 int _age;//默认为@protected 6 } 7 8 - (void)setAge:(int)age; 9 - (int)age; 10 11 @end Person类的实现: 1 #import "Person.h" 2 3 @imp

Python基本语法_输入/输出语句详解

目录 目录 前言 输入 raw_input input raw_input 和 input 的区别 输出 print print 基本格式化输出 print复杂格式化输出 flags标志位 width宽度 precision精度 dictionaryName字典 print自动换行底层实现 最后 前言 程序最基本需要的两个要素,一个是数据,另外一个便是逻辑.而控制语句在程序中扮演的角色便是实现程序的逻辑,即数据的导向和对数据的操作.当然,这并不代表,程序中的数据的行为只能通过控制语句来实.但在P

Python基本语法_函数属性 &amp;amp; 参数类型 &amp;amp; 偏函数的应用

目录 目录 前言 软件环境 Python Module的程序入口 函数的属性 Python函数的创建 函数的参数 必备参数 缺省参数 命名参数 不定长参数 匿名参数 偏函数的应用 前言 Python除了有丰富的标准库之外,还可以自己定义满足自身需求的类和函数.本篇主要介绍如何创建Python自定义函数. 软件环境 系统 UbuntuKylin 14.04 软件 Python 2.7.4 IPython 4.0.0 Python Module的程序入口 因为一个Python的程序文件同时也是一个模

Python基本语法_异常处理详解

目录 目录 异常 异常类型 异常处理 触发异常raise 传递异常 assert语句触发异常 捕获异常tryexceptelse 捕捉多个异常 tryfinally语句 自定义异常 withas触发异常自动关闭资源 as获取异常信息 异常参数 traceback追踪异常 sysexc_info获取异常信息 最后 异常 异常即非正常状态,在Python中使用异常对象来表示异常.若程序在编译或运行过程中发生错误,程序的执行过程就会发生改变,抛出异常对象,程序流进入异常处理.如果异常对象没有被处理或捕

Python基本语法_基本数据类型_数值型详解

目录 目录 软件环境 Python变量调用的过程 数值型 删除一个数值类型对象 布尔型 Bool 标准整型 Int 长整型 双精度浮点型 Float 复数 数值类型对象的内建功能函数 absNumber 求Number的绝对值 coercex y 将x y转换为同一种数值类型 divmodx y 除法-取余运算的结合 pow 指数运算或将结果取余 round 浮点型的四舍五入运算和求精度 仅用于整型对象的函数 数值型相关模块推荐 软件环境 系统 CentOS 7 软件 Python 2.7.5

Python基本语法_运算符详解

目录 目录 前言 软件环境 身份运算符 算术运算符 比较运算符 位移运算符 自变运算符 位运算符 逻辑运算符 成员关系运算符 Python真值表 最后 前言 在前面的博文介绍了Python的数据结构之后,接下来结合Python操作符来对Python程序中的数据进行处理.操作符/运算符的使用,可简洁地表示内建类型的对象处理.主要是对程序中的数据进行逻辑操作.算术操作.比较操作等动作行为,本质是将在程序中会非常常用的程序操作封装成成类或函数后,再以字符的形式调用,使执行程序语言更加简洁和符合国际化.

Python基本语法_控制流语句_if/while/for

目录 目录 前言 软件环境 If 语句 While循环 break continue for 循环 遍历String 遍历Tuple 遍历List 遍历Dictionary 最后 前言 控制流语句用于改变程序语句流(默认为自上而下顺序执行)的执行顺序,其中Python的基本控制流语句,主要有以下3种: 1. if 语句:根据条件返回的结果执行相应的代码块 2. for 循环:通过遍历容器对象的索引来实现循环 3. while 循环:根据条件返回是否为True来判断是否执行循环体 在本篇中主要会介

Python基本语法_集合set/frozenset_内建方法详解

目录 目录 前言 软件环境 可变集合Set set函数创建集合 创建空集合 集合元素的唯一性 集合推导式 set类型对象的内置方法 add增加一个元素 remove删除一个元素 pop随机删除并返回一个元素 discard删除一个元素 clear 不可变集合Frozenset frozenset创建一个frozenset集合 set能够与frozenset作比较 set和frozenset的混合运算 frozenset集合作为dic的key setfrozenset共有的内建函数 interse

Python基本语法_文件操作_读写函数详解

目录 目录 软件环境 file文件对象 open文件操作 读文件 read读取所有文件内容 readline获取一行内容 readlines读取所有文件内容 readreadlinereadlines的区别 写文件 write writelines写入多行内容 write和writelines的区别 将标准输出重定向写入到指定文件 文件指针 tell获取当前文件指针位置 truncate截断文件 seek转移文件指针 最后 软件环境 系统 UbuntuKylin 14.01 软件 Python