元组是不可变的,但是其中的值可以改变
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对象都可以作为弱引用的目标,基本的list
和dict
实例不能作为所指对象,但是它们的子类可以解决这个问题。set
实例可以作为所指对象,int
和tuple
实例不能作为弱引用的目标,子类也不行
python对不可变类型施加的把戏#
对元组t
来说,t[:]
不创建副本,而是返回同一个对象的引用。此外,tuple(t)
获得的也是同一个元组的引用。
str
、bytes
和frozenset
实例也有这种行为。注意,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 不会驻留所有字符串和整数,驻留的条件是实现细节,而且没有文档说明。