Python 程序设计讲义(62):从内存的角度分析 Python 的变量与对象

Python 程序设计讲义(62):从内存的角度分析 Python 的变量与对象

一、Python的变量、对象和引用

Python中需要明确三个概念:变量,引用,对象。

1、变量

python中的变量简而言之就是指针,变量存放的是对象的引用,或者说存放的是对象的地址,可以使用函数id()查看对象的地址。

(1)任何变量所需要的存储空间大小相同,与所指向的对象类型无关,因为任何变量都只是保存指向对象的内存地址。

(2)变量没有类型(变量都是对象的引用,变量的类型其实是变量指向的数据的类型),这意味着变量可以指向任何对象。

(3)变量分配在栈内存中,用来指向某一个对象。

(4)变量分为可变变量和不可变变量。

2、对象

对象就是分配的一块内存+存储的值。对象有id(地址),type(类型)和value(值)。

(1)对象分配在堆内存中,用来存放真正的数据。

(2)对象有类型,不同对象有不同的类型,如strint等。

(3)不同类型的对象根据存储的数据大小不同,所占用的内存空间也不同。

(4)python中一切皆对象,一个列表、值、函数、类等都可以成为对象。

3、引用

引用是建立变量和对象之间的关系,即某个变量指向了某个对象,二者之间的关系便是引用。

因为变量是无类型的,它可以指向任何对象。同一个对象可能被多个变量引用(指向),因此会涉及一个对象引用计数,回收机制,以及引用同一个对象的不同变量之间的操作影响关系。

4、变量,对象和引用之间的关系

变量,对象和引用之间的关系如下图所示:

在这里插入图片描述

当执行代码str1='hello world'时,python解释器做了以下三件事情:

(1)创建一个变量,名称为str1

(2)创建一个对象(字符串类型)并开辟一块存储空间,该对象的id0x020typestr,存储的valuehello world

(3)将变量和对象通过指针链接起来。从变量到对象的连接,称为引用。

例如:

print(str1)
print('变量str1的地址:',id(str1))
print('变量str2的地址:',id(str2))
print('字符串Hello Python的地址:',id("Hello Python"))
print('变量str1的类型:',type(str1))
print('字符串Hello Python的类型:',type("Hello Python"))

str2="Python"
print('变量str1的地址:',id(str1))
print('变量str2的地址:',id(str2))
print('字符串Python的地址:',id("Python"))

程序的执行结果为:
Hello Python
变量str1的地址: 1975302064560
变量str2的地址: 1975302064560
字符串Hello Python的地址: 1975302064560
变量str1的类型: <class 'str'>
字符串Hello Python的类型: <class 'str'>
变量str1的地址: 1975302064560
变量str2的地址: 1975301640128
字符串Python的地址: 1975301640128

如果变量引用的是一个组合对象(如:列表,元组,集合,字典等),则该变量指向组合对象,组合对象中的每个元素又指向其他对象。如下图所示:

在这里插入图片描述

可以使用如下代码查看变量的引用情况:

l1=[80,90]
l2=['张静',l1,'xinxiang']
print(l2)
print('列表l2的地址:',id(l2))
print('列表l2中第1个元素的地址:',id(l2[0]))
print('字符串"张静"的地址:',id("张静"))
print('列表l2中第2个元素的地址:',id(l2[1]))
print('列表l1的地址:',id(l1))
print('列表l2中第3个元素的地址:',id(l2[2]))
print('字符串"xinxiang"的地址:',id("xinxiang"))

程序的执行结果为:
['张静', [80, 90], 'xinxiang']
列表l2的地址: 2261667505856
列表l2中第1个元素的地址: 2261673852032
字符串"张静"的地址: 2261673852032
列表l2中第2个元素的地址: 2261667653376
列表l1的地址: 2261667653376
列表l2中第3个元素的地址: 2261671059696
字符串"xinxiang"的地址: 2261671059696

python的变量本质是一个指针,变量不需要指定类型(变量本来就没有类型),它可以指向任意类型的对象。称为动态类型机制,任何时候根据需要,变量都可以重新绑定在另一个不同类型的对象上。

当一个对象不存在任何引用时,就通过垃圾回收机制被回收。

而在强类型语言(如C语言)中,创建一个变量就是开辟一块存储空间,并且给该空间命名,这个名字就是变量。因此在强类型语言中,定义变量时都需要指定变量类型。

二、可变对象与不可变对象

1、不可变对象

一旦创建后不允许被修改的对象称为不可变对象。包括:整数(int)、浮点数(float)、字符串(str)、元组(tuple)等。这些对象的共同特点是一旦创建,它们的内存中的数据不可改变。

当修改变量时,Python会创建一个新的对象,并让变量引用新的对象,而不会修改原对象。如下图所示:

在这里插入图片描述

验证:

(1)字符串

str1 = "Hello"
str2 = str1.replace('H', 'J')  # 更改字符串的内容,其实是生成一个新字符串,赋值给str2
print(str1) 
print('str1的地址:', id(str1))
print(str2) 
print('str2的地址:', id(str2))

程序的运行结果为:
Hello
str1的地址: 2644748157744
Jello
str2的地址: 2644750983376

(2)数值

x = 10
y = x
print('x=',x)
print('变量x的地址:', id(x))
print('y=',y)
print('变量y的地址:', id(y))
x = x + 1  #修改x的内容,其实是让x执行一个新对象
print('x=',x)
print('变量x的地址:', id(x))
print('y=',y)
print('变量y的地址:', id(y))

程序的运行结果为:
x= 10
变量x的地址: 140708086826184
y= 10
变量y的地址: 140708086826184
x= 11
变量x的地址: 140708086826216
y= 10
变量y的地址: 140708086826184

(3)元组

不能直接更改元组中的元素。如果尝试修改元组中的某个元素,Python 将抛出一个错误。

例如:

tup = (1, 2, 3)
tup[0] = 100  # 尝试修改元组的第一个元素,将抛出一个错误

程序的运行结果为:
Traceback (most recent call last):
  File "C:\Users\wgx58\PycharmProjects\PythonProject1\hello.py", line 2, in <module>
    tup[0] = 100  # 尝试修改元组的第一个元素,将抛出一个错误
    ~~~^^^
TypeError: 'tuple' object does not support item assignment

说明:尽管元组本身是不可变的,但如果元组中包含可变对象,如列表,这些可变对象内部的内容是可以被修改的。这种情况下,元组的不可变性只适用于顶层结构,不影响内部元素的可变性。

例如:

t1 = (1, [2, 3], 4)
t1[1][1]=20
print(t1)  # 输出 (1, [2, 20], 4)

程序的运行结果为:
(1, [2, 20], 4)

上述的修改过程可使用下图表示:

在这里插入图片描述

2、可变对象

创建后可以改变的对象称为可变对象。包括:列表(list)、字典(dict)、集合(set)等。

当通过一个变量修改对象时,所有引用该对象的变量都会受到影响。如下图所示:

在这里插入图片描述

验证:

(1)列表

l1 = [1, 2, 3]
l2=l1
print('l1的地址:',id(l1))
print('l2的地址:',id(l2))

l1[1]=20
print(l1)  # 输出 [1, 20, 3],列表内容被修改
print(l2)  # 输出 [1, 20, 3],因为l2仍然指向原来的列表
print('l1的地址:',id(l1))
print('l2的地址:',id(l2))

程序的运行结果为:
l1的地址: 2309674149632
l2的地址: 2309674149632
[1, 20, 3]
[1, 20, 3]
l1的地址: 2309674149632
l2的地址: 2309674149632

(2)字典

d1 = {1:"tom", 2:"jack", 3:"mark"}
d2=d1
print('d1的地址:',id(d1))
print('d2的地址:',id(d2))

d1[1]="black"
print(d1)  # 输出 {1:"black", 2:"jack", 3:"mark"},列表内容被修改
print(d2)  # 输出 {1:"black", 2:"jack", 3:"mark"},因为d2仍然指向原来的字典
print('d1的地址:',id(d1))
print('d2的地址:',id(d2))

程序的运行结果为:
d1的地址: 2389055546368
d2的地址: 2389055546368
{1: 'black', 2: 'jack', 3: 'mark'}
{1: 'black', 2: 'jack', 3: 'mark'}
d1的地址: 2389055546368
d2的地址: 2389055546368

(3)集合

尽管集合是可变的,但也有一些操作上的限制。集合只能包含不可变(可哈希)的对象。因此不能将列表或另一个集合作为元素添加到集合中。

s1={1,2,3}
s2=s1
print('s1的地址:',id(s1))
print('s2的地址:',id(s2))

s1.add(4)
print(s1)  # 输出{1, 2, 3, 4},列表内容被修改
print(s2)  # {1, 2, 3, 4},因为d2仍然指向原来的集合
print('s1的地址:',id(s1))
print('s2的地址:',id(s2))

程序的运行结果为:
s1的地址: 2326100593120
s2的地址: 2326100593120
{1, 2, 3, 4}
{1, 2, 3, 4}
s1的地址: 2326100593120
s2的地址: 2326100593120

三、变量的删除

Python 提供了 del 语句来删除变量,使其不再指向任何对象。

例如:

x = 10
del x  # 删除变量 x
print(x)  #抛出变量未定义错误

程序的运行结果为:
Traceback (most recent call last):
  File "C:\Users\wgx58\PycharmProjects\PythonProject1\hello.py", line 3, in <module>
    print(x)  #抛出变量不存在错误
          ^
NameError: name 'x' is not defined

四、函数的参数传递问题

Python中参数传递方式:按引用传递,实际是对象的引用地址传递。

1、不可变对象的传递

向函数传递不可变对象时,对该对象的任何修改都不会影响原始对象,因为实际上是创建了一个新的对象传递给函数的形参。

例如:

def update_number(n):
    n=n*2
    return n
x=5
print(update_number(x))   # 把对象5传递给实参,x不受影响。输出10
print(x)  # 输出5

程序的运行结果为:
10
5
2、可变对象的传递

传递可变对象时,函数内对对象的修改将影响原始对象。因为形参和实参指向同一个对象。

例如:

def update_list(x):
    x[1]=60
l1=["张平",85]
update_list(l1)   # l1和x指向同一个列表对象
#列表l1的元素发生变化时,应为l1仍然指向该列表。因此:l1的内容发生变化
print(l1) 

程序的运行结果为:
['张平', 60]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

睿思达DBA_WGX

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

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

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

打赏作者

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

抵扣说明:

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

余额充值