ljzsdut
GitHubToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

03 变量

类型属于对象,而不是变量

​ 在Python中,变量并不需要预声明,变量会在第一次赋值时创建,任何类型的对象都可以赋值给一个变量,但是变量一旦被赋值,其对应的类型就已经确定了。

​ 需要注意的是:虽然变量不需要提前声明,但是在使用变量之前必须对其赋值以完成变量的创建。实际上,在对变量使用之前,一般将变量赋值成空对象。(变量是没有类型的,对象才有类型。变量的类型指的是变量指向的对象的类型。)

​ Python中,变量是动态类型的,变量不需要预声明,变量的类型是动态的,它自动地跟踪指向对象的类型。所以,在任何时刻,在需要时,某个变量都可以重新引用一个不同的对象,并且可以是不同的数据类型的对象。

​ 但是Python也是强类型语言(对象存在不同是数据类型,你只能对一个对象进行适合该类型的有效的操作,而bash是弱类型语言,所有的对象都是字符串类型)。

​ 变量名没有类型,类型属于对象,而不是变量,例如我们可以对同一个变量先后赋值不同类型的对象。这跟C语言中不同(c中变量是有类型的,只能赋值相关类型的对象给变量)

变量命名原则

  • 只能包含字母、数字和下划线,且不能数字开头;
  • 区分大小写;
  • 禁止使用保留字(关键字)(python2与python3保留字不同)

变量命名惯例

  • 以单一下划线开头的变量名(_x)不会被from module import语句导入。
  • 前后都有双下划线的变量名(__x__)是系统定义的变量名,对python解释器有特殊意义。
  • 前双下划线的变量名(__x)是类的本地变量,在类的内部使用;类外部无法调用_
  • 交互模式下,变量名为"_"的变量,用于保持最后表达式的结果。

注意:变量名没有类型,对象才有,变量引用的对象才有,在Python中变量可以引用任意类型的对象。变量名只是内存引用的标识而已。

变量赋值与对象引用

对象引用

​ Python中一切皆对象。python将所有数据都存储为内存对象(有内存地址),各种内存对象在不被引用时(引用计数为0时),会自动地由垃圾收集器回收。

​ python中,变量本质是一个指针指向了对象的内存空间(引用=指针=内存地址)。

img

​ 上图表示a=3的赋值过程,做如下说明:

  • 变量和对象保存在内存中的不同部分,并通过连接来关联它们(这个连接在图显示为一个箭头)。在Python中从变量到对象的连接称作引用。也就是说,引用是一种关系,以内存中的指针的形式实现。 对象是分配的一块内存,要有足够的空间去表示它们所代表的值。 引用是自动形成的从变量到对象的指针。

  • “=”是赋值操作,用于将变量名与内存中的某对象进行绑定(连接);如果对象事先存在,就直接绑定;否则,则由“=”创建引用对象。

  • 变量总是连接到对象,并且绝不会连接到其他变量上,但是对象可能连接到其他的对象(例如,一个列表对象能够连接到它所包含的对象)。

从概念上讲,在脚本中,每一次通过运行一个表达式生成一个新的值,Python都创建了一个新的对象(换言之,一块内存)去表示这个值。

但是Python从内部做了一种优化,Python缓存了不变的对象并对其进行复用,例如,小的整数和字符串(每一个0都不是一块真正的、新的内存块)。但是,从逻辑的角度看,这工作起来就像每一个表达式结果的值都是一个不同的对象,而每一个对象都是不同的内存。

从技术上来讲,对象有更复杂的结构而不仅仅是有足够的空间表示它的值那么简单。每一个对象都有两个标准的头部信息:一个类型标志符去标识这个对象的类型,以及一个引用计数器,用来决定是不是可以回收这个对象。

共享引用

Python中,赋值操作总是储存对象的引用,而不是这些对象的拷贝。例如多个变量名引用了同一个对象。

>>> a=3
>>> b=a
>>> id(a)
15105080
>>> id(b)
15105080

1. 共享引用之不可变对象:对于不可变对象,通过其中一个变量名对对象的修改不会影响其他变量的值。

img

img

2. 共享引用之可变对象(原处修改):对于可变对象,通过其中一个变量名对对象的修改会将对象在原处修改,从此影响其他变量的值,即所有变量的值都发生改变。对于可变变量,可以使用序列的列表机制或模块的深复制来解决这个问题,如b=a[:]赋值,会产生a列表的以个副本来赋值给变量b(分片会产生新的对象)。

img

说明列表的原处修改修改的是元素的引用。

在原处修改可变对象时可能会影响程序中其他地方对相同对象的其他引用,如果要避免出现这种情况,需要明确地告诉Python复制该对象,复制对象的方法有如下:

  • 没有限制条件的分片表达式(L[:])能够复制序列。

  • 字典copy方法(X.copy())能够复制字典。

  • 有些内置函数(例如,list)能够生成拷贝(list(L))。

  • copy标准库模块能够生成完整拷贝。copy.deepcopy(list1)、copy.copy(list1)

拷贝需要注意的是:无条件值的分片以及字典copy方法只能做顶层复制。也就是说,不能够复制嵌套的数据结构(如果有的话)。如果你需要一个深层嵌套的数据结构的完整的、完全独立的拷贝,那么就要使用标准的copy模块,copy.deepcopy(list1)

对象的深拷贝和浅拷贝

使用copy模块实现对象的深浅拷贝。

1、 对于"数字"和"字符串"而言,赋值、浅拷贝和深拷贝无意义,因为其永远指向同一个内存地址。

img

2、 列表、字典、集合

  • 赋值:赋值操作总是储存对象的引用(包括函数的参数传递也是赋值操作)

img

  • 浅拷贝:只对第一层进行拷贝,copy.copy()

img

  • 深拷贝:对所有层进行拷贝,copy.deepcopy()。(其实,对于不可变对象(数字和字符串)不会进行拷贝,因为修改一个不可变对象,引用、深浅拷贝修改操作的效果是一样的,都会进行拷贝操作)

img

>>> l=[11,22,33]
>>> l1=[1,3,5]
>>> l1.append(l)
>>> import copy
>>> l2 = copy.deepcopy(l1)
>>>
>>> l1
[1, 3, 5, [11, 22, 33]]
>>> l2
[1, 3, 5, [11, 22, 33]]
>>>
>>> id(l1[3][0])  
4539923680
>>> id(l2[3][0])
4539923680
>>>
>>> id(l1[0])
4539923360
>>> id(l2[0])
4539923360
>>>
>>> id(l1[3])
4543663632
>>> id(l2[3])
4543685648