python对象回收_Python 对象引用、可变性、垃圾回收之2

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

默认做浅复制

复制列表(或多数内置的可变集合)最简单的方式是使用内置的类型构造方法。

l1 = [1, [2, 3], (4, 5, 6)]

l2 = list(l1) # list(l1)创建l1的副本

print(l2)

print(l2 == l1) # 副本与源列表相等

True

print(l2 is l1) # 但是二者只带不同的对象

构造方法和[:]做的是浅复制(即复制了最外层容器,副本中的元素是源容器中元素的引用)

如果所有的元素都是不可变的,那么没毛病,还能节省内存,但如果有可变元素,可能会导致意想不到的问题。

Why?

l1 = [2, [3,4,5], (6,7,8)]

l2 = list(l1)

l1.append(100)

l1[1].remove(3)

print('l1->',l1)

print('l2->',l2)

l2[1] += [66, 88]

l2[2] += (22, 33)

print('l1->', l1)

print('l2->', l2)

分析:l2 = list(l1) 赋值后的程序状态,l1和l2指代不同的列表,l1 和 l2中的列表最终值还是一致的,但是元组的内容不一致了,l2重新创建了一个元组。

为任意对象做深复制和浅复制

浅复制没什么问题,有时需做深复制(即副本不共享内部对象的引用),copy模块提供了deepcopy和copy来为任意对象做深复制和浅复制。

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(['x', 'q', 'l'])

bus2 = copy.copy(bus1)

bus3 = copy.deepcopy(bus1)

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

140343326769896 140343326772080 140343326771296

bus1.drop('x')

print(bus2.passengers)

['q', 'l']

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

140343326769896 140343326772080 140343326771296

print(bus3.passengers)

['x', 'q', 'l']

小结:一般来说,深复制不是一件简单的事,如果对象有循环引用,那么会进入无限循环。deepcopy会记住已经复制的对象,因此能优雅的处理循环引用。

eg:

a = [1, 2]

b = [a,4]

a.append(b)

print(a)

[1, 22, [[...], 4]]

from copy import deepcopy

c=deepcopy(a)

print(c)

思考:为什么通过别名共享对象能解释python中传递参数的方式,及使用可变类型作为参数默认值会产生什么问题?

函数的参数作为引用时

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

什么是共享传参?

共享传参指函数的各个形式参数获得各个引用的副本,即:函数内部的形参是实参的别名。

造成的后果?

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

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

class Bus:

def __init__(self, passengers=[]):

self.passengers = passengers

def pick(self, name):

self.passengers.append(name)

def drop(self, name):

self.passengers.remove(name)

bus1 = HauntedBus(['Alice', 'Bill'])

print(bus1.passengers)

bus1.pick('Charlie')

bus1.drop('Alice')

print(bus1.passengers)

bus2 = HauntedBus()

bus2.pick('Carrie')

bus2.passengers

bus3 = HauntedBus()

bus3.passengers

bus3.pick('Dave')

print(bus2.passengers)

bus2.passengers is bus3.passengers

print(bus1.passengers)

防御可变参数

如果定义的函数接收可变参数,应该谨慎考虑调用方是否期望修改传入的参数。

TwilightBus 实例与客户共享乘客列表,产生意料之外的结果。

从 TwilightBus 下车后,乘客消失了

>>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat']

>>> bus = TwilightBus(basketball_team)

>>> bus.drop('Tina')

>>> bus.drop('Pat')

>>> basketball_team

['Sue', 'Maya', 'Diana']

TwilightBus 违反了设计接口的最佳实践,即“最少惊讶原则”。学生从校车中下车后,她的名字就从篮球队的名单中消失了,这确实不合理。

class TwilightBus:

"""惊魂校车"""

def __init__(self, passengers=None):

if passengers is None:

passengers = [] # 谨慎处理,当passengers为None时,创建一个新的空list

else:

self.passengers = passengers # self.passengers 变成 passengers 的别名,而passengers是传给 __

init__ 方法的实参

def pick(self, name):

self.passengers.append(name)

def drop(self, name):

self.passengers.remove(name)

小结:

问题所在:校车为传给构造方法的列表创建了别名

正确的做法:校车自己维护乘客列表

修正的方法:在__init__中,传入passengers参数时,应该把参数值的副本赋值给self.passengers

def __init__(self, passengers=None):

if passengers is None:

self.passengers = []

else:

self.passengers = list(passengers) # 创建passengers列表的副本;如果不是列表,就将其转为list

这样,在内部像这样处理乘客列表,就不会影响初始化校车时传入的参数了。

注意事项:

除非这个方法确实想通过参数传入的对象,否则在类中直接把参数赋值给实例变量之前一定要三思,因为这样会为参数对象创建别名。如果不确定,那就创建副本。

del和垃圾回收

对象绝对不会自行销毁,然而,无法得到对象时,可能会被当作垃圾回收。

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

注意事项:

"""有个__del__ 特殊方法,但是它不会销毁实例,不应该在代码中调用。即将销毁实例时,Python 解释器会调用__del__ 方法,给实例最后的机会,释放外部资源。自己编写的代码很少需要实现 __del__ 代码,有些 Python 新手会花时间实现,但却吃力不讨好,因为 __del__ 很难用对。"""

在Cpython中,垃圾回收使用的主要算法是引用计数。

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

CPython 2.0 增加了分代垃圾回收算法,用于检测引用循环中涉及的对象组——如果一组对象之间全是相互引用,即使再出色的引用方式也会导致组中的对象不可获取。Python 的其他实现有更复杂的垃圾回收程序,而且不依赖引用计数,这意味着,对象的引用数量为零时可能不会立即调用__del__方法.

没有指向对象的引用时,监视对象生命结束时的情形

import weakref

s1 = {1,2,3}

s2 = s1 # s1、s2 是别名,指向同一个集合

def bye():

# 此函数一定不能是要销毁的对象的绑定方法,否则会有一个指向对象的引用

print('Lin with the wind')

ender = weakref.finalize(s1, bye) # 在s1引用的对象上注册bye回调

ender.alive # 调用 finalize 对象之前,.alive 属性的值为 True

True

del s1 # del不删除对象,而是删除对象的引用

ender.alive

True

s2 = 'spam' # 重新绑定最后一个引用s2,让{1,2,3}无法获取,对象被销毁了,调用了bye回调

ender.alive

False

小结:

del不会删除对象,但是执行del操作后可能会导致对象不可获取,从而被删除

疑惑?

为什么{1,2,3}对象被销毁了?毕竟,把s1引用传给finalize函数了,而为了监控对象和调用回调,必要有引用,因为finalize持有{1,2,3}的弱引用。

弱引用

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

当对象的引用数量归零后,垃圾回收程序会把对象销毁。但是,有时需要引用对象,而不让对象存在的时间超过所需时间。这经常用在缓存中。

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

引用的目标对象称为所指对象(referent)。

弱引用不会妨碍所指对象被当作垃圾回收。

弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。

弱引用是可调用的对象,返回的是被引用的对象;如果所指对象不存在了,返回 None

注意事项:

weakref 模块的文档(http://docs.python.org/3/library/weakref.html)指出,weakref.ref 类其 实是低层接口,供高级用途使用,多数程序最好使用 weakref 集合和 finalize。也就是说, 应该使用 WeakKeyDictionary、WeakValueDictionary、WeakSet 和 finalize(在内部使用弱 引用),不要自己动手创建并处理 weakref.ref 实例。

本章总结:

变量保存的是引用,这一点对python变成有很多实际的影响简单的复制不创建副本

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

为现有的变量赋予新值,不会修改之前绑定的变量。这叫重新绑定:现在变量绑定了其他对象。如果变量是之前那个对象的最后一个引用,对象会被当作垃圾回收。

函数的参数以别名的形式传递,这意味着,函数可能会修改通过参数传入的可变对象。这一行为无法避免,除非在本地创建副本,或者使用不可变对象(即传入元组,而不传入列表)

使用可变类型作为函数参数的默认值有危险,因为如果就地修改了参数,默认值也就变 了,这会影响以后使用默认值的调用。

可变对象还是导致多线程编程难以处理的主要原因,因为某个线程改动对象后,如果不正确地同步,那就会损坏数据。但是过度同步又会导致死锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值