有关python变量的6个概念,一次说透

1. 变量不能独立存在

在C++等语言中,变量的声明和赋值是可以分开的

int a;
a = 343;

而在python中却不行,在声明python变量的同时必须进行赋值操作

a = 343

如果你直接使用一个不存在的变量,就会发生错误,NameError: name 'b' is not defined

2. 变量是内存中数据的引用

 

a = 343 这样代码被执行时,首先要在内存中创建出343这个对象,然后让a指向它,这便是引用。

此后,我们在程序中使用变量a时,其实都是在使用343,python可以通过a找到343, 这是对引用最通俗的解释。

赋值语句执行过程中,有一点极容易被忽略掉,那就是这个过程中,在内存中创建了新的数据

a = [1]

b = [1]

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

两行赋值语句,分别将列表[1] 赋值给a 和 b,表达式 a == b 的结果是Ture,因为他们的内容的确相同,但表达式a is b的结果是False, 因为这两行赋值语句执行过程中,一共创建了两个列表,他们只是内容相同而已,但内存地址绝对不相同,下图是这两个变量的内存描述示意图

3. 变量的内存地址与类型

3.1 内存地址与类型

下面的代码会输出变量的内存地址与类型

a = 343
print(id(a))
print(type(a))

程序输出为

4325858928
<class 'int'

虽然标题是“变量的内存地址和类型”,但从本质上来说,内存地址是343的内存地址,类型是343的类型。

3.2 改变指向

如果将a 的指向改变,即将另外一个值赋值给a, 那两行print输出的内容就会发生变化

a = 343
print(id(a))
print(type(a))

print('改变a的指向')
a = 'python'
print(id(a))
print(type(a))

程序输出

4325858928
<class 'int'>
改变a的指向
4339812760
<class 'str'>

这究竟是怎么一回事呢,下图展示了变量a改变指向的过程

变量a最终指向了字符串'python', 而343由于没有变量指向它,因此引用数量从1变成了0。

python的变量,总是要指向一个内存中的数据才行,因此,它不能孤立的存在。

3.3 多个变量指向同一个数据

a = 343
b = a

print(id(a))
print(id(b))

程序输出为

4325858928
4325858928

变量a的内存地址和变量b的内存地址一样,下图是这种情况的示意图

4. 引用数量

当一个数据没有变量指向它时,这个数据的引用数量就变成了0,python会销毁掉这种对象,这就是GC(垃圾回收),你可以通过sys.getrefcount() 方法查看一个数据的引用数量。

下面这段代码,一定要在python交互式解释器下执行,因为其他环境下,会做优化处理,导致实际结果与理论结果不符。

Python 3.6.3 (v3.6.3:2c5fed86e0, Oct  3 2017, 00:32:08)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> a = 343
>>> b = a
>>> sys.getrefcount(a)
3
  • a 指向了343,引用次数加1
  • b 也指向了343,引用次数加1
  • a作为实参传入getrefcount函数中,会进行一次参数复制,引用次数加1

所以,最终343的引用数量是是3

5. 可变对象与不可变对象

可变对象包括列表,集合,字典
不可变对象包括int,float,bool, str, tuple

所谓不可变,是指对象的内容不能发生变化,就像3.2 小节中的示意图所展示的那样,变量a的值虽然发生变化了,但那仅仅是a的指向发生了变化,343 和字符串'python'一旦创建了,就不能在发生任何改变,他们的值永远都是343,永远是'python',直到引用数量降为0时,被GC回收,他们所占的那块内存区域会被重新分配使用。

所谓可变,并不是对象的内存地址发生变化,而是对象的内容发生了变化。

a = [1]
a.append(2)

print(a)

上面的代码执行时,内存中究竟是怎样变化的呢?

注意看,a所指向的内存地址没有发生变化,但是列表里的数据发生变化了,这就叫可变对象

6. 深拷贝与浅拷贝

关于深拷贝和浅拷贝,我们主要从是否生成新的对象这个层面来进行比较。

6.1 浅拷贝

拷贝规则如下

  • 如果被拷贝对象是不可变对象,则不会生成新的对象
  • 如果被拷贝对象是可变对象,则会生成新的对象,但是只会对可变对象最外层进行拷贝
import copy


a = 4343.23
b = copy.copy(a)

print(id(a))
print(id(b))

上面的代码对一个float类型的数据进行了浅拷贝,根据规则,不会生成新的对象,因此a,b两个变量的内存地址是相同的。

下面是一个可变对象的拷贝示例

import copy

a = [1, [1]]
b = copy.copy(a)

print(id(a), id(b))
print(id(a[1]), id(b[1]))

程序输出结果是

4739120648 4387519880
4739160904 4739160904

a和b的内存地址是不相同的,说明生成了一个新的数据,但由于是浅拷贝,因此列表里的元素并不进行拷贝,只对最外层进行了拷贝。

通过对内存的观察,我们可以更清楚了解浅拷贝的过程,下图是浅拷贝之前的内存示意图

浅拷贝发生之后,内存变成如下图所示

为了便于识别,我特地将代表引用的线条加粗并加上颜色来区分,通过对比浅拷贝前后的示意图,你可以看到,仅仅生成了一个新的对象,地址是4350709128。

a[1], b[1] 的数据类型是列表,是可变对象,他们的内存地址相同,因此,对b[1]的操作,将会影响到a[1]

import copy

a = [1, [1]]
b = copy.copy(a)

b[1].append(2)
print(a)

程序输出结果

[1, [1, 2]]

明明只是对变量b进行了操作,却影响到了a,这绝对是个安全隐患,因此进行浅拷贝时要非常小心,除非你清楚的知道自己在做什么,可能带来哪些影响,否则就不要进行浅拷贝,现在不缺内存,别玩火。

6.2 深拷贝

拷贝规则

  • 如果被拷贝对象是不可变对象,深拷贝不会生成新对象,因为被拷贝对象是不可变的,继续用原来的那个,不会产生什么坏的影响
  • 如果被拷贝对象是可变对象,那么会彻底的创建出一个和被拷贝对象一模一样的新对象
import copy

a = [1, [1]]
b = copy.deepcopy(a)

print(id(a), id(b))
print(id(a[1]), id(b[1]))

程序输出结果

4739124744 4350819720
4739165000 4739236104

为了清晰的理解深拷贝的作用,还是放上拷贝前后的内存对比图

深拷贝前

深拷贝之后

和浅拷贝相比,最大的不同在于,新生成了一个列表[1],内存地址和a[1]不一样,深拷贝之后,对b的任何操作,都不会影响到a,虽然多耗费了一些内存,但是更加安全

 

个人微信公众号,关注我,获得python学习资源,探索python前沿技术

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

酷python

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值