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

流畅的python第八章--对象引用 可变性 垃圾回收,第1张

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

本章内容:

对象引用 :

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

可变性

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

垃圾回收

删除对象
弱引用

提示:本章内容理解不清很可能出现不易发现的错误
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 charles

id(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 l1

l2==l1

l2 is l1

l1[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+=b
	return 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=passengers

    def 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 data
            self.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
?

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5156631.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-18
下一篇 2022-11-18

发表评论

登录后才能评论

评论列表(0条)

保存