从Python的对象说起
一、Python对象
学过Python的肯定都听过一句话:“一切皆对象”,那这句话是啥意思呢?
在Python中,我们都知道数据类型有数字、字符串、元组、列表、字典、集合等,还包括函数、方法、类等,这些都是对象,甚至包括Python模块(py)等等都可以称为对象。
object的概念在Python中非常常见,比如PyIntObject,PyStringObject,PyFunctionObject等等。那么对象有什么特点呢?
对象并不一定是像我们理解的称类是对象,即含有属性和方法,Python对象不一定有属性和方法,但Python对象都可以赋值给变量,也可以当做参数传递给函数或类。作为Python对象,它们有三个特点:
- 值 对象肯定都有一个value,比如2;
- id 对象都会有一个ID,唯一标识自己,表示了在内存中的位置 id(2)可查看到;
- 类型 对象都有一个特定的类型,可通过type(2)查看。
每一个对象都会有两个标准的头部信息:
- 类型标识符;标志对象的类型;
- 引用计数器;代表目前引用该对象的只恨数目,用来决定是否回收这个对象,这个和Python的垃圾回收机制有关,当计数为0时,就会回收。
二、赋值和引用
说完对象,就要说下Python的赋值和引用了。先看一下下面的代码:
a = 3
a = 1.2
a = 'd'
a = [1,32]
看到了吗?它没有声明变量,而且竟然赋了不同类型的值。这段代码在C语言中或者Java中是行不通的,而python中却可以这样灵活应用。这就和它的一切皆对象有关了。
对于这句a = 3,Python主要做了以下几步:
- 创建一个对象来代表值 3;
- 创建一个变量a;
- 建立一个指针,从变量a指向对象3;
变量名a——>对象3。
看到了吧,在Python中,a = 3并不是像C语言中那样,“a就是代表整数类型,a和3拥有同样的内存地址”,python中的a只是一个变量,它和对象拥有不同的内存地址,当a=3执行时,只是创建了一个从变量到对象的一个连接。在内部,变量就是一个指向对象内存地址的指针。
这个在Python中叫做“引用”,引用就是自动形成的从变量到对象的指针。而我们在谈论数据类型的时候,并不是针对变量而言,都是针对对象而言的。
再说下Python的共享引用。共享引用就是多个变量引用同一个对象。
a = 3
b = a
上面就是一个共享引用的例子,变量a和b都指向了对象3.你通过id查看,值是一样的。再看一个例子:
a = 3
b = a
a = 1
print a,b
>> 1,3
上面当执行a=1后,a指向了新的对象1,而b仍然指向对象3。再看一个:
a = [1,2]
b = a
a[0]=3
print a,b
>> [3,2] [3,2]
a=[1,4]
print a,b
>> [1,4] [3,2]
列表[1,2]是一个对象,但里面的元素也是对象,也就是说1也是对象。当我们改变a[0]时,就是改变了第一个元素要引用的对象。那么a指向的对象改变了,b的值也同样会改变;
如果你想改变a时,b的值不变,那么就需要使用拷贝,即创建对象的一个拷贝,和原来的对象拥有不同的内存空间。Python中可以用分片,copy或deepcopy。
再看下面的,当执行a=[1,4]时,实际上是又新创建了一个对象[1,4],这样a就指向了新的对象,b的值就不会改变;
共享引用和相等
先拿代码:
a=[1,2]
b=[1,2]
print a==b
print a is b
>>True False
a=1.1
b=1.1
print a is b
>>False
a=1
b=1
print a is b
>>True
上面的列表,浮点数是两个对象,is是比较是否引用不同的对象,因此有false;下面的整数却是True,说不通啊。其实这和Python机制有关,Python会缓存并复用小的整数或者字符串。
函数参数引用:
a ,b = [1,2], 4
def test(a,b):
a[0] = 3
b = 2
print a,b
test(a,b)
print a,b
>>[3,2] 2
>>[3,2] 4
Python函数的传递只是传递对象的引用,而这种说法和其他语言的传值和传址说法侧重点不同。对象引用可能因为结果的不同表现为传值和传址的特点。
看上面的代码,变量a的值变了,变量b的值没变。这就是传址和传值两种表现形式。那为什么会出现这种情况呢?除了Python的对象和引用之外,还得说另外一个,Python的LEGB准则,函数结束后,生命周期也结束,详细可看Python官网。
a改变了,b没变。这是因为,函数内,改变了a所指的对象(列表),那么即使生命周期结束了,全局变量a还是指向同一个对象啊,所以这里的结果是a变了。
再看b,函数内重新赋值,函数的局部变量b指向了新的对象:2,等函数结束后,局部变量生命周期结束了,全局变量b还是指向变量b。
也就是说,对于可变对象,我们可以理解为传址;对于不可变对象,就是传址。但他们都是对象的引用。再解释一下传址和传值:传值传入的参数是不会改变的,用传址传入就会改变。
上面的例子应该明白了吧。
微信分享/微信扫码阅读