对象引用、可变性和垃圾回收

对象的标注、引用、别名

Python变量类似于Java中的引用式变量,因此最好把他们理解为附加在对象上的标注。

Python赋值语句应该始终先读右边。

对象在右边创建或获取,再此之后左边的变量才会绑定到对象上,这就像为对象贴上标注。

变量只不过是标注,所以无法阻止为对象贴上多个标注。贴的多个标注,就是别名。 

>>> f1 = {'name':'Li','age':19}
>>> f2 = f1    # f2是f1的别名
>>> f2 is f1
True

每个变量(object)都有标识(identity)、类型(type)和值(value)。

对象一旦创建,它的标识绝不会变。可以把标识理解为对象在内存中的地址。

is运算符比较两个对象的标识,id()函数返回对象标识的整数表示,==运算符比较两个对象的值(对象中保存的数据)。

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

元组的相对不可变性

元组的相对不可变性:

元组与多数Python集合(列表、字典、集)一样,保存的是对象的引用。

如果引用的元素是可变的,即便元组本身不可变,元素依然可变。

copy、deepcopy

浅复制:(复制最外层容器,副本中的元素是源容器中元素的引用)

复制列表最简单的方式:

(1)使用构造方法

(2)使用 [ :]

>>> l1 = [1,2,3,4]
>>> l2 = list(l1)
>>> l3 = l1[:]

深复制:副本不共享内部对象的引用

class Bus:
    def __init__(self,passengers=None):
        if passengers is None:
            self.passengers = []
        else:
            self.passengers = list(passengers)
            
    def pick(self,name):
        self.passengers.append(name)
        
    def drop(self,name):
        self.passengers.remove(name)

import copy

bus1 = Bus([1,2,3,'David','Tom'])
bus2 = copy.copy(bus1)
bus3 = copy.deepcopy(bus1)

print(id(bus1),id(bus2),id(bus3))
print(id(bus1.passengers),id(bus2.passengers),id(bus3.passengers))

bus1.drop('David')
bus1.pick(998)

print(bus1.passengers)
print(bus2.passengers)
print(bus3.passengers)
View Code

函数参数传递模式

函数参数作为引用时:

Python唯一支持的参数传递模式:共享传参(call by sharing)

共享传参:指函数各个形式参数获得实参中各个引用的副本。(函数内部的形参是实参的副本)

def f(a,b):
    a += b
    return a
    
a = 1
b = 2
c = f(a,b)
>>> print(c)
3
>>> print(a,b)
1,2

# ========

def f(a,b):
    a += b
    return a
    
a = [1,2]
b = [3,4]
c = f(a,b)
>>> print(c)
[1, 2, 3, 4]
>>> print(a,b)
[1, 2, 3, 4] [3, 4]

避免使用可变类型作为参数的默认值

bus_team = ['Sue','David','Pat','Maria']
bus = Bus(bus_team)
bus.drop('David')
bus.drop('Pat')
>>> bus_team
['Sue','Maria']

在类中直接把参数赋值给实例变量等于为参数对象创建别名。

class Bus:
    def __init__(self,bus_team):
        if bus_team is None:
            self.bus_passengers = []
        else:
            self.bus_passengers = bus_team

# =============

class Bus:
    def __init__(self,bus_team):
        if bus_team is None:
            self.bus_passengers = []
        else:
            self.bus_passengers = list(bus_team)

垃圾回收机制

del和垃圾回收:

del语句删除名称,而不是对象。

del命令可能会导致对象被当作垃圾回收,但是仅当删除的变量保存的是对象的最后一个引用,或者无法得到对象时。

在Cpython中,垃圾回收使用的主要算法是引用计数。实际上,每个对象都会统计有多少引用指向自己。

当引用计数归零时,对象立即就被销毁。Cpython会在对象上调用__del__方法,然后释放分配给对象的内存。

>>> import weakref 
>>> s1 = {1,2,3}
>>> s2 = s1
>>> ender = weakref.finalize(s1,bye)   # 注册一个回调函数
>>> ender.alive
True
>>> del s1
>>> ender.alive
True
>>> s2 = '123'
Gone with the wind
>>> ender.alive
False

弱引用:

正因为有引用,对象才会在内存中存在。

弱引用不会增加对象的引用数量。

引用的目标对象称为所指对象(referent),弱引用不会妨碍所指对象被当作垃圾回收。

WeakValueDictionary 类实现的是一种可变映射,里面的值是对象的弱引用。

被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从WeakValueDictionary中删除。

class Cheese:
    def __init__(self,kind):
        self.kind = kind

>>> import weakref
>>> stock = weakref.WeakValueDictionary()
>>> catalog = [Cheese('Red Leicester'),Cheese('Tilsit'),Cheese('Parmesan')]
>>> for cheese in catalog:
            stock[chess.kind] = cheese

>>> sorted(stock.keys())
['Parmesan','Red Leicester','Tilsit']
>>> del catalog
>>> sorted(stock.keys())
['Parmesan']
>>> del cheese
>>> sorted(stock.keys())
[]

▲ for循环中的变量cheese是全局变量,除非显式删除,否则不会消失。

weakref 模块还提供了WeakSet 类,如果一个类需要知道所有实例,一种好的方案是创建一个WeakSet 保存实例引用。

常规Set实例永远也不会被垃圾回收,因为类中有实例的强引用。而类存在的时间与Python进程一样长,除非显式删除类。

弱引用的局限:

基本的list和dict实例不能作为所指对象,但是他们的子类可以。set实例和用户自定义类型可以作为所指对象。

同时int和tuple实例不能作为弱引用的目标,甚至它们的子类也不行。

class Mylist(list):
    ...

a_list = Mylist(range(10))
wref_to_a_list = weakref.ref(a_list)

  

原文地址:https://www.cnblogs.com/5poi/p/11201564.html