脚本专家 发表于 6 天前

Python全局变量与局部变量作用域及NameError解析

在 Python 里讨论全局变量和局部变量,最容易踩坑的不是概念本身,而是函数、类和方法中的名字查找顺序。原文的核心结论很直接:局部变量通常只能在定义它的函数内部访问,全局变量可以在模块级直接访问;一旦函数内部对某个名字赋值,这个名字在函数内就会被当作局部变量处理,从而影响读取顺序和报错类型。下面按原文示例,从类变量访问、真正全局变量、同名优先级、UnboundLocalError 和 global 几个场景梳理。

一、类中的变量不能在方法里直接访问
原文先给出一个类变量示例。类中定义 a,方法中定义 b,如果方法里直接 print(a),编译器会报错,因为 a 是类变量,不是方法内的局部变量,也不是全局变量。错误写法:

class variable:
    a = '我是类变量'
    def showvarible(self):
      b = '我是函数变量'
      print(a)
      print(b)
variable().showvarible()

要访问类变量,可以用类名 variable.a,也可以通过 self.a。两种写法的结果一致:

class variable:
    a = '我是类变量'
    def showvarible(self):
      b = '我是函数变量'
      print(variable.a)
      print(b)
variable().showvarible()


class variable:
    a = '我是类变量'
    def showvarible(self):
      b = '我是函数变量'
      print(self.a)
      print(b)
variable().showvarible()

输出为:
我是类变量
我是函数变量

原文还演示了通过构造函数给实例属性赋值:

class variable:
    def __init__(self, a):
      self.a = '我是类变量'
    def showvarible(self):
      b = '我是函数变量'
      print(self.a)
      print(b)
variable(1).showvarible()

这里需要注意,实例化时必须传入参数,因为 __init__ 定义了参数 a。Python 是动态语言,不需要指定参数类型,传 int 如 1 可以,传字符串也可以。这个例子里的 self.a 被赋值为字符串,但参数本身没有类型限制。

二、真正的全局变量和函数局部变量
原文给出真正的全局变量示例:a 定义在模块级,函数内部可以读取;b 定义在函数内部,属于局部变量:

a = '我是真正的全局变量'
def showvariable():
    b = '我一直都是局部变量'
    print(a)
    print(b)
showvariable()

输出为:
我是真正的全局变量
我一直都是局部变量

如果试图在函数外访问函数内的局部变量 b,就会触发 NameError:

a = '我是真正的全局变量'
def showvariable():
    b = '我一直都是局部变量'
    print(a)
def showb():
    print(b)
showvariable()

典型报错是:
我是真正的全局变量
Traceback (most recent call last):
File 'F:/leetcode/xxx.py', line 13, in <module>
    showb()
File 'F:/leetcode/xxx.py', line 9, in showb
    print(b)
NameError: name 'b' is not defined
这说明 b 只属于 showvariable 的局部作用域,函数执行结束后不能在外部继续使用。

三、同名时局部变量优先,函数内赋值会导致 UnboundLocalError
当全局变量和函数内局部变量同名时,函数内部优先使用局部变量:

a = '我是真正的全局变量'
def showvariable():
    a = '我一直都是局部变量'
    print(a)
print(a)
showvariable()

输出为:
我是真正的全局变量
我一直都是局部变量
也就是模块级先打印全局 a,调用 showvariable 后打印的是函数内局部 a。

更隐蔽的情况是函数内先读取、后赋值:

a = 3
def showvariable():
    a = a * 5
    print(a)
showvariable()

这段代码不会输出 15,而是抛出:
Traceback (most recent call last):
...
UnboundLocalError: local variable 'a' referenced before assignment
原因不是全局变量不能参与运算,而是函数内部出现了 a = ... 这样的赋值语句,Python 会把 a 视为局部变量。局部变量 a 在参与 a * 5 之前还没有被赋值,所以报 UnboundLocalError。

四、global 声明和赋值顺序
如果确实要在函数内部操作全局变量,需要用 global 声明:

def showvariable():
    global a
    a = '我是global'
    print(a)
showvariable()

输出:
我是global

声明后,这个 a 就能在函数外访问:

def showvariable():
    global a
    a = '我是global'
    print(a)
def showglobal():
    print(a)
showvariable()
showglobal()

输出:
我是global
我是global

但 global 只是声明,不等于赋值。原文特别强调,必须赋值后才有意义。如果还没有执行赋值就去打印,仍然会报 NameError:

def showvariable():
    global a
    a = '我是global'
    print(a)
def showglobal():
    print(a)
print(a)
showvariable()
showglobal()

报错为:
Traceback (most recent call last):
...
NameError: name 'a' is not defined
因为此时 global a 还没有完成赋值,模块级 print(a) 访问不到已定义的 a。

五、排查与总结
从这些示例可以归纳出几条实用判断:局部变量只能在函数内部访问;全局变量可以在模块级直接访问,函数内默认可读;函数内对同名变量赋值时,会优先按局部变量处理;要在函数内修改全局变量,需要先写 global 声明,并确保赋值发生在读取之前;类变量不要直接当全局变量或局部变量使用,应通过类名或 self 访问;实例化带 __init__ 参数的类时,必须按形参传入实参。

遇到 NameError: name 'xxx' is not defined,先检查变量是否在当前位置的作用域内,以及 global 后是否已经赋值。遇到 UnboundLocalError: local variable 'xxx' referenced before assignment,通常要检查函数内部是否在后面写了赋值语句,导致该变量被判定为局部变量。把作用域和赋值顺序理清,这类 Python 变量问题就能定位。

热心网友6 发表于 6 天前

Re: Python全局变量与局部变量作用域及NameError解析

这篇整理挺实用的,特别是把 UnboundLocalError 单独拎出来讲。很多人看到 a = a * 5 报错,会以为全局变量不能参与运算,其实关键就是函数里出现了对 a 的赋值,Python 在整个函数作用域内都把 a 当局部变量,赋值前读取自然就炸了。类变量那块也容易混,方法里裸写 print(a) 不行,得用 self.a 或类名.a;而 global 更像是先声明后赋值,声明本身不会创建可用变量,顺序反了或者模块级提前访问还是会 NameError。看完最大的收获就是:函数内有没有赋值,基本决定了这个名字在函数里的身份。

热心网友2 发表于 6 天前

Re: Python全局变量与局部变量作用域及NameError解析

整理得很清楚,几个容易混的点基本都覆盖了。我的理解就是:函数里只要出现对某个名字的赋值,整个函数内就会把它当局部变量看,所以先读取后赋值才会报 UnboundLocalError,而不是因为全局变量不能参与运算。类变量那块也是,方法里直接写 print(a) 不行,得用 self.a 或者类名.a。 global 那段也很关键,声明不等于赋值,必须真的执行到赋值那一步,函数外才能访问;光 global 一下然后直接在外面 print,还是会 NameError。最后一段好像没贴完,不过不影响前面例子的理解。感谢分享,这种按报错类型来梳理的方式挺适合复习。

热心网友4 发表于 6 天前

Re: Python全局变量与局部变量作用域及NameError解析

看完楼主这篇梳理,作用域这几个坑确实一下就串起来了。类变量在方法里不能直接写变量名访问,得用类名或 self;模块级全局变量函数内可以读,但函数内局部变量出了函数就 NameError;同名时局部优先,而且只要函数内部有赋值,整个函数里这个名字就会按局部变量处理,所以才会出现 UnboundLocalError。global 那部分也提醒得很到位,声明只是告诉 Python 要操作模块级名字,真正要让它在外面可见,还得先执行到赋值,不然照样 NameError。例子很直观,尤其把“能读”和“能在外部访问”区分开了,后面写脚本遇到类似报错可以按这个顺序排查。
页: [1]
查看完整版本: Python全局变量与局部变量作用域及NameError解析