6.3 我是风儿 我是沙
1.动态类型
动态类型(Dynamic Typing)是Python的另一个重要核心概念。前面说过,Python的变量不需要声明。在赋值时,变量可以重新赋值为其他任意值。Python变量这种一会儿变风一会儿变沙的能力,就是动态类型的体现。我们从最简单的赋值语句入手:
a = 1
在Python中,整数1是一个对象。对象的名字是"a"。但更精确地说,对象名其实是指向对象的一个引用。对象是存储在内存中的实体。但我们并不能直接接触到该对象。对象名是指向这一对象的引用(reference)。借着引用操作对象,就像是用筷子夹起热锅里的牛肉。对象是牛肉,对象名就是那双好用的筷子。
通过内置函数id(),我们能查看到引用指向的是哪个对象。这个函数能返回对象的编号:
a = 1
print(id(1))
print(id(a))
可以看到,赋值之后,对象1和引用a返回的编号相同。
在Python中,赋值其实就是用对象名这个筷子去夹其他的食物。每次赋值时,我们让左侧的引用指向右侧的对象。引用能随时指向一个新的对象:
a = 3
print(id(a))
a = "at"
print(id(a))
第一个语句中,3是储存在内存中的一个整数对象。通过赋值,引用a指向对象3。第二个语句中,内存中建立对象"at",是一个字符串。引用a指向了"at"。通过两次的id()返回,我们能发现,引用指向的对象发生了变化。既然变量名是个随时可以变更指向的引用,那么它的类型自然可以在程序中动态变化。因此,Python是一门动态类型的语言。
一个类可以有多个相等的对象。比如两个长字符串可以是不同的对象,但它们的值可以相等。
除了直接打印id外,我们还可以用is运算来判断两个引用是否指向同一个对象。但对于小的整数和短字符串来说,Python会缓存这些对象,而不是频繁地建立和销毁它们。因此,下面的两个引用指向同一个整数对象3。
a = 3
b = 3
print(a is b) # 打印True
2.可变与不可变对象
一个对象可以有多个引用,看下面一个例子:
a = 5
print(id(a))
b = a
print(id(a))
print(id(b))
a = a + 2
print(id(a))
print(id(7))
print(id(b))
通过前两个语句,我们让a、b指向同一个整数对象5。其中,b = a的含义是让引用b指向引用a所指的那一个对象。我们接下来对对象进行操作,让a增加2,再赋值给a。可以看到,a指向了整数对象7,而b依然指向对象5。本质上,加法操作并没有改变对象5。相反,Python只是让a指向加法的结果—另一个对象7。这就好像把老人变成少女的魔术,其实老人和少女都没有变化。只不过是让少女站在老人的舞台上。在这里,变的只是引用的指向。改变一个引用,并不会影响其他引用的指向。从效果上看,就是各个引用各自独立,互不影响。
但注意以下情况:
list2 = [1,2,3]
list1 = list2
list1[0] = 10
print(list2)
在我们改变list1时,list2的内容发生了改变。引用之间似乎失去了独立性。其实这并不矛盾。因为list1、list2的指向没有发生变化,依然是同一个列表。但列表是一个包含了多个引用的集合。每个元素是一个引用,比如list1[0]、list1[1]等。每个引用又指向一个对象,比如1、2、3。而list1[0] = 10这一赋值操作,并不是改变list1的指向,而是对list1[0]。也就是说,列表对象的一部分,即一个元素的指向发生了变化。因此,所有指向该列表对象的引用都受到影响。
因此,在操作列表时,如果通过元素引用改变了某个元素,那么列表对象自身会发生改变(in-place change)。列表这种自身能发生改变的对象,称为可变对象(Mutable Object)。我们之前见过的词典也是可变数据对象。但之前的整数、浮点数和字符串,则不能改变对象本身。赋值最多只能改变引用的指向。这种对象称为不可变对象(Immutable Object)。元组包含多个元素,但这些元素完全不可以进行赋值,所以也是不可变数据对象。
3.从动态类型看函数的参数传递
函数的参数传递,本质上传递的是引用,比如:
def f(x):
print(id(x))
x = 100
print(id(x))
a = 1
print(id(a))
f(a)
print(a) # 通过打印出的第二行,可以看到id发生了变化
参数x是一个新的引用。当我们调用函数f时,a作为数据传递给函数,因此x会指向a所指的对象,也就是进行一次赋值操作。如果a是不可变对象,那么引用a和x之间相互独立,即对参数x的操作不会影响引用a。
如果传递的是可变对象,那么情况就发生了变化:
def f(x):
x[0] = 100
print(x)
a = [1,2,3]
f(a)
print(a) # 打印[100, 2, 3]
上面的函数中,a指向一个可变的列表。在函数调用时,a把指向传给了参数x。这时,a和x两个引用都指向了同一个可变的列表。根据前文介绍我们知道,通过一个引用操作可变对象,会影响到其他的引用。程序运行的结果同样说明了这一点。打印a时,结果变成了[100,2,3]。即函数内部对列表的操作,会被外部的引用a“看到”。编程的时候要对此问题留心。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论