Python的作用域

Python的作用域

    Python是静态作用域语言,尽管它自身是一个动态语言。也就是说,在Python中变量的作用域是由它在源代码中的位置决定的,这与C有些相似,但是Python与C在作用域方面的差异还是非常明显的。

    接下来会谈论Python的作用域规则,在这中间也会说明一下Python与C在作用域方面的不同。

    在Python 2.0及之前的版本中,Python只支持3种作用域,即局部作用域,全局作用域,内置作用域;在Python 2.2中,Python正式引入了一种新的作用域 --- 嵌套作用域;在Python 2.1中,嵌套作用域可以作为一个选项被开启;嵌套作用域的引入,本质上为Python实现了对闭包的支持,关于闭包的知识,网上有很多解释,这里就不详细展开了。相应地,变量查找顺序由之前的LGB变成LEGB(L:Local,E:Enclosing,G:Global,B:Built-in)。

    在Python中,并不是任何代码块都能引入新的作用域,这与C有很大的不同:

复制代码
#include<stdio.h>
int main() {
    if(2 > 0) {
        int i = 0;
    }
    printf("i = %d", i);
    return 0;
}
复制代码

    在这段代码中,if子句引入了一个局部作用域,变量i就存在于这个局部作用域中,但对外不可见,因此,接下来在printf函数中对变量i的引用会引发编译错误。

    但是,在Python中却并非如此:

if True:
    i = 0
print i

    在这段代码中,if子句并没有引入一个局部作用域,变量i仍然处在全局作用域中,因此,变量i对于接下来的print语句是可见的。

    实际上,在Python中,只有模块,类以及函数才会引入新的作用域,其它的代码块是不会引入新的作用域的。

    在Python中,使用一个变量之前不必预先声明它,但是在真正使用它之前,它必须已经绑定到某个对象;而名字绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量,不论这个名字绑定发生在当前作用域中的哪个位置。 

def f():
    print i
f()

    运行结果将显示:NameError: global name 'i' is not defined。Python首先在函数f的本地作用域中查找变量i,查找失败,接着在全局作用域和内置作用域中查找变量i,仍然失败,最终抛出NameError异常。

复制代码
i = 0
def f():
    i = 8
    print i
f()
print i
复制代码

    运行结果显示:8和0。i = 8是一个名字绑定操作,它在函数f的局部作用域中引入了新的变量i,屏蔽了全局变量i,因此f内部的print语句看到的是局部变量i,f外部的print语句看到的是全局变量i。

i = 0
def f():
    print i
    i = 0
f()

     运行结果显示:UnboundLocalError: local variable 'i' referenced before assignment。在这个例子当中,函数f中的变量i是局部变量,但是在print语句使用它的时候,它还未被绑定到任何对象之上,所以抛出异常。

print i
i = 0

    不论是以交互的方式运行,还是以脚本文件的方式运行,结果都显示:NameError: name 'i' is not defined。这里的输出结果又与上一个例子不同,这是因为它在顶级作用域(模块作用域)的缘故。对于模块代码而言,代码在执行之前,没有经过什么预处理,但是对于函数体而言,代码在运行之前已经经过了一个预处理,因此不论名字绑定发生在作用域的那个位置,它都能感知出来。Python虽然是一个静态作用域语言,但是名字查找确实动态发生的,因此直到运行的时候,才会发现名字方面的问题。

    在Python中,名字绑定在所属作用域中引入新的变量,同时绑定到一个对象。名字绑定发生在以下几种情况之下:

    1.参数声明:参数声明在函数的局部作用域中引入新的变量;

    2.赋值操作:对一个变量进行初次赋值会在当前作用域中引入新的变量,后续赋值操作则会重新绑定该变量;

    3.类和函数定义:类和函数定义将类名和函数名作为变量引入当前作用域,类体和函数体将形成另外一个作用域;

    4.import语句:import语句在当前作用域中引入新的变量,一般是在全局作用域;

    5.for语句:for语句在当前作用域中引入新的变量(循环变量);

    6.except语句:except语句在当前作用域中引入新的变量(异常对象)。

    在Python中,类定义所引入的作用域对于成员函数是不可见的,这与C++或者Java是很不同的,因此在Python中,成员函数想要引用类体定义的变量,必须通过self或者类名来引用它。

    嵌套作用域的加入,会导致一些代码编译不过或者得到不同的运行结果,在这里Python解释器会帮助你识别这些可能引起问题的地方,给出警告。

    locals函数返回所有的局部变量,但是不会返回嵌套作用域中的变量,实际上没有函数会返回嵌套作用域中的变量。

    参考:

    http://www.python.org/dev/peps/pep-0227/

    http://beastie.cs.ua.edu/cs150/book/book_13.html

    http://www-inst.eecs.berkeley.edu/~selfpace/cs9honline/Q2/scope.html

    http://www.saltycrane.com/blog/2008/01/python-variable-scope-notes/

分类: Python
5
0
(请您对文章做出评价)
« 上一篇: Python源代码文件的文本编码
» 下一篇: 理解SVN关键词BASE,HEAD,COMMITTED,PREV

posted on 2012-08-12 17:58 frydsh 阅读(15342) 评论(2) 编辑 收藏

评论

#1楼 2015-03-24 17:39happen23  

最后一个例子是不是该这么理解?:f引用符号i时在本地找不到绑定信息,于是去全局作用域找到i,于是符号i绑定到全局i,但后面又对符号i赋初值,编译器根据作用于规则认为符号i是本地符号,而编译器不允许本地符号先引用再绑定,所以报错。反之,下例f引用i就没报错

>>> i = 0

>>> def f():
... print i
... j = 4
...
>>> f()
0
<think>好的,用户想了解Python中的作用域规则LEGB以及使用方法。让我先回忆一下相关知识点。LEGB代表Local、Enclosing、Global、Built-in四个作用域层级。用户之前询问过列表的常用方法,现在转向作用域,可能需要基础到应用的全面解释。 首先,我需要确保正确理解LEGB的顺序。根据引用的资料,查找变量时先检查局部作用域,然后是嵌套函数的作用域(如果有的话),接着是全局,最后是内置作用域。比如在函数内部定义的变量属于Local,如果没找到,就会去外层函数找Enclosing,再找模块级的Global,最后是Python内置的Built-in。 用户可能需要具体的例子来理解每个层级。比如,在函数里引用一个变量,Python如何逐层查找。例如,在函数inner里使用变量,如果在local没有定义,就会查看outer函数的作用域,然后是全局,最后是内置函数如len或print。 同时,用户可能对全局变量和局部变量的区别有疑问,如何修改全局变量需要使用global关键字,或者nonlocal在闭包中的使用。这些是常见的易错点,需要详细说明。 另外,用户可能想知道实际应用中如何正确使用作用域规则,比如避免变量覆盖,或者合理设计函数嵌套。例如,在闭包中使用nonlocal来修改外部函数的变量。 还要考虑用户可能的误区,比如在函数内部意外覆盖全局变量,或者不理解为什么在函数内部修改全局变量需要声明global。这时候需要用例子说明声明前后的区别。 另外,根据引用内容,LEGB的实例分析很重要。比如,引用5中的例子,函数内部找不到变量就去全局找。可以构造类似的例子,帮助用户理解查找顺序。 最后,用户可能需要知道常见错误及解决方法,比如UnboundLocalError,以及如何通过变量声明来避免。同时,比较global和nonlocal的区别,以及它们的适用场景。 总结下来,回答的结构应该先解释LEGB每个层级,用代码示例说明,然后讨论变量声明(global和nonlocal),最后给出实践建议和常见问题。确保引用相关的资料,如引用1、2、3等,来支持解释的准确性。同时,注意用户之前的问题是关于列表方法,现在转向作用域,可能需要更基础的讲解,避免过于深入高级话题,但也要覆盖必要的细节。</think>### Python作用域规则LEGB详解 Python作用域规则遵循**LEGB原则**,这是变量查找的核心机制。通过以下分层解析和实例演示,帮助您掌握作用域的实际应用方法。 --- #### 一、LEGB层级定义 1. **L-Local(局部作用域)** 定义在**函数内部**的变量,仅在当前函数内有效 ```python def func(): x = 10 # 局部变量 print(x) # 输出10 func() # print(x) # 报错NameError ``` 2. **E-Enclosing(嵌套作用域)** 外层嵌套函数的变量(闭包环境) ```python def outer(): e_var = "闭包变量" # E层 def inner(): print(e_var) # 访问外层函数变量 inner() outer() # 输出"闭包变量" [^4] ``` 3. **G-Global(全局作用域)** 定义在**模块层级**的变量,跨函数共享 ```python g_var = "全局变量" # G层 def show_global(): print(g_var) # 直接访问全局变量 show_global() # 输出"全局变量" [^4] ``` 4. **B-Built-in(内置作用域)** Python预定义的函数/变量(如`print`, `len`) ```python def calc_length(s): return len(s) # 调用内置函数len [^3] print(calc_length("test")) # 输出4 ``` --- #### 二、变量查找顺序 Python按**L→E→G→B**顺序查找变量,找到即停止: ```python x = "全局变量" # G层 def outer(): x = "闭包变量" # E层 def inner(): x = "局部变量" # L层 print(x) # 优先输出局部变量 inner() outer() # 输出"局部变量" ``` --- #### 三、关键操作规范 1. **访问全局变量** 使用`global`声明修改全局变量: ```python count = 0 def increment(): global count # 声明使用全局变量 count += 1 increment() print(count) # 输出1 [^4] ``` 2. **修改嵌套作用域变量** 使用`nonlocal`声明修改闭包变量: ```python def outer(): total = 0 # E层 def inner(): nonlocal total # 声明修改闭包变量 total += 5 inner() print(total) # 输出5 outer() ``` --- #### 四、典型场景与避坑指南 1. **变量覆盖问题** 局部变量与全局变量同名时,默认访问局部变量: ```python value = 100 def test(): value = 200 # 创建新局部变量 print(value) # 输出200 test() print(value) # 仍输出100 [^2] ``` 2. **UnboundLocalError** 未声明`global`时尝试修改全局变量会报错: ```python total = 10 def update(): # 尝试修改全局变量会报错 total = total + 1 # UnboundLocalError ``` --- #### 五、作用域对比表 | 作用域类型 | 定义位置 | 生命周期 | 访问范围 | 修改方式 | |------------|--------------------|-------------------|------------------|----------------| | Local | 函数内部 | 函数执行期间 | 仅限当前函数 | 直接修改 | | Enclosing | 外层嵌套函数 | 外层函数执行期间 | 嵌套函数内部 | `nonlocal`声明 | | Global | 模块层级 | 程序运行期间 | 跨模块访问 | `global`声明 | | Built-in | Python解释器预定义 | 始终存在 | 全局可用 | 不可修改 | --- #### 六、最佳实践建议 1. **避免过度使用全局变量** 优先使用函数参数和返回值传递数据 2. **闭包中合理使用nonlocal** 明确修改外层作用域变量时需声明 3. **命名规范** 全局变量使用全大写命名(如`CONFIG`)提高可读性 ```python # 推荐写法示例 def calculate(data): result = sum(data) # 使用局部变量 return result * 2 GLOBAL_FACTOR = 1.5 # 明确标识全局变量 [^4] ``` --- ### 相关问题 1. 如何在类方法中访问全局变量? 2. `global`和`nonlocal`关键字的区别是什么? 3. 什么是Python的闭包陷阱?如何避免?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值