本章内容:

对象引用 :

引用式变量
对象标识符,值,别名

可变性

元组特性
潜复制和深复制
引用和函数参数

垃圾回收

删除对象
弱引用

提示:本章内容理解不清很可能出现不易发现的错误
8.1 变量不是盒子

#a是对象[1,2,3]的标识,或者说引用式变量
a=[1,2,3]
#b也是对象[1,2,3]的标签,或者说别名
b=a
a.append(4)
print(b)

对引用式变量来说,说把变量分配给对象更合理

8.2 标识、相等性和别名

charles={"name":"Charles L","born":1832}#lewis是charles的笔名
lewis=charles lewis is charlesid(charles),id(lewis)#此时有一个冒充者:
alex={"name":"Charles L","born":1832}#==比较的是值
alex==charles #is比较的是对像的id值
alex is not charles 

==运算符比较两个对象的值(对象中保存的数据),而is比较对象的内存地址。

8.2.1 在==和is之间选择

通常我们使用==更多,因为我们通常关心的是对象的值

最常使用is检查变量绑定的值是不是None。下面是推荐的写法:

x is None

此是is运算符的调用速度更快,因为is运算符不能重载,所以Python不用寻找并调用特殊方法

8.2.2 元组的相对不可变性
元组保存的是对象的引用,元组的不可变性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象无关

即元组不可变的仅仅是元素的标识,而指向的对象是可变的

t1=(1,2,[30,40])
t2=(1,2,[30,40])
t1==t2
id(t1[-1])
t1[-1].append(99)
id(t1)
t1==t2

所以元组只具有相对不可变性,这是元组不可散列的原因
8.3 默认做浅复制

浅复制

l1=[3,[55,44],[7,8,9]]#[:]构造函数默认做浅复制
l2=l1[:]l2 l1l2==l1l2 is l1l1[1] is l2[1]l3=la.copy()
#该方法也是潜复制
l4=list(l1)l5=list(l1)
#也是潜复制

深复制(即副本不共享内部对象的引用)

import copy l5=copy.deepcopy(l1)

用下例看潜复制和深复制的差异

class Bus:def __init__(self,passengers=None):#创建一个passenger列表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(["Alice","bill","Charles","David"])
bus2=copy.copy(bus1)
#用copy模块直接对变量进行复制,这里是潜复制
bus3=copy.deepcopy(bus1)
#用copy模块中的deepcopy方法进行深复制
bus1.drop("Alice")print(bus2.passengers)
print(bus3.passengers)print(id(bus1.passengers)==id(bus2.passengers))
#bus2是bus1的浅复制,和bus共享同一个乘客列表
print(id(bus1.passengers)==id(bus3.passengers))

8.4 函数的参数作为引用时
Python唯一支持的参数传递模式是共享传参

共享传参
:函数内部的形参是实参的别名
这种传参方式的结果:函数可能会修改作为参数传入的可变对象

当传入参数是不可变对象,比如整数、元组时,改变形参并不能把 实参改变。

def f(a,b):a+=breturn a
x=1
y=2
f(x,y)
print(x,y)#元组
t=(10,20)
u=(30,40)
f(t,u)
print(t,u)

特别注意当参数是可变对象时,例如列表

x=[1,2]
y=[3,4]
f(x,y)
print(x,y)

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

python函数中,可选参数可以有默认值。
这样可以实现向后兼容。
(向后兼容:例如在python2.0上写的程序,在python3.0上可以完满运行时,我们称这种现象叫向后兼容,或者叫向历史兼容)

使用可变对象作为参数的默认值,有可能会造成不容易发现的错误。
看下例:

class funnyBus:"""人会失踪的神奇校车"""def __init__(self,passengers=[]):#这个将一个列表(可变对象)设置为默认参数self.passengers=passengersdef pick(self,name):self.passengers.append(name)def drop(self,name):self.passengers.remove(name)bus1=funnyBus(["uni","xiaoma","leo"])
#此时传入了一个列表参数。这时候,没有什么问题
bus1.passengers
bus1.pick("alice")
bus2=funnyBus()
bus3=funnyBus()

这个时候问题就来了,bus2和bus2两个实例会共享一个默认的列表[]

我们查看一下这时类的属性。

bus2=funnyBus()
bus2.pick("hello")
bus3=funnyBus()
bus2.pick("hello ni mei")
print(dir(funnyBus.__init__))
print(funnyBus.__init__.__defaults__)

output:

(['hello', 'hello ni mei'],)

运行下代码:

print(funnyBus.__init__.__defaults__[0]  is bus2.passengers)
#实例bus2的属性passengers帮定到了类的第一个属性上。

正确的处理默认参数的方法:

class Bus:def __init__(self,passengers=None):#创建一个passenger列表if passengers is None:#如果没有默认参数,实例就自己创建个参数。self.passengers=[] else:self.passengers=list(passengers)

8.4.2 防御可变参数

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

下面例子说明可变参数的风险:

class Bus:def __init__(self,passengers=None):#创建一个passenger列表if passengers is None:self.passengers=[] else:self.passengers=passengers#(1)def pick(self,name):self.passengers.append(name)def drop(self,name):self.passengers.remove(name)

此时,将可变参数传入了对象,当对象改变该参数时,外面的可变参数也会改变。因为是参数的引用传入了。

所以,如果我们不希望改变传入的参数,
上述代码的(1)部分应该这么写

self.passengers=list(passengers)
#创建参数的副本

这么写还有一个好处,就是还可以传入元组,等其他参数。

在不确定是否需要改变参数对象的时候,我们会先默认采用这种方法。

8.5 del和垃圾回收

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

del语句删除引用,而不是对象。当del删除的引用,是该对象的唯一引用时,该对象会被垃圾回收。

python垃圾回收机制:
引用计数法
当引用计数归零时,对象立即就被销毁。

下面例子演示了对象的回收。

import weakref
s1={1,2,3}
s2=s1
def bye():
print("Gone....")
ender=weakref.finalize(s1,bye)
ender.alive
def sl
ender.alive
s2="spam"
ender.alive

8.6 弱引用

实际使用中,有时需要引用对象,但是不让对象存在的时间超过所需时间。

例如我们把数据对象保存在缓存中使用,当对象的引用不存在,我们希望将缓存中的这部分数据释放掉。这时候我们就可以使用弱引用。
对弱引用的原理解释:

弱引用底层实现原理
弱应用的一个使用场景。

假设我们有一个多线程程序,并发处理应用数据:

class Date:def __init__(self,key):pass

key是该数据的唯一标识,该数据可能同时被多个线程同时访问。
当Data数据量很大时,创建的成本很高,我们希望Data在程序中只维护一个副本,
就算被多个线程同时访问,也不重复创建。

可以设计一个缓存中间件Cacher

import threading
#数据缓存class Cacher:def __init__(self):self.pool={}self.lock=threading.Lock()def get(self,key):with self.lock:#加锁data=self.pool.get(key)if data:return data self.pool[key]=data=Data(key)return data

该代码有个严重的问题,

Data一旦穿件,就会保存在数据字典,哪怕已经没有线程需要该数据,因为该数据在字典中有键进行了引用,所以对象不会被回收。

这个时候就用到弱引用,用字典键值对中的值来保存数据对象的弱引用,当数据对像的引用为0之后,就会自动释放该对象。

import threading
import weakref
# 数据缓存
class Cacher:def __init__(self):self.pool=weakref.WeakValueDictionary()self.lock = threading.Lock()def get(self, key):with self.lock:data = self.pool.get(key)if data:return dataself.pool[key] = data = Data(key)return data

弱引用类在 weaker模块中,

常见的有:

weakValueDictinary
weakKEYDictinary
weakSet

最后看一个小程序,猜一个结果:

t1=(1,2,3)
t2=tuple(t1)

t2 is t1
?

t3=t1[:]

t3 is t1
?

流畅的python第八章--对象引用 可变性 垃圾回收相关推荐

  1. python内存管理和释放_《python解释器源码剖析》第17章--python的内存管理与垃圾回收...

    17.0 序 内存管理,对于python这样的动态语言是至关重要的一部分,它在很大程度上决定了python的执行效率,因为在python的运行中会创建和销毁大量的对象,这些都设计内存的管理.同理pyt ...

  2. Python的内存管理与垃圾回收机制

    在使用真格量化时,一些用户希望了解如何来提高系统性能.通过了解Python的内存管理和垃圾回收机制,我们可以有针对性地去提高策略代码性能. Python内存管理机制 Python的内存管理机制:引入计 ...

  3. python解析原理_Python语法垃圾回收机制原理解析

    一 引入 解释器在执行到定义变量的语法时,会申请内存空间来存放变量的值,而内存的容量是有限的,这就涉及到变量值所占用内存空间的回收问题,当一个变量值没有用了(简称垃圾)就应该将其占用的内存给回收掉,那 ...

  4. python入门系列:对象引用、垃圾回收、可变性

    Python中的变量是什么 引言 Python和java中的变量本质不一样,java的变量可以理解为一个盒子,用来容纳我们的对象,使用前要先声明它,好分配给我们合适的内存空间.Python的变量可以理 ...

  5. Python内存管理方式和垃圾回收算法解析

    在列表,元组,实例,类,字典和函数中存在循环引用问题.有 del 方法的实例会以健全的方式被处理.给新类型添加GC支持是很容易的.支持GC的Python与常规的Python是二进制兼容的. 分代式回收 ...

  6. python内存回收垃圾有哪些_[Python之路] 内存管理垃圾回收

    一.python源码 1.准备源码 解压得到文件夹: 我们主要关注Include中的".h"文件以及Objects目录中的".c"文件. 我们从Include和 ...

  7. Python的内存管理以及垃圾回收

    参考:http://www.cnblogs.com/CBDoctor/p/3781078.html 先从较浅的层面来说,Python的内存管理机制可以从三个方面来讲 (1)垃圾回收 (2)引用计数 ( ...

  8. python有向图_Python 中的垃圾回收机制

    一.概述 python采用的是引用计数机制为主,标记-清除和分代收集(隔代回收)两种机制为辅的策略. 现在的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的 ...

  9. [转载] Python和java中的垃圾回收机制

    参考链接: Python中的垃圾回收 Python的垃圾回收机制 Python的垃圾回收机制有两种(也可以说一种:叫引用计数): 一是引用计数, 二是隔代回收. 引用计数 引用计数原理: 当数据的引用 ...

最新文章

  1. Linux下C程序的反汇编【转】
  2. iphone开发小技巧,转载
  3. 要命的定义函数。。。参数组合。请认真理解!
  4. scala中_下划线的使用
  5. android进程调试(ro.debuggable=1或android:debuggable=true)----JDWP线程
  6. 利用 Conda 尝鲜 Python 3.10 不一样的特性 快来试试
  7. python3入门经典100例-Python3入门机器学习_经典算法与应用-慕课网实战
  8. 25.Linux/Unix 系统编程手册(上) -- 进程的终止
  9. idea springboot启动报SLF4J:Failed to load class “org.slf4j.impl.StaticLoggerBinder”
  10. C#串口介绍以及简单串口通信程序设计实现
  11. 扫描二维码 打开 小程序或是H5网页
  12. 为什么使用工作流引擎,什么是工作流引擎,工作流引擎选型以及如何使用
  13. NOIP模拟赛 czy的后宫3
  14. 一张图看明白云计算数据中心总体分层架构
  15. 微信Wifi物联架构---机智云/云智易如何接入微信硬件平台
  16. 这才是陆奇看重的创业者:最小19岁,开拖拉机的斯坦福毕业生,弃医从文的武大学生……...
  17. 安卓点击图片跳转界面_详解拳头注册 + 安卓LOL试玩教程
  18. Cuil搜尋引擎 挑戰Google
  19. 战地2服务器地图修改,《战地2》地图修改秘籍
  20. 读稻盛和夫《活法》-现代人的修行之路

热门文章

  1. Hybris阶段总结(2)hybris架构
  2. uniapp base64加密
  3. 一个可乐瓶自制捕蟑螂神器
  4. 加拿大留学商科好还是计算机科学好,加拿大留学商科专业有哪些好大学
  5. [Mysql] CONVERT函数
  6. 32位系统能使用的内存大小以及PAE机制
  7. border-image巧妙实现带圆角的渐变边框
  8. HTTP跳槽涨薪篇,通俗易懂~
  9. C语言 逻辑与、逻辑或
  10. 3D游戏基础 Direct3D(一) D3D基本概念及渲染流水线简介