2.3 变量和常量
变量是计算机内存中的一块区域,变量可以存储任何值,而且值可以改变。常量是一块只读的内存区域,常量一旦初始化就不能修改。
2.3.1 变量的命名
变量由字母、数字或下划线组成。变量的第1个字符必须是字母或下划线,其他字符可以由字母、数字或下划线组成。例如:
01 # 正确的变量命名 02 var_1=1 03 print (var_1) 04 _var1=2 05 print( _var1)
【代码说明】
❑第2行代码定义了一个名为var_1的变量,该变量的初始值为1。这个变量以字母开头,后面的字符由字母、下划线和数字组成。
❑第3行代码输出结果如下。
1
❑第4行代码定义了一个名为_var1的变量,该变量的初始值为2。这个变量以下划线开头,后面的字符由字母和数字组成。
❑第5行代码输出结果如下。
2
下面这段代码演示了错误的变量命名方式。
01 # 错误的变量命名 02 1_var=3 03 print (1_var) 04 $var=4 05 print ($var)
【代码说明】
❑第2行代码定义了一个名为1_var的变量,该变量以数字开头,后面的字符由字母、下划线组成。
❑第3行代码,变量以数字开头,不符合变量命名的规则。提示如下错误:
SyntaxError: invalid syntax
❑第4行代码定义了一个名为$var的变量,该变量以$符号开头。
❑第5行代码,变量以$符号开头,不符合变量命名的规则。提示如下错误:
SyntaxError: invalid syntax
2.3.2 变量的赋值
Python中的变量不需要声明,变量的赋值操作即是变量声明和定义的过程。每个变量在内存中创建,都包括变量的标识、名称和数据这些信息。例如:
x=1
上面的代码创建了一个变量x,并且赋值为1,如图2-1所示。
Python中一次新的赋值,将创建一个新的变量。即使变量的名称相同,变量的标识并不相同。下面的代码演示了Python的变量声明以及赋值操作。
图2-1 变量的内部结构
01 # 一次新的赋值操作,将创建一个新的变量 02 x=1 03 print (id(x)) 04 x=2 05 print( id(x))
【代码说明】
❑第2行代码定义了一个名为x的变量,该变量的初始值为1。
❑第3行代码,输出变量x的标识。输出结果如下。
11229424
❑第4行代码再次定义了一个x的变量,该变量的初始值为2。该变量与前面的变量x并不是同一变量。
❑第5行代码,输出变量x的标识。输出结果如下。
11229412
如果变量没有赋值,Python将认为该变量不存在。例如:
print y
运行后,解释器提示:
NameError: name 'y' is not defined
在变量y没有赋值的前提下,不能直接输出y的值。每个变量在使用前都必须赋值,这样可以避免由于变量的空值引起的一些异常。Python支持对一些变量同时赋值的操作,例如:
01 # 给多个变量赋值 02 a=(1, 2, 3) 03 (x, y, z)=a 04 print( "x =", x) 05 print( "y =", y) 06 print( "z =", z)
【代码说明】
❑第2行代码定义了一个序列a,这个序列有3个值:1、2、3。
❑第3行代码,把序列a的值分别赋值给序列(x, y, z)中的变量x、y、z。
❑第4行代码输出变量x的值。输出结果:
x=1
❑第5行代码输出变量y的值。输出结果:
y=2
❑第6行代码输出变量z的值。输出结果:
z=3
通过序列的装包和拆包操作,实现了同时给多个变量赋值。关于序列的概念参见第4章的内容。
2.3.3 局部变量
局部变量是只能在函数或代码段内使用的变量。函数或代码段一旦结束,局部变量的生命周期也就结束。局部变量的作用范围只在其被创建的函数内有效。例如,文件1的fun()中定义了一个局部变量,则该局部变量只能被fun()访问,而不能被fun2()访问,也不能被文件2访问,如图2-2所示。
下面定义了一个函数fun(),该函数中定义了一个局部变量。
01 # 局部变量 02 def fun(): 03+local=1 04+print(local) 05 fun()
【代码说明】
❑第2行代码定义了一个函数fun()。
❑第3行代码定义了一个局部变量local。
❑第4行代码输出local的值。输出结果如下。
1
❑第5行代码调用函数fun()。此时已超出local变量的作用范围。
注意 Python创建的变量就是一个对象,Python会管理变量的生命周期。Python对变量的回收采用的是垃圾回收机制。
2.3.4 全局变量
全局变量是能够被不同的函数、类或文件共享的变量,在函数之外定义的变量都可以称为全局变量。全局变量可以被文件内部的任何函数和外部文件访问。例如,如果文件1中定义了一个全局变量,文件1中的函数fun()可以访问该全局变量。此外,该全局变量也能被文件1、文件2访问,如图2-3所示。
图2-2 局部变量的作用范围
图2-3 全局变量的作用范围
全局变量通常在文件的开始处定义。下面定义了两个全局变量_a、_b和两个函数add()、sub(),这两个函数将调用全局变量执行加法和减法计算。
01 # 在文件的开头定义全局变量 02 _a=1 03 _b=2 04 def add(): 05+global _a 06+_a=3 07+return "_a+_b =", _a+_b 08 def sub(): 09+global _b 10+_b=4 11+return "_a-_b =", _a-_b 12 print (add()) 13 print( sub())
【代码说明】
❑第2行代码定义了一个名为_a的全局变量,这个变量的作用范围从定义处到文件的结尾。之所以使用下划线是为了区分于其他变量,引起程序员对全局变量出现的重视。
❑第3行代码定义了一个名为_b的全局变量。同样,变量_b的作用范围从定义处到文件的结尾。
❑第4行代码定义了一个函数add(),用于执行加法计算。
❑第5行代码引用全局变量_a。这里使用了global关键字,global用于引用全局变量。
❑第6行代码对全局变量_a重新赋值。
❑第7行代码返回_a+_b的值。
❑第8行代码定义了一个函数sub(),用于执行减法运算。函数内的实现方式和add()相同。
❑第12代码调用函数add()。输出结果如下。
('_a+_b =', 5)
❑第13行代码调用函数sub()。输出结果如下。
('_a-_b =', -1)
如果不使用global关键字引用全局变量,而直接对_a、_b赋值,将得到不正确的结果。
01 # 错误地使用全局变量 02 _a=1 03 _b=2 04 def add(): 05+_a=3 06+return "_a+_b =", _a+_b 07 def sub(): 08+_b=4 09+return "_a-_b =", _a-_b 10 print (add()) 11 print (sub())
【代码说明】
❑第5行代码中的_a并不是前面定义的全局变量,而是函数add()中的局部变量。虽然输出的结果相同,但是运算的对象并不相同。
❑第6行代码中的_b还是前面定义的全局变量_b。
❑第8行代码中的_b是局部变量。
❑第10行代码的输出结果如下。
('_a+_b =', 5)
❑第11行代码的输出结果如下。
('_a-_b =', -3)
注意 变量名相同的两个变量可能并不是同一个变量,变量的名称只是起标识的作用。变量出现的位置不同,变量的含义也不同。
同样可以把全局变量放到一个专门的文件中,便于统一管理和修改。创建一个名为gl.py的文件。
01 # 全局变量 02 _a=1 03 _b=2
【代码说明】pl.py创建了两个全局变量_a和_b。
再创建一个调用全局变量的文件use_global.py。
01 # 调用全局变量 02 import gl 03 def fun(): 04+print(gl._a) 05+print(gl._b) 06 fun()
【代码说明】
❑第2行代码导入前面创建的文件gl.py,即模块gl。
❑第3行代码定义了一个函数fun(),该函数调用全局变量_a和_b。这里不需要使用global引用gl.py中的全局变量,因为前导符可以定位全局变量_a和_b。
❑第4行代码输出_a的值,使用前导符gl定位。输出结果:
1
❑第5行代码输出_b的值,使用前导符gl定位。输出结果:
2
❑第6行代码调用fun()。
应该尽量避免使用全局变量。因为不同的模块都可以自由地访问全局变量,可能会导致全局变量的不可预知性。对于gl.py中的全局变量,如果程序员甲修改了_a的值,程序员乙同时也要使用_a,这时可能导致程序中的错误。这种错误是很难发现和更正的。
全局变量降低了函数或模块之间的通用性,不同的函数或模块都要依赖于全局变量。同样,全局变量降低了代码的可读性,阅读者可能并不知道调用的某个变量是全局变量。
2.3.5 常量
常量是指一旦初始化后就不能改变的变量。例如,数字5、字符串“abc”都是常量。C++中使用const关键字指定常量,Java使用static和final关键字指定常量,而Python并没有提供定义常量的关键字。Python是一门功能强大的语言,可以自己定义一个常量类来实现常量的功能。在《Python Cookbook》一书中定义了一个常量模块const。
01 class _const:+# 定义常量类_const 02+class ConstError(TypeError): pass+# 继承自TypeError 03+def __setattr__(self,name,value): 04+if self.__dict__.has_key(name):+# 如果__dict__中不包含对应的key则抛出错误 05+raise self.ConstError, "Can't rebind const(%s)"%name 06+self.__dict__[name]=value 07 import sys 08 sys.modules[__name__]=_const()+# 将const注册进sys.modules的全局dict中
【代码说明】
❑这个类定义了一个方法__setattr__()和一个异常类型ConstError,ConstError类继承自TypeError。通过调用类自带的字典__dict__,判断定义的常量是否包含在字典中。如果字典中包含此常量,将抛出异常。否则,给新创建的常量赋值。
❑最后两行代码的作用是把const类注册到sys.modules这个全局字典中。
以下代码在use_const.py中调用const,定义常量。
01 import const 02 const.magic=23 03 const.magic=33
【代码说明】
❑第1行代码导入const模块。
❑第2行代码定义了一个常量magic。
❑第3行代码修改常量magic的值,抛出异常。
const.ConstError: Can't rebind const(magic)