安全区研究院

相关阅读

Pythonhttps://blog.csdn.net/weixin_45791458/category_12403403.html?spm=1001.2014.3001.5482

在Python编程中,我们经常会遇到各种赋值操作,无论是简单的变量赋值,还是复杂的数据结构操作。表面上看,赋值就是把一个值“赋予”给另一个变量,但其实Python中的赋值并不像很多人理解的那样简单。赋值的本质其实是“引用”,这个概念在理解Python的内存管理和性能优化时尤为关键。

赋值的行为:引用还是拷贝?

当我们在Python中进行赋值操作时,赋值号=的作用是让一个标识符引用某个对象,而不是像C语言那样是将值保存在标识符代表的内存空间中,这也解释了为什么Python无需定义数据类型。

# 例1

a = [1, 2, 3]

b = a

如例1所示,在执行第一行代码时,首先会创建一个列表对象,随后用标识符a引用该对象。在执行第二行代码时,由于赋值号=右边是一个简单的标识符a,这代表标识符b也引用了列表对象(没有新对象创建)。

可以使用sys包种的getrefcount函数获得一个标识符指向的对象被几个标识符引用,如例2所示。

# 例2

import sys

a = [1, 2, 3]

print(sys.getrefcount(a)) # 输出:2

b = a

print(sys.getrefcount(b)) # 输出:3

第一个输出为2是因为在函数调用传参(可以理解为赋值)时,内部的形参也引用了列表对象。

使用内建函数id可以获得一个标识符指向对象的内存地址,例3显示两个标识符指向对象的地址是相同的,即指向的是同一个对象。

# 例3

a = [1, 2, 3]

b = a

print(id(a)) # 输出:2825757347904

print(id(b)) # 输出:2825757347904

当通过标识符a或标识符b改变列表对象时(例如添加元素),同时会影响到另一个标识符(因为对象相同),如例4所示。

# 例4

a = [1, 2, 3]

b = a

b.append(4)

print(a) # 输出:[1, 2, 3, 4]

print(b) # 输出:[1, 2, 3, 4]

print(id(a)) # 输出:2248917209152

print(id(b)) # 输出:2248917209152

但是有些情况下,赋值操作却会导致新对象的创建,如例5所示。

# 例4

a = [1, 2, 3]

b = a

b = "Test"

print(a) # 输出:[1, 2, 3, 4]

print(b) # 输出:Test

print(id(a)) # 输出:1714054847296

print(id(b)) # 输出:1714054845040

要搞明白这点,首先得了解一些概念,可变对象与不可变对象。

可变对象

可变对象是那些在创建之后可以通过标识符修改其属性或内容的对象,常见的可变对象包括:

列表

可以添加、删除、修改元素。

a = [1, 2, 3]

a.append(4) # 列表的内容改变

print(a) # 输出:[1, 2, 3, 4]

字典

可以增删键值对。

d = {'a': 1, 'b': 2}

d['c'] = 3 # 字典的内容改变

print(d) # 输出:{'a': 1, 'b': 2, 'c': 3}

集合

可以添加或删除元素。

d = {'a': 1, 'b': 2}

d['c'] = 3 # 字典的内容改变

print(d) # 输出:{'a': 1, 'b': 2, 'c': 3}

字节数组

可以修改其中的字节。

b = bytearray(b'abc')

b[0] = 100 # 修改字节

print(b) # 输出:bytearray(b'dbc')

部分类实例

class Person:

def __init__(self, name, age):

self.name = name

self.age = age

# 创建一个 Person 类的实例

p = Person("Alice", 25)

# 修改实例的属性

p.age = 26

print(p.age) # 输出:26

不可变对象

不可变对象在创建之后无法通过标识符修改其属性或内容的对象(这不代表该类对象没有属性,只是无法修改),常见的不可变对象包括:

整数

x = 10

浮点数

x = 3.14

字符串

s = "hello"

元组

t = (1, 2, 3)

冻结集合

d = frozenset([1, 2, 3])

字节串

b = b'abc'

回到正题,当一个标识符已经引用了一个可变对象,且赋值号左侧是利用某些语法改变该对象的属性或内容时(例如列表索引、切片),此时不会创建新的对象。对于其他情况,比如一个标识符引用了一个不可变对象,或一个标识符已经引用了一个可变对象但赋值号左侧只有该标识符时(表示并不是改变该对象的属性或内容),此时则会将标识符引用赋值号右侧的另一个对象(这就可能导致新对象创建)

例5展示了一些赋值行为背后的本质。

# 例5

a = [1, 2, 3]

b = a # 列表对象有两个引用

a[0] = 2 # 利用标识符a改变可变列表对象的内容

b = "Test" # 标识符b不再引用列表对象,而是引用一个新建的字符串对象

b = a # 标识符b不再引用字符串对象,而是引用回之前的列表对象

当一个对象没有任何标识符引用时会被自动回收,这很好理解,因为如果没有任何标识符能引用该对象,则无法再访问到该对象,自然它也就没有存在的意义了,例5中的字符串对象“Test”只有标识符b引用,因此在执行b = a后,字符串对象没有任何标识符引用了,因此会被回收。