python 变量作用域详细介绍

 在python中,变量查找遵循LGB原则,即优先在局部作用域(local scope)中对变量进行查找,失败则在全局作用域(global scope)中进行查找,最后尝试再内建作用域(build-in scope)内查找,如果还是未找到的话,则抛出异常。后来由于闭包和嵌套函数的出现,作用域又增加了外部作用域,这样变量的查找作用域优先级变为:局部、外部、全局和内建。 作用域由def、class、lambda等语句产生,if、try、for等语句并不会产生新的作用域。我们看下面的一个例子:

 代码如下 复制代码

def scope1_f1():
    def f2():
        print local_v
    local_v = 'local'
    f2()
    print global_v

if __name__ == "__main__":
    global_v = 'global'
    scope1_f1()

   在scope1_f1函数中我们并未对global_str进行赋值(即scope1_f1的局部作用域中并不存在变量lobal_str),但程序正常输出结果'local'和'global'。

一 局部作用域中不应对全局变量进行赋值
   需要注意的是虽然我们可以在函数中对全局的变量进行访问,但一旦局部作用域中对全局变量进行了赋值操作,python解释器就不会从全局作用域中查找,而会抛出UnboundLocalError错误。该规则在由局部作用域向外部作用域查找时同样有效。

 代码如下 复制代码

def scope1_f1():
    print global_v
    global_v = 'local'

if __name__ == "__main__":
    global_v = 'global'
    scope1_f1()

   运行程序我们会得到:UnboundLocalError: local variable 'global_v' referenced before assignment

   有资料将该规则描述为“局部作用域中全局变量应是只读”是不准确的。因为如果变量为list等类型,我们可以通过append这样的方法来修改全局变量,而不影响局部作用域对变量的访问。

 代码如下 复制代码

def scope1_f1():
    print global_v
    global_v.add('local')

if __name__ == "__main__":
    global_v = ['global']
    scope1_f1()

二 局部作用域中使用 global实现对全局变量进行赋值

   如果说确实需要对全局变量进行赋值的话,应在局部作用域中使用global来修饰变量。python2.6中global对变量的修饰可以在函数的任意地方进行,但如果global在赋值之后的话,解释器会提示SyntaxWarning 。

 代码如下 复制代码

def scope1_f1():
    global global_v
    print global_v
    global_v = 'local'

if __name__ == "__main__":
    global_v = 'global'
    scope1_f1()

三 继承类优先使用第一个基类中的变量

   在没有对变量初始化的情况下,继承类会优先使用第一个基类中的变量。下面的例子会输出1而不是2。(说明:如果继承类为初始化函数,会优先调第一个基类的初始化函数,如果前面的基类都没有的话才会调后面基类的初始化函数,初始化函数对变量的修改不在本文讨论范围),

 代码如下 复制代码

class sclass1():
    a = 1
    def run(self):
        print self.a
class sclass2():
    a = 2
    def run(self):
        print self.a

class dclass(sclass1, sclass2):
    def run(self):
        print self.a

if __name__ == "__main__":
    a = dclass()
    a.run() 

四 全局作用域指的是本模块而不是程序

  在变量查找时只会在本模块范围内进行变量的查找,即使使用from xxx import *也不会垮模块查找。在python中导入一个模块可以理解为是将另外一个模块各变量赋值给当前模块的同名变量, 对当前模块中变量的赋值不会影响到导入模块的变量。

 代码如下 复制代码

main.py
from module2 import *
if __name__ == "__main__":
   module2_v = 5
   module2_v1 = 3
   print_modul2()

modul2.py
module2_v1 = 'module2'
def print_modul2():
   print module2_v1
   print module2_v 

   在python中一切都是对象和变量,因此对函数、模块等的的查找同样遵循上面的规则。
   最后还存在的一个问题是:使用global可以再局部作用域中对全局变量赋值,那么如何在局部作用域中对外部变量赋值呢? 目前除了使用list这样的引用传递外我还没发现其他解决方法,留在这里大家一起思考吧。

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函数返回所有的局部变量,但是不会返回嵌套作用域中的变量,实际上没有函数会返回嵌套作用域中的变量。

全局变量(Global variable)

不论是其他普通函数中,还是Class类中,都是可以通过对应的变量名,而可以直接引用的。

局部变量(automatic variable)

对于函数内,包括类Class的函数内,普通的变量,都是自动临时变量

 此处插播背景知识:

变量的优先级关系

普通局部变量和全局变量的作用域,最核心的区别在于:

内部变量的优先级大于外部变量

此处即,(函数内的)局部变量,优先级大于,(外部的)全局变量

换句话说:

A。如果函数内部,出现了和全局变量同名的变量,则说明是局部变量;

B。如果,函数内,没有同名的局部变量的情况下,对应的变量,则是全局变量。

这个变量的优先级关系,不仅针对与Python语言,几乎其他所有语言,都适用,比如C/C++/C#等等。

 所以,上面示例中的__init__中的name,不是全局变量:

name = “whole global name”;

中的name而是,局部变量;

其中,此处是属于那种,Python(和,或其他特殊)语言所特有的,无需声明变量,就可以直接使用的情况。

即局部变量name,并没有声明,但是就直接拿过来使用,通过:

name = newPersionName;

在初始化,设置为对应的名字,此处即”crifan”了。

 类(Class)的变量

类的变量,在类的范围内,只能通过

ClassName.PropertyName

或者说

ClassName.VariableName

去访问,当前类ClassName的变量PropertyName

例子中,就是通过

Person.name

去访问类Person中的变量name的值的。

 示例(Instance)的变量

示例中的变量,理论上,是用

InstanceObject.PropertyName

去访问的。

而此处,由于Python中,默认的,约定俗成的写法,把类的函数的第一个参数的名字,定义为Instance变量,且名字叫做self,所以,就变成了:

self.PropertyName

了。

所以,上述在类的函数中,想要访问Instance的变量的话,就是通过

self.name,去访问类Person的示例self中的变量name了。

 什么是全局变量和局部变量

在函数外,一段代码最始开所赋值的变量,它可以被多个函数引用,这就是全局变量;
在函数内定义的变量名,只能被函数内部引用,不能在函数外引用这个变量名,这个变量的作用域就是局部的,也叫它为局部变量;

如果函数内的变量名与函数外的变量名相同,也不会发生冲突。好比下面这种情况:
x = 100

def func():
    x = 55
x = 100这个赋值语句所创建的变量X,作用域为全局变量;
x = 55这个赋值语句所创建的变量X,它的作用域则为局部变量,只能在函数func()内使用。

尽管这两个变量名是相同的,但它的作用域为它们做了区分。作用域在某种程度上也可以起到防止程序中变量名冲突的作用,但如果做为玩蛇网python初学者来说,尽量避免这种情况发生比较好。

总结

1、变量的作用域由代码被赋值的位置所决定
2、变量可以在3个不同地方,对应3种不同作用域:
(一)一个变量在函数内赋值,它的作用范围被定位在函数之内
(二)当变量是在一个嵌套的函数中赋值时,对于这个嵌套的函数来说,这个变量是非本地的
(三)变量在函数外赋值,它作用域就是当前整个文件的全局变量

时间: 2024-10-26 05:30:18

python 变量作用域详细介绍的相关文章

JavaScript中变量作用域详细介绍

  以下变量具有全局作用域:     1.所有定义在最外层的变量(非函数体内部)具有全局作用域.     2.未定义直接赋值的变量,系统会把它声明为全局作用域.     3.所有window对象的属性具有全局作用域. 以下变量具有函数作用域   1. 在函数体内部用var定义的变量,这里要注意一点,只要是在函数里定义的变量,就算是在最       后一句定义,该变量也拥有整个函数的作用域.但是它的赋值是等到运行到那一句代码以         后才赋值的!!!   特别应该说明的一点是,作用域是层

javascript中的变量作用域以及变量提升详细介绍_javascript技巧

变量作用域"一个变量的作用域表示这个变量存在的上下文.它指定了你可以访问哪些变量以及你是否有权限访问某个变量." 变量作用域分为局部作用域和全局作用域. 局部变量(处于函数级别的作用域)不像其他对面对象的编程语言(比方说C++,Java等等),javascript没有块级作用域(被花括号包围的):当是,javascript有拥有函数级别的作用域,也就是说,在一个函数内定义的变量只能在函数内部访问或者这个函数内部的函数访问(闭包除外,这个我们过几天再写个专题). 函数级别作用域的一个例子

python的变量作用域详细

#定义变量a  代码如下 复制代码 >>> a = 0 >>> print a 0#定义函数p()  代码如下 复制代码 >>> def p(): ...  print a ...   >>> p() 0#定义函数p2()  代码如下 复制代码 >>> def p2(): ...  print a ...  a = 3 ...  print a ...   >>> p2() # 运行出错,外部变量a

php 常量、变量用法详细介绍

变量: 变量用于存储值,比如数字.文本字符串或数组. 一旦设置了某个变量,我们就可以在脚本中重复地使用它. PHP 中的所有变量都是以 $ 符号开始的. 在 PHP 中设置变量的正确方法是:  代码如下 复制代码 $var_name = value; PHP 的入门者往往会忘记在变量的前面的 $ 符号.如果那样做的话,变量将是无效的. 让我们试着创建一个存有字符串的变量,和一个存有数值的变量:  代码如下 复制代码 <?php $txt = "Hello World!"; $nu

Python sys.path详细介绍_python

如何将路径"永久"添加到sys.path? sys.path是python的搜索模块的路径集,是一个list 复制代码 代码如下: ['', 'C:\\WINDOWS\\system32\\python26.zip', 'C:\\Python26\\DLLs', 'C:\\Python26\ \lib', 'C:\\Python26\\lib\\plat-win', 'C:\\Python26\\lib\\lib-tk', 'C:\\Python26 ', 'C:\\Python26\

Python基本数据类型详细介绍_python

1.空(None)表示该值是一个空对象,空值是Python里一个特殊的值,用None表示.None不能理解为0,因为0是有意义的,而None是一个特殊的空值.2.布尔类型(Boolean)在Python中,None.任何数值类型中的0.空字符串"".空元组().空列表[].空字典{}都被当作False,还有自定义类型,如果实现了__nonzero__()或__len__()方法且方法返回0或False,则其实例也被当作False,其他对象均为True布尔值和布尔代数的表示完全一致,一个

Python的异常处理详细介绍

    一旦引发而且没有捕捉SystemExit异常,程序执行就会终止.如果交互式会话遇到一个未被捕捉的SystemExit异常,会话就会终止.     一.异常的捕获     异常的捕获有以下几种方法:     1:使用try和except语句  代码如下 复制代码 try:     block except [exception,[data-]]:     block try:     block except [exception,[data...]]:     block else:  

详细介绍Python中的偏函数_python

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function).要注意,这里的偏函数和数学意义上的偏函数不一样. 在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度.而偏函数也可以做到这一点.举例如下: int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换: >>> int('12345') 12345 但int()函数还提供额外的base参数,默认值为10.如果传入base参数

浅谈php中include文件变量作用域_php实例

在php中我们有时候需要include一个文件.比如我前段时间在写一个框架的时候,打算用原生的php作为模板,然后写一个display方法引入模板文件就可以,但是这只是我的意淫而已. 写完后发现在模板中所有的变量都提示未定义.通过各种研究和查找资料,总结了include文件时的几种情况下的作用域. 第一种情况:A文件include B文件,在B文件中可以调用A中的变量. A文件代码: <?php $aaa = '123'; include "B.php"; B文件代码: <