python中变量,对象和引用详解

    python这个语言和别的语言有一个很大的不同,那就是python的变量。如果你学过C/C++或者java,你会发现python变量没有类型修饰符,一般我们学习其他语言,我们定义一个变量,会像下面这样:

int a = 0;

 上面这个语句定义了一个int类型的变量a,并将这个变量赋值为0,如果是python,那么会有如下的定义:

a = 0

很明显,这条语句也是定义了一个变量a,并且将a指向对象0,这样的说明似乎和上面有点不同。

没错,就是因为这点不同,引出了python一个很核心的概念:对象。

    什么是对象?

    python中万物皆对象,什么意思呢?一个字符‘a’叫对象,一个整数1叫对象,一个类叫对象,一个类的实例也叫对象,一个函数也叫对象。既然万物都是对象,我们可以将任何类型的对象通过引用赋值给变量,所以我们会有a=函数,a=类,a=字符串等等这样的赋值操作。

    上面的代码a=0,这个语句的执行,首先python解释器会在内存中建立一个整数对象,值为0,然后将变量a通过引用链接到对象0。听起来有点奇怪,但是事实上就是如此,a变量和对象0通过引用关联,这个引用你可以认为是变量a存放了对象的地址,变量a类似C语言中指针,但是这个指针没有类型,它可以指向任何对象,所以你可以认为变量是void类型的指针。

     这时候我们明确另外一个概念,变量。变量是没有类型的,它可以指向任何类型的对象,对象是有类型的,比如整型,字符型。

    变量和对象通过=(引用)关联,我们通过变量来操作对象。

    看一下下面的代码:

a = [1,2,3] 
a = “abc”

这个变量a第一次指向了一个列表对象,第二次指向了字符串对象。那么背后有什么别的事情发生么?这就引出了下一个话题,垃圾回收。在java虚拟机,我们应该经常听到垃圾回收,python中也同样有垃圾回收的概念,python和java垃圾回收的具体实现可能不同,但是基本的理论是一样的,那就是如果这个对象没有被引用,则该对象会被回收,释放内存。

垃圾回收:

    上面的第一条语句,在内存中分配了一片空间存放列表[1,2,3], 并将变量a 指向该列表。第二条语句,在内存中分配了一片空间存放字符串"abc",并将变量a指向该字符串。这时候因为变量a存放的是字符串的引用,对于列表对象[1,2,3] 已经没有别的变量或者对象引用了,所以很快被垃圾回收,这块内存会被释放。和java一样,垃圾回收是自动调用的,一般情况下,我们不需要主动调用垃圾回收函数。如果想要查看这个对象的引用计数,可以通过 sys.getrefcount()来查看,有兴趣的同学可以试试。

可变对象和不可变对象

    在python中,对象分为可变对象和不可变对象。看看下面这个例子:

a = 0

我们可以改变对象0的值么?答案是否定的。我们没有任何办法去改变对象0的值, 试试a = 1呢,这个改变了变量a的引用,并没有改变对象0的值,a=1使得python解释器又新创建了一个对象1。所以对于整型对象,一旦创建,我们根本没有办法改变它的值,所以该对象就为不可变的对象,数字,字符串,元组都是不可变对象,可变的对象有列表,字段,集合。

我们看一些代码示例:

1,字符串

a = 'abc'
b = a 
a = 'bcd'

这三行代码运行后,b的值变了没有?答案是没有变,因为字符串‘abc’是不可变的对象,我们只是将a的改为指向新字符串的位置,而b的引用还是指向字符串“abc”。假如将第三个语句改为a[0]='d' 会怎样?python解释器会报错,因为字符串是不可变得对象,该代码企图改变该字符串的第一个字符。

再看看垃圾回收。这个代码的第二行运行后,对于字符串对象‘abc’,变量a和变量b都引用了它,所以此时它不会被垃圾回收,第三行a引用了新的对象“bcd”,这时候对象‘abc’的引用计数变成了1,同样不回被回收。

再看一个字符串的例子:

a = ‘abc’
a = ‘abc’+‘bcd’

上面的列子,python解释器分配了几个对象呢?答案是三个。第一个对象是‘abc’,第二个对象是‘bcd’,第三个对象是‘abcbdc’。对象‘abc’和‘bcd’都是不可变对象,它们两个相加产生了一个新对象‘abcbdc’。因为最后a引用的对象‘abcbdc’,对象‘abc’和‘bcd’的引用为0,所以被垃圾回收了。

2, 列表

a = [1,2,3]
b = a
a[0]=2

这三行代码执行后,b的值变了么?答案是肯定的,因为列表是可变对象,支持原地修改,所以我们通过变量可以直接操纵修改对象,最后b和a引用的列表都变成了[2,2,3],其中第三行的代码将列表的第一个值修改成了2。

对象的比较:

    对于对象来讲,对象的比较有两种,一种是对象的值的比较,还有一种是对象是否为同一个对象。对于对象的值的比较,自定义的类型的对象,我们可以自定义比较的函数来比较值,当然也可以使用内置的比较方式,但是可能得不到们想要的结果。对于内置类型对象的比较,python有一套规则,一般情况下都是适用的。在这篇文章我不想谈的太过于深入,只是来谈一下对象在内存中的处理方式,所以我用字符串和列表对象来说明这个问题,同时只选取==和is这两个运算符来举例,要不然,又将是一篇很长的文章,写文章真的是很耗时的事情,而且文章太长,看的也累。

    好了,言归正传,请看下面的代码:

a = “123”
b = “123”

print(a==b)
print(a is b)

c = [1,2,3]
d = [1,2,3]

print(c==d)
print(c is d)

上面我们说到==只是对象的值的比较,所以我们得出a==b 和 c==d的结果都是True。那么a is b和c is d的结果呢?不知道你猜对了没有,a is b 是True,但是c is d 是False。是不是有点惊讶,如果你不惊讶,那说明你已经懂了python如何处理可变对象和不可变对象。接下来我们来说明原因。

    当第一个语句a = “123”执行时,python解释器创建了一个字符串对象“123”,然后将变量a引用该对象,第二个语句执行b = “123”时,python解释器发现已经有了一个值同样是“123”的字符串对象,python解释器便不再创建相同的对象,直接将b引用已有的对象“123“。为什么这么做呢,之前我们谈到了字符串是不可变对象,所以你无法改变字符串的值,既然b也是指向相同值的字符串,那么python解释器也没必要浪费资源再创建一个相同的对象。因为通过引用a不能改变对象”abc“的值,所以不存在b在不知情的情况下,引用对象的值被改变的风险。

    再看c = [1,2,3],这时候python解释器新建一个列表对象[1,2,3],并用变量c引用该列表对象,然后再执行d = [1,2,3],这时候python解释器再新建一个列表对象[1,2,3],并用变量d引用该对象,所以此时内存中存在两个列表对象,他们的值时相同的。为什么python解释器的行为和刚才字符串的处理不一样了呢?那是因为列表是可变对象,python器为了防止c在d不知情的情况下修改了对象,所以就会为d新建同样值的对象。如果你想让通过c修改对象同样也要影响到b,那么就应该把代码改成如下:

c = [1,2,3]
d = c

print(c==d)
print(c is d)

通过d = c,表明d要和c引用同一个对象,这样解释器就不会再为d重新分配空间。

对于不可变对象,下面的代码是一样的效果:

a = “123”
b = “123”

print(a==b)
print(a is b)

#下面的代码和上面的代码是一样的效果
a = “123”
b = a

print(a==b)
print(a is b)


    在这里特别说明一下,元组也是不可变对象,他虽然和列表很像,但是因为是不可变对像,所以对于上面的代码如果换成是元组,那么解释器的行为和对待字符串是一样的,有兴趣的同学可以试一下。

    至此,我们通过上面的学习,明白了变量和对象是不一样的,变量没有类型,而对象有类型,变量和对象通过引用关联。通过引用变量就可以操作对象,获得对象的值,修改对象的值,甚至是删除对象。对象是有生命周期的,如果对象没有被引用,那么这个对象将被垃圾回收器回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白快快跑哦

您的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值