前言

元组是不可变的,但是其中的值可以改变

is==

1
2
3
4
5
6
7
d1 = {'one':1, 'two':2}
d2 = {'one':1, 'two':2}

print(d1 == d2)
>>>True
print(d1 is d2)
>>>False

可见==是判断内容相等(使用__eq__()来判断),is判断是否为同一个引用(使用id())来判断

元组的相对不可变性

元组的不可变性指的是保存的对象的引用不可变,若保存的对象的内容是可变的(但元组保存的引用不变),元组也是某种意义上的可变

1
2
3
4
5
6
7
8
9
t1 = (1, 2, [3, 4])
t2 = (1, 2, [3, 4])
print(t1 == t2)
>>>True
t1[-1].append(5)
print(t1)
>>>(1, 2, [3, 4, 5])
print(t1 == t2)
>>>False

默认做浅复制

复制list最简单的方式是使用内置的类型构造方法(书上的意思感觉是要说这是深复制,但是测试了还是浅复制(python3.6.9))

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
l1 = [1, [2, 3], (4, 5, 6)]
l2 = list(l1)
print(l2)
>>>[1, [2, 3], (4, 5, 6)]
print(l1 == l2)
>>>True
print(l1 is l2)
>>>False
l2[1].append(3.5)
print(l1)
>>>[1, [2, 3, 3.5], (4, 5, 6)]
print(l2)
>>>[1, [2, 3, 3.5], (4, 5, 6)]

可见,上述的复制是浅复制,构造方法以及[:]做的也是浅复制。使用deepcopy()解决浅复制。

函数的参数

python唯一支持的参数传递方式是传引用,这样就导致了函数可能会修改传入的参数

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

老生常谈了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def fun(L=[]):
    L.append(2333)
    return sum(L)

print(fun())
>>>2333
print(fun())
>>>4666

class A:
    def __init__(self, L=[]):
        self.L = L
    
    def append(self, x):
        self.L.append(x)

a = A()
a.append(1)
print(a.L)
>>>[1]
b = A()
print(b.L)
>>>[1]

一般使用None作为默认形参来解决上面的问题

del和垃圾回收

del语句删除名称,而不是对象del命令可能导致对象被当作垃圾回收,当且仅当:

  • 删除的变量保存着该对象的最后一个引用
  • 重新绑定时也可能导致对象的引用数量归零,导致对象被销毁
  • 该对象为无法获得的对象(见下面的例子), 它们的引用只存在二者之间时,垃圾回收程序会判定它们都无法获 取,进而把它们都销毁
1
2
3
4
5
6
a = [1, 2]
b = [a, 3]
a = [b, 4]
del b
print(a)
>>>[[[1, 2], 3], 4]

测试了结果和书上说的不一致….

在 CPython 中,垃圾回收使用的主要算法是引用计数。实际上,每个对象都会统计有多少引用指向自己。当引用计数归零时,对象立即就被销毁:CPython 会在对象上调用__del__方法(如果定义了),然后释放分配给对象的内存。CPython 2.0 增加了分代垃圾回收算法,用于检测引用循环中涉及的对象组——如果一组对象之间全是相互引用,即 使再出色的引用方式也会导致组中的对象不可获取。Python 的其他实现有更复杂的垃圾回 收程序,而且不依赖引用计数,这意味着,对象的引用数量为零时可能不会立即调用__del__方法。A. Jesse Jiryu Davis 写的“PyPy, Garbage Collection, and a Deadlock”一文(https://emptysqua.re/blog/pypy-garbage-collection-and-a-deadlock/)对__del__方法的恰当用法和不当用法做了讨论。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import weakref
s1 = {1, 2, 3}
s2 = s1
def bye():
    print('Gone with the wind...')

ender = weakref.finalize(s1, bye)
print(ender.alive)
>>>True
del s1
print(ender.alive)
>>>True
s2 = u'重新绑定'
>>>Gone with the wind...
print(ender.alive)
>>>False

上面这个例子可以看到,del删除的是名称,其对象仍然存在; 后面我们把s2重新绑定成str类型的字符串,导致原对象被删除

弱引用

某些情况下,可能需要保存对象的引用,但不留存对象本身,这就是弱引用的作用。弱引用不会增加对象的引用数量(即不会妨碍弱引用对象被当作垃圾回收)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import weakref
a_set = {0, 1}
wref = weakref.ref(a_set)
print(wref)
>>><weakref at 0x7f0a3881cef8; to 'set' at 0x7f0a36ae93c8>
print(wref())
>>>{0, 1}
a_set = {2, 3, 4}
print(wref())
>>>None
print(wref() is None)
>>>True
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import weakref

class Cheese:
    def __init__(self, kind):
        self.kind = kind
    
    def __repr__(self):
        return 'Cheese(%r)' % self.kind

stock = weakref.WeakValueDictionary()
catalog = [
    Cheese('Red Leicester'), 
    Cheese('Tilsit'), 
    Cheese('Brie'), 
    Cheese('Parmesan')
]
for cheese in catalog:
    stock[cheese.kind] = cheese
print(sorted(stock.keys()))
>>>['Brie', 'Parmesan', 'Red Leicester', 'Tilsit']

del catalog
print(sorted(stock.keys()))
>>>['Parmesan']

这个例子说明了for循环中的cheese保留这最后一个Cheese(Parmesan)的引用.在这个例子中cheese是全局变量,除非显式删除,否则不会消失

WeakValueDictionary, WeakKeyDictionary, WeakSet

弱引用的局限性

不是每个python对象都可以作为弱引用的目标,基本的listdict实例不能作为所指对象,但是它们的子类可以解决这个问题。set实例可以作为所指对象,inttuple实例不能作为弱引用的目标,子类也不行

python对不可变类型施加的把戏

对元组t来说,t[:]不创建副本,而是返回同一个对象的引用。此外,tuple(t)获得的也是同一个元组的引用。

strbytesfrozenset实例也有这种行为。注意,frozenset实例不是序列,因此不能使用fs[:](假设fs是一个frozenset实例)。但是,fs.copy()具有相同的效果:它会欺骗你,返回同一个对象的引用,而不是创建一个副本

字符串字面量可能会创建共享的对象

1
2
3
4
5
6
7
8
9
t1 = (1, 2, 3)
t2 = (1, 2, 3)
print(t1 is t2)
>>>False

s1 = 'abc'
s2 = 'abc'
print(s1 is s2)
>>>True

共享字符串字面量是一种优化措施,称为驻留(interning)。CPython还会在小的整数上使用这个优化措施,防止重复创建“热门”数字,如 0、-1 和 42。注意,CPython 不会驻留所有字符串和整数,驻留的条件是实现细节,而且没有文档说明。