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

栏目: Python · 发布时间: 6年前

内容简介:变量的赋值方式:比如x = 2是将一个变量s分配给一个对象比如整数2。而不是把整数对象2分配给变量s每个变量都有标识、类型和值。对象一旦创建,他的标识绝不会变;你可以把标识理解为对象在内存中的地址。is运算符比较两个对象的标识;id()函数返回对象标识的整数表示。不显式的使用copy模块的deepcopy函数深复制时,都默认为浅复制

对象引用和可变性

变量不是盒子,而是‘便利贴’

>>> a = [1,2,3]
>>> b = a
>>> a.append(5)
>>> a
[1, 2, 3, 5]
>>> b
[1, 2, 3, 5]

变量的赋值方式:比如x = 2是将一个变量s分配给一个对象比如整数2。而不是把整数对象2分配给变量s

>>> c = {'name':'yang','born':1997}
>>> a = c  #a为c的一个别名。他们俩同时指向一个对象,'=='和'is'运算符证明这一点
>>> a == c
True
>>> a is c
True
>>> id(a),id(c)
(139644203394464, 139644203394464)
>>> a['name'] = 'yyy'  #用a修改内容
>>> c   #c也会被修改,因为它们俩指向的是一个对象
{'name': 'yyy', 'born': 1997}
>>> d = {'name': 'yyy', 'born': 1997} #新建一个d对象,与a和c的值一样
>>> d == a  # '=='运算符比较值是否相等
True
>>> d is a  #'is'运算符比较对象的标识是否相等,就是比较id()是否相等。d是新建的对象很明显不会相等
False
>>> id(d), id(a)
(139644203394536, 139644203394464)

每个变量都有标识、类型和值。对象一旦创建,他的标识绝不会变;你可以把标识理解为对象在内存中的地址。is运算符比较两个对象的标识;id()函数返回对象标识的整数表示。

元组的不可变性

#元组的不可变性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。比如元组里引用了一个可变对象列表,不能改变这个引用让他变成其他字典或整数对象,但是可以修改这个可变对象的值。
>>> t1 = (1, 2, [3, 4])
>>> t2 = (1, 2, [3, 4])
>>> id(t1),id(t2)
(139644201933272, 139644201953896)
>>> t1 == t2  #值相等
True
>>> t1 is t2  #标识不相等,两个除了值相等其他完全不相关的变量
False
>>> t1[-1].append(5)  #可以对元组内的列表元组进行添加操作
>>> t1 == t2  #此时他们俩的值不相等了
False

不显式的使用copy模块的deepcopy函数深复制时,都默认为浅复制

浅复制复制了最外层的容器,副本中的元素是原容器中元素的引用

>>> a = [1, 2, [3, 4]]
>>> c = a[:]
>>>a == c
True
>>> a is c  #容器不一样,但是里元素的引用一样
False

>>> r = (1, 2, [4,5])  #对元组或其他不可变类型对象浅复制返回的是同一个对象的引用。类似于rr = r
>>> rr = r[:]
>>> rr is r
True

#浅复制后母本和副本内的元素都互为对方的标识,也就是都指向同一个对象。如果对母本或副本中的可变元素操作,因为两个引用是同一个对象,所以会影响到另一个母本或副本。但是,比如在副本中对不可变元素操作会生成一个新的对象引用,就和母本中的不可变元素不是同一个引用了,就不会影响到母本。
#下面是示例:
>>> l1 = [3, [66,55,44], (7, 8, 9)]
>>> l2 = list(l1)  #对列表l1浅复制,赋值给l1
>>> l1.append(100)  #l1添加一个新元素100
>>> l2
[3, [66, 55, 44], (7, 8, 9)]  #l2中没有添加
>>> l1
[3, [66, 55, 44], (7, 8, 9), 100]  #l1中添加成功
>>> l1[1].remove(55)  #将l1[1]这个列表中的55元素删除
>>> l1
[3, [66, 44], (7, 8, 9), 100]
>>> l2
[3, [66, 44], (7, 8, 9)] #对l2也有影响,因为l2[1]这个列表和l1[1]的列表时同一个,他们两个互相时对方的别名,都指向同一个列表元素
>>> l2[1] += [1,1,1] #l2[1]就地修改列表
>>> l2
[3, [66, 44, 1, 1, 1], (7, 8, 9)]
>>> l1
[3, [66, 44, 1, 1, 1], (7, 8, 9), 100] #l1[1]也被修改,因为这是同一个对象
>>> l1[2] is l2[2] #此时l1[2]和l2[2]这两个元组是同一个对象
True
>>> l2[2] += (1,1,1)  #对l2[2]这个元组添加(1,1,1)。因为元组是不可变元素,这个赋值操作不能就地添加,相当于l2[2] = l2[2]+(1,1,1),这里创建了一个新元组。
>>> l1[2] is l2[2] #此时l1[2]和l2[2]这两个元素不再是同一个对象
False
>>> l2
[3, [66, 44, 1, 1, 1], (7, 8, 9, 1, 1, 1)]
>>> l1
[3, [66, 44, 1, 1, 1], (7, 8, 9), 100] #所以这个修改并没有对l1起作用

>>> l2[1] is l1[1] #可变对象就地修改,再改还是引用的同一个对象
True
>>> l1[0] is l2[0]
True

深复制

#定义一个类来测试
class Bus:
    def __init__(self, p=None):
        if p is None:
            p = []
        else:
            self.p = list(p)

    def pick(self, name):
        self.p.append(name)

    def drop(self, name):
        self.p.remove(name)

>>> from bus import *
>>> bus1 = Bus(['a', 'b', 'cc'])
>>> bus1
<bus.Bus object at 0x7fb47e6ccba8>
>>> bus1.p
['a', 'b', 'cc']
>>> bus2 = copy.copy(bus1)   #浅复制bus1
>>> bus3 = copy.deepcopy(bus1)  #深复制bus1
#到此 创建了三个bus实例
>>> bus1.drop('a')  #bus1的p列表中删除一个元素
>>> bus2
<bus.Bus object at 0x7fb47e6cc7b8>
>>> bus2.p  #bus2的p列表中也没有这个元素了,浅复制共享一个列表对象
['b', 'cc']
>>> bus3.p  #深复制不会共享列表,所以不会修改
['a', 'b', 'cc']
>>> bus1.p is bus2.p
True
>>> bus1.p is bus3.p
False

函数的可变参数

#函数可能会修改接收到的任何可变对象
>>> def f(a, b):
...     a += b
...     print(id(a))
...     return a
>>> x = [1,1]
>>> id(x)
139901112369928
>>> y = [2, 2]
>>> f(x, y) #函数的形参获得各个实参的副本,也就是说,函数内部的形参是实参的别名
139901112369928  
[1, 1, 2, 2]
>>> x
[1, 1, 2, 2]
>>> x = 1  #当实参为不可变类型时
>>> y = 2
>>> id(x)
10910400
>>> f(x, y) # a += b相当于重新创建了一个a对象,上面的浅复制讲的很清楚了
10910464
3

函数的默认值是可变参数时

class Bus:
    def __init__(self, p=[]):
        self.p = p

    def pick(self, name):
        self.p.append(name)

    def drop(self, name):
        self.p.remove(name)
        
>>> from bus import *
>>> bus1 = Bus(['a', 'b'])
>>> bus2 = Bus()
>>> bus3 = Bus()  #创建三个实例,bus3和bus3使用默认值
>>> bus1.pick('c')  #bus1添加新元素
>>> bus1.p,bus2.p,bus3.p  #只有bus1变了
(['a', 'b', 'c'], [], [])
>>> bus2.pick('e')  #bus2添加新元素
>>> bus1.p,bus2.p,bus3.p  #bus2和bus3都变了
(['a', 'b', 'c'], ['e'], ['e'])
>>> Bus.__init__.__defaults__ #这时Bus类的defaults属性已经变了
(['e'],)

上面很明显的说明了:bus2和bus3使用了参数默认值(列表对象)。默认值一是在模块加载时计算,self.p变成了p参数默认值的别名。就是说不管多少个实例,只要使用的是默认值(列表对象),那么所有实例和Bus类共享这一个列表。

函数的默认值是可变参数时的解决办法

#错误的方法
class Bus:
    def __init__(self, p=None):
        if p is None:
            self.p = []
        else:
            self.p = p #self.p为p的别名,他们俩都指向同一个对象。

    def pick(self, name):
        self.p.append(name)

    def drop(self, name):
        self.p.remove(name)

>>> from bus import *
>>> i = ['a', 'b', 'c']
>>> b = Bus(i)
>>> b.drop('a')  # b实例调用他的drop方法删除'a'的时候把i列表中的'a'也删了
>>> b.p
['b', 'c']
>>> i
['b', 'c']
#正确方法
class Bus:
    def __init__(self, p=None):
        if p is None:
            self.p = []
        else:
            self.p = list(p) #这时self.p是对p的一个浅复制,self.p和p指向不同对象,但是容器里面的元素还是相同的引用,如果元素为可变类型,那么还是会出现问题

    def pick(self, name):
        self.p.append(name)

    def drop(self, name):
        self.p.remove(name)
>>> from bus import *
>>> i = ['a', 'b', 'c']
>>> b = Bus(i)
>>> b.drop('a')  # b实例删除'a'后i列表并没有受到影响
>>> i
['a', 'b', 'c']

>>> i = ['a', 'b', [1,2]]  #如果参数内元素是可变类型还是有影响
>>> b = Bus(i)
>>> b.p[2].pop()  # b实例删除列表内的一个列表中的元素
2
>>> i  # i列表也受到影响
['a', 'b', [1]]

总结:浅复制复制的是最外层的容器,里面的元素还是原容器中元素的引用,也就是修改里面的可变元素两个容器都会受到影响。深复制相当于重新创建了一个对象,里面的元素和原容器一点关系都没有。

垃圾回收

在cpython中,垃圾回收的主要算法是引用计数。每个对象都会统计有多少引用指向自己。当计数归零时对象就立即被销毁。当然 python 还有其他更复杂的垃圾回收算法,而且不依赖引用计数。

>>> import weakref
>>> s1 = {1,2,3}
>>> s2 = s1  #s2 is s1。指向同一个集合
>>> def a():
...     print('aaa')
>>> end = weakref.finalize(s1, a)  # weakref是一个弱引用包。这里在s1引用对象上注册a回调
>>> end
<finalize object at 0x7faa0a5fa3b0; for 'set' at 0x7faa0a3e6048>
>>> del s1
>>> end.alive  #对象还没有被销毁
True
>>> s2 = 2  # 让s2指向其他对象,此时没有对那个集合的引用。对象被销毁执行回调函数输出'aaa'
aaa

以上所述就是小编给大家介绍的《python对象引用,可变性和垃圾回收》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Machine Learning

Machine Learning

Kevin Murphy / The MIT Press / 2012-9-18 / USD 90.00

Today's Web-enabled deluge of electronic data calls for automated methods of data analysis. Machine learning provides these, developing methods that can automatically detect patterns in data and then ......一起来看看 《Machine Learning》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具