AdvancePython-python中变量的本质、可变性及垃圾回收

一、变量不是盒子

学过java的人都知道,java中的变量可以当成盒子,变量使用前必须声明,才能进行存储空间分配

而在python中,我们最好把它理解为‘便利贴’

示例:

a = [1,2,3]
b = a
b.append(4)
print(a) #[1, 2, 3, 4]  b a 贴在了同一个对象上面
print(a is b)  #true 是同一个对象,id相同

在Python中,先创建对象,再进行赋值,所以我们说把变量分配给对象,而不是把对象分配给变量

people = People() #一切皆对象 类本身也是对象,全局中只存在一个
if type(people) is People:
    print("yes")

结果当然为yes,这是因为在Python中一切为对象,类本身也是对象,在全局中只存在一个这样的对象 

二、元组的相对不可变性

元组与多数Python集合(列表,字典等)一样,保存的是对象的引用。如果引用的元素是可变的,即便元组本身不可变,元素依然可变。也就是说,元组的不可变性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。

示例:一开始,t1 t2相等,但是修改t1中的一个可变元素后,二者不相等了

t1 = (1,2,[3,4])
t2 = (1,2,[3,4])
print(t1 == t2) #True t1 t2 是两个不同的对象,但值相等
print(t1 is t2)  #False 不同的对象,id自然不同

print(id(t1[-1])) #2919174080328  查看t1[-1]列表的标识

t1[-1].append(99) #就地修改t1[-1]列表
print(t1)  #(1, 2, [3, 4, 99])

print(id(t1[-1])) #2919174080328 t1[-1]的标识没变,只是值变了

print(t1 == t2) #False 现在,t1 t2值不再相等

三、函数的参数作为引用时

Python唯一支持的参数传递模式为共享传参,多数面向对象语言都采用这一模式。共享传参指函数的各个形式参数获得实参中各个引用的副本。也就是说,函数内部的形参是实参的别名。

扫描二维码关注公众号,回复: 9236084 查看本文章

这种机制的结果是,函数可能会修改作为参数传入的可变对象,但是无法修改那些对象的标识(即不能把一个对象替换成另外一个对象)。

下面的例子中,分别把数字、列表、元组传递给函数,实际传入的实参会以不同的方式受到影响:

def add(a,b):
    a +=b
    return a

a = 1
b = 2
print(add(a,b)) #3
print(a,b)#1,2

a = [1,2]
b = [3,4]
#参数类型为list时,a的值收到了影响
print(add(a,b)) #[1, 2, 3, 4]
print(a,b)#[1, 2, 3, 4] [3, 4]

a = (1,2)
b = (3,4)
print(add(a,b)) #(1, 2, 3, 4)
print(a,b)#(1, 2) (3, 4)

对+=或*=之类所做的增量赋值来说,如果左边变量绑定的是不可变对象,会创建新对象;如果是可变对象,会就地修改。

所以,不要使用可变类型作为参数的默认值

举个例子:

#举个例子
class Company:
    def __init__(self,name,staffs=[]):
        self.name = name
        self.staffs = staffs
    def add(self,staff_name):
        self.staffs.append(staff_name)
    def remove(self,staff_name):
        self.staffs.remove(staff_name)

if __name__ == '__main__':
    com1 = Company('com1',['hobby1','hobby2'])
    com1.add('hobby3')
    com1.remove('hobby1')
    print(com1.staffs)#['hobby2', 'hobby3']

    com2 = Company('com2')
    com2.add('hobby4')
    print(com2.staffs)#['hobby4']
    print(Company.__init__.__defaults__) #(['hobby4'],)

    com3 = Company('com3')
    com3.add('hobby5')
    print(com2.staffs)#['hobby4', 'hobby5']
    print(com3.staffs)#['hobby4', 'hobby5']
    print(com2.staffs is com3.staffs) #true
    #因为我们传递进来的是一个列表,即可变对象,我们没有给com2 com3的staffs赋值,所以com2 com3共用了同一个staffs,该staffs为空列表
    #尽量不传递list因为其可修改

本例中,如果没传入staffs参数,则使用默认绑定的列表对象,一开始是空列表,

self.staffs = staffs

这个赋值语句把self.staffs变成了staffs的别名,而没有传入staffs参数时,后者又是默认列表的别名。

在self.staffs上调用remove()和append()方法时,修改的其实是默认列表,它是函数对象的一个属性。

四、del 和垃圾回收

del语句指删除名称,而不是对象。del命令可能会导致对象被当做垃圾回收,但是仅当删除的变量保存的是对象的最后1个引用,或者无法得到对象时。重新绑定也可能会导致对象的引用数量归零,导致对象被销毁。

在Cpython中,垃圾回收使用的主要算法是引用计数,实际上,每个对象都会统计有多少引用指向自己。当引用计数归零时,对象就被销毁:Cpython会在对象上调用__del__方法(如果定义了),然后释放分配给对象的内存。

a = 1
b = a #此时1上面有两个变量指向了它
del a #将引用计数器减一,只有当减到0时,Python回收机制回收
#和java不同,java中的del只是进行回收,而Python会对计数器减一

a = object()
b = a
del a #注意delete(引用计数器)与垃圾回收的区别
print(b)
#print(a)#NameError: name 'a' is not defined

我们可以在自己的类中定义__del__魔法函数,当del的时候,就可以执行相应的处理:

class A:
    #实际上我们可以自己定义魔法函数来进行delete,当del对象的时候,就可以使用这里面的逻辑
    def __del__(self):
        pass
发布了16 篇原创文章 · 获赞 4 · 访问量 1780

猜你喜欢

转载自blog.csdn.net/qq_40509206/article/details/103647724
今日推荐