• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

Python变量及赋值

Python编程 彭东稳 8年前 (2016-05-08) 34236次浏览 已收录 0个评论

一、Python变量及赋值

变量是什么?变量是指在程序运行过程中,值会发生变化的量。变量就是存储在内存中的值,这就意味着在创建变量时会在内存中开辟一个空间。基于变量的数据类型,Python 解释器会分配指定内存,并决定什么数据可以被存储在内存中。因此,变量可以指定不同的数据类型,这些变量可以存储整数,小数或字符。

除了经常听到变量外,可能还会经常听到常量这个词,常量是指在程序运行过程中,值不会发生变化的量。无论是变量还是常量,在创建时都会在内存中开辟一块空间,用于保存它的值。

  • 变量定义

Python 中的变量不需要声明,变量的赋值操作既是变量声明和定义的过程,当我们定义一个变量时,Python 解释器会根据语法和操作数决定对象的类型。如果了解 C 语言的同学应该知道,C 语言中变量的定义需要提前此变量的类型后才可被赋值。每个变量在内存中创建,都包括变量的标识,名称和数据这些信息。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建,且才可以被使用。

变量在程序中就是用一个变量名表示了,变量名必须是大小写英文、数字和_的组合,且不能用数字开头。另外不能使用Python关键字作为变量名。

普通变量赋值

定义一个变量,变量名为 “var”,赋值了一个整数,我们使用 type 方法查看此变量的类型,可以看出是一个整数类型。

此时变量 var 是一个浮点型。

此时变量 var 是一个字符串。

此时变量 var 是一个布尔值。

在 Python 中,等号 “=” 是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,如上操作就是把一个变量进行了多次赋值,以最后一次赋值为最终结果。

这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配就会报错;比如 C 语言和 Go 都是静态语言,赋值语句如下:

和静态语言相比,动态语言更灵活,就是这个原因。另外请不要把赋值语句的等号等同于数学的等号。比如下面的代码:

如果从数学上理解 x = x + 2 那无论如何是不成立的,在程序中,赋值语句先计算右侧的表达式 x + 2,得到结果 12,再赋给变量 x。由于 x 之前的值是 10,重新赋值后,x 的值变成 12。

  • 变量赋值过程

Python 中的变量赋值,不是真正意义上的修改内存的值,而是将变量名和在内存中的值做了一个绑定,称之为“引用”。

当我们创建一个变量时:

这时,Python 解释器干了两件事情:

  1. 在内存中创建了一个对象 1
  2. 在内存中创建了一个名为 a 的变量并指向对象 1

也可以把一个变量 a 赋值给另一个变量 b,这个操作实际上是把变量 b 指向变量 a 所指向的数据,例如下面的代码:

这里要注意,Python 里的对象可以被多个变量所指向或引用。

再看下面这个示例:

这里首先将 1 赋值于 a,即 a 指向了 1 这个对象。接着 b = a 则表示,让变量 b 也同时指向 1 这个对象。

最后执行 a = a + 1。需要注意的是,Python 的数据类型,例如整型(int)、字符串(string)等等,是不可变的。所以,a = a + 1,并不是让 a 的值增加 1,而是表示重新创建了一个新的值为 2 的对象,并让 a 指向它。但是 b 仍然不变,仍然指向 1 这个对象。

因此,最后的结果是,a 的值变成了 2,而 b 的值不变仍然是 1。

通过这个例子你可以看到,这里的 a 和 b,开始只是两个指向同一个对象的变量而已,或者你也可以把它们想象成同一个对象的两个名字。简单的赋值 b = a,并不表示重新创建了新对象,只是让同一个对象被多个变量指向或引用。

同时,指向同一个对象,也并不意味着两个变量就被绑定到了一起。如果你给其中一个变量重新赋值,并不会影响其他变量的值。

明白了这个基本的变量赋值例子,我们再来看一个列表的例子:

同样的,我们首先让列表 l1 和 l2 同时指向了 [1, 2, 3] 这个对象。

由于列表是可变的,所以 l1.append(4) 不会创建新的列表,只是在原列表的末尾插入了元素 4,变成 [1, 2, 3, 4]。由于 l1 和 l2 同时指向这个列表,所以列表的变化会同时反映在 l1 和 l2 这两个变量上,那么,l1 和 l2 的值就同时变为了 [1, 2, 3, 4]。

另外,需要注意的是,Python 里的变量可以被删除,但是对象无法被删除。比如下面的代码:

del l 删除了 l 这个变量,从此以后你无法访问 l,但是对象 [1, 2, 3] 仍然存在。Python 程序运行时,其自带的垃圾回收系统会跟踪每个对象的引用。如果 [1, 2, 3] 除了 l 外,还在其他地方被引用,那就不会被回收,反之则会被回收。

由此可见,在 Python 中:

  • 变量的赋值,只是表示让变量指向了某个对象,并不表示拷贝对象给变量;而一个对象,可以被多个变量所指向。
  • 可变对象(列表,字典,集合等等)的改变,会影响所有指向该对象的变量。
  • 对于不可变对象(字符串,整型,元祖等等),所有指向该对象的变量的值总是一样的,也不会改变。但是通过某些操作(+= 等等)更新不可变对象的值时,会返回一个新的对象。
  • 变量可以被删除,但是对象无法被删除。

对象引用的优缺点:

  • 优点,节省内存空间,多个变量可以引用同一个对象。
  • 缺点,如果修改变量的变量值在内存中不存在,需要重新申请内存,绑定变量名和地址,降低了执行效率。

我们看一下下面代码的赋值过程:

当你创建一个变量 x,值等于 2 时;Python 解释器会先找内存中有没有2的值,如果有直接做绑定,然后2这个值得引用计数加1即可。如果没有那么 Python 解释器就需要开辟一段内存空间,创建一个2的值,我们使用 id(x) 可以看出变量 x 当前引用的内存地址。同样变量 y 也是这样的操作。

此时,我们如果修改变量 x = 3,那么 Python 解释器就会把变量 x 的与内存地址 “32771128” 做绑定,然后把对象 3 的引用加 1,如下操作:

那么此时,内存中的整数值 “2” 就没有被引用了,当 Python 解释器检查到 “2” 的引用为 0 时就会启动内存回收机制把此内存空间给释放掉了。

不过,需要注意,对于整型数字来说,以上结论只适用于 -5 到 256 范围内的数字。比如下面这个例子:

这里我们把 257 同时赋值给了 a 和 b,可以看到 a == b 仍然返回 True,因为 a 和 b 指向的值相等。但奇怪的是,a is b 返回了 false,并且我们发现,a 和 b 的 ID 不一样了,这是为什么呢?

事实上,出于对性能优化的考虑,Python 内部会对 -5 到 256 的整型维持一个数组,起到一个缓存的作用。这样,每次你试图创建一个 -5 到 256 范围内的整型数字时,Python 都会从这个数组中返回相对应的引用,而不是重新开辟一块新的内存空间。

但是,如果整型数字超过了这个范围,比如上述例子中的 257,Python 则会为两个 257 开辟两块内存区域,因此 a 和 b 的 ID 不一样。

二、Python解释器内部机制

  • Python引用计数机制

要保持追踪内存中的对象,Python 使用了引用计数这一简单计数。也就是说 Python 内部记录着所有使用中的对象各有多少引用。一个内部跟踪变量也称为引用计数器。每个对象各有多少个引用简称引用计数;当对象被创建时,就创建了一个引用计数,当这个对象不再需要时,也就是说这个对象的引用计数变为 0 时它会被当垃圾回收。

  • Python垃圾回收机制

Python 中不再使用的内存会被一种称为垃圾收集的机制释放。像上面说的,虽然解释器跟踪对象的引用计数,但垃圾收集器负责释放内存。垃圾收集器是一块独立代码,它用来寻找引用计数为 0 的对象。它也负责检查哪些虽然应用计数大于 0 但也应该被销毁的对象。特定情形会导致循环引用。

三、变量赋值的方法

1. 普通变量赋值

2. 增量赋值

3. 多重赋值

4. 多元赋值

5. 分解赋值

元祖和列表分解赋值时当赋值符号(*)的左侧为元祖或列表时、Python 会按照位置把右边的对象和左边的目标自左而右逐一进行配对;个数不同时会触发异常,此时可以切片的方式进行.

四、变量的作用域

作用域简单说就是一个变量的命名空间。代码中变量被赋值的位置,就决定了哪些范围的对象可以访问这个变量,这个范围就是命名空间。Python 赋值时生成了变量名,当然作用域也包括在内。

Python 变量作用域遵循 LEGB 原则:

  • L (Local) 局部作用域
  • E (Enclosing) 闭包函数外的函数中
  • G (Global) 全局作用域
  • B (Built-in) 内建作用域

以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。

def/class/lambda内定义的变量名,只能被函数内部引用,不能在函数外引用这个变量名,这个变量的作用域就是局部的,也叫它为局部变量。def/class/lambda外,一段代码最始开所赋值的变量,它可以被多个函数引用,这就是全局变量。局部作用域会覆盖全局作用域,但不会影响全局作用域。

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

x = 100 这个赋值语句所创建的变量 X,作用域为全局变量。

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

尽管这两个变量名是相同的,但它的作用域为它们做了区分。作用域在某种程度上也可以起到防止程序中变量名冲突的作用。

每次对函数的调用都会创建一个新的本地作用域,赋值的变量除非使用global申明为全局变量否则均为本地变量

内置的__builtin__模块提供

<参考>

Python 变量作用域


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (10)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!