Python内存机制简介
Posted 程序员的自我修养
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python内存机制简介相关的知识,希望对你有一定的参考价值。
1:
变量不是盒子,应该把变量视作便利贴。变量只不过是标注,所以无法阻止为对象贴上多个标注。标注就是别名:
>>> a = [1, 2, 3] >>> b = a >>> a.append(4) >>> b [1, 2, 3, 4]
下面的代码中,lewis 和 charles 是别名,即两个变量绑定同一个对象。而 alex 不是 charles 的别名,因为二者绑定的是不同的对象。alex 和 charles 绑定的对象具有相同的值(== 比较的就是值),但是它们的标识不同。
>>> charles = {‘name‘: ‘Charles L. Dodgson‘, ‘born‘: 1832} >>> lewis = charles >>> lewis is charles True >>> id(charles), id(lewis) (140334566757552, 140334566757552) >>> lewis[‘balance‘] = 950 >>> charles {‘born‘: 1832, ‘balance‘: 950, ‘name‘: ‘Charles L. Dodgson‘} >>> alex = {‘name‘: ‘Charles L. Dodgson‘, ‘born‘: 1832, ‘balance‘: 950} >>> alex == charles True >>> alex is not charles True
2:
每个变量都有标识、类型和值。对象一旦创建,它的标识绝不会变;可以把标识理解为对象在内存中的地址。
is 运算符比较两个对象的标识;id() 函数返回对象标识的整数表示。== 运算符比较两个对象的值(对象中保存的数据)。is 运算符比 == 速度快,因为它不能重载,所以 Python 不用寻找并调用特殊方法,而是直接比较两个整数
3:
元组与多数 Python 集合(列表、字典、集合等)一样,保存的是对象的引用。如果引用的元素是可变的,即便元组本身不可变,元素依然可变。也就是说,元组的不可变性其实是指 tuple 数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。
而 str、bytes 和 array.array 等单一类型序列是扁平的,它们保存的不是引用,而是在连续的内存中保存数据本身(字符、字节和数字)。
4:
复制列表(或多数内置的可变集合)最简单的方式是使用内置的类型构造方法。例如:
>>> l1 = [3, [55, 44], (7, 8, 9)] >>> l2 = list(l1) >>> l3 = l1 >>> l2 [3, [55, 44], (7, 8, 9)] >>> l3 [3, [55, 44], (7, 8, 9)] >>> l2 is l1 False >>> l3 is l1 True
对列表和其他可变序列来说,还能使用简洁的 l2 = l1[:]语句创建副本。
构造方法或 [:] 做的是浅复制(即复制了最外层容器,副本中的元素是源容器中元素的引用)。如果所有元素都是不可变的,那么这样没有问题,还能节省内存。但是,如果有可变的元素,可能就会导致意想不到的问题:
>>> l1 = [3, [66, 55, 44], (7, 8, 9)] >>> l2 = list(l1) >>> l1.append(100) >>> l1[1].remove(55) >>> print(‘l1:‘, l1) (‘l1:‘, [3, [66, 44], (7, 8, 9), 100]) >>> print(‘l2:‘, l2) (‘l2:‘, [3, [66, 44], (7, 8, 9)]) >>> l2[1] += [33, 22] >>> l2[2] += (10, 11) >>> print(‘l1:‘, l1) (‘l1:‘, [3, [66, 44, 33, 22], (7, 8, 9), 100]) >>> print(‘l2:‘, l2) (‘l2:‘, [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)])
有时我们需要的是深复制(即副本不共享内部对象的引用)。copy 模块提供的 deepcopy 和 copy 函数能为任意对象做深复制和浅复制。
>>> class Bus: ... def __init__(self, passengers=None): ... 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‘, ‘Claire‘, ‘David‘]) >>> bus2 = copy.copy(bus1) >>> bus3 = copy.deepcopy(bus1) >>> bus4 = bus1 >>> >>> print id(bus1), id(bus2), id(bus3), id(bus4) 140334566771960 140334566290640 140334566290928 140334566771960 >>> bus1.drop(‘Bill‘) >>> bus2.passengers [‘Alice‘, ‘Claire‘, ‘David‘] >>> bus3.passengers [‘Alice‘, ‘Bill‘, ‘Claire‘, ‘David‘] >>> bus4.passengers [‘Alice‘, ‘Claire‘, ‘David‘] >>> >>> print id(bus1.passengers), id(bus2.passengers), id(bus3.passengers), id(bus4.passengers) 140334566290568 140334566290568 140334566768792 140334566290568
5:
Python 中,函数内部的形参是实参的别名。这种方案的结果是,函数可能会修改作为参数传入的可变对象,但是无法修改那些对象的标识(即不能把一个对象替换成另一个对象):
>>> def f(a, b): ... a += b ... return a ... >>> x = 1 >>> y = 2 >>> f(x, y) 3 >>> x, y (1, 2) >>> a = [1, 2] >>> b = [3, 4] >>> f(a, b) [1, 2, 3, 4] >>> a, b ([1, 2, 3, 4], [3, 4]) >>> t = (10, 20) >>> u = (30, 40) >>> f(t, u) (10, 20, 30, 40) >>> t, u ((10, 20), (30, 40))
对元组来说,+= 运算符创建一个新元组,
6:
应该避免使用可变的对象作为参数的默认值。比如下面的例子:
>>> class HauntedBus: ... def __init__(self, passengers=[]): ... self.passengers = passengers ... def pick(self, name): ... self.passengers.append(name) ... def drop(self, name): ... self.passengers.remove(name) ... >>> >>> bus1 = HauntedBus([‘Alice‘, ‘Bill‘]) >>> bus1.pick(‘Charlie‘) >>> bus1.drop(‘Alice‘) >>> bus1.passengers [‘Bill‘, ‘Charlie‘] >>> >>> bus2 = HauntedBus() >>> bus2.pick(‘Carrie‘) >>> bus2.passengers [‘Carrie‘] >>> >>> bus3 = HauntedBus() >>> bus3.passengers [‘Carrie‘] >>> >>> bus3.pick(‘Dave‘) >>> bus2.passengers [‘Carrie‘, ‘Dave‘] >>> >>> bus2.passengers is bus3.passengers True >>> >>> bus1.passengers [‘Bill‘, ‘Charlie‘] >>> HauntedBus.__init__.__defaults__ ([‘Carrie‘, ‘Dave‘],)
实例化 HauntedBus 时,如果传入乘客,会按预期运作。但是不为 HauntedBus 指定乘客的话,奇怪的事就发生了,没有指定初始乘客的 HauntedBus 实例会共享同一个乘客列表。self.passengers 变成了 passengers 参数默认值的别名。出现这个问题的根源是,默认值在定义函数时计算(通常在加载模块时),这样默认值变成了函数对象的属性。因此,如果默认值是可变对象,而且修改了它的值,那么后续的函数调用都会受到影响。
7:
del 语句删除名称,而不是对象。del 命令可能会导致对象被当作垃圾回收,但是仅当删除的变量保存的是对象的最后一个引用,或者无法得到对象时。 重新绑定也可能会导致对象的引用数量归零,导致对象被销毁。
如果两个对象相互引用,当它们的引用只存在二者之间时,垃圾回收程序会判定它们都无法获取,进而把它们都销毁。
在 CPython 中,垃圾回收使用的主要算法是引用计数。实际上,每个对象都会统计有多少引用指向自己。当引用计数归零时,对象立即就被销毁:CPython 会在对象上调用__del__ 方法(如果定义了),然后释放分配给对象的内存。
为了演示对象生命结束时的情形,下面使用 weakref.finalize (python3)注册一个回调函数,在销毁对象时调用。
>>> import weakref >>> s1 = {1, 2, 3} >>> s2 = s1 >>> def bye(): ... print(‘Gone with the wind...‘) ... >>> ender = weakref.finalize(s1, bye) >>> ender.alive True >>> >>> del s1 >>> ender.alive True >>> >>> s2 = ‘spam‘ Gone with the wind... >>> ender.alive False
正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。但是,有时需要引用对象,而不让对象存在的时间超过所需时间。这经常用在缓存中。弱引用不会增加对象的引用数量。弱引用不会妨碍所指对象被当作垃圾回收。弱引用在缓存应用中很有用,
下例展示了如何使用 weakref.ref 实例获取所指对象。如果对象存在,调用弱引用可以获取对象;否则返回 None。
>>> import weakref >>> a_set = {0, 1} >>> wref = weakref.ref(a_set) >>> wref <weakref at 0x7fdb8cc1c368; to ‘set‘ at 0x7fdb8cc2bd00> >>> >>> wref() set([0, 1]) >>> >>> a_set = {2, 3, 4} >>> wref() set([0, 1]) >>> >>> wref() is None False >>> >>> wref() is None True
上例是一个控制台会话,而Python 控制台会自动把 _ 变量绑定到结果不为 None 的表达式结果上。首次调用 wref() 返回的是被引用的对象{0, 1}, {0, 1} 会绑定给 _ 变量。接下来,a_set 不再指代 {0, 1} 集合,因此集合的引用数量减少了。但是 _ 变量仍然指代它。所以再次调用 wref() 依旧返回 {0, 1}。计算这个表达式时,{0, 1} 存在,因此 wref() 不是 None。但是,随后 _ 绑定到结果值 False。现在 {0, 1} 没有强引用了。因为 {0, 1} 对象不存在了,所以 wref() 返回 None
weakref 模块的文档(http://docs.python.org/3/library/weakref.html)指出,weakref.ref类其实是低层接口,供高级用途使用,多数程序最好使用 weakref 集合和 finalize。也就是说,应该使用 WeakKeyDictionary、WeakValueDictionary、WeakSet 和finalize(在内部使用弱引用),不要自己动手创建并处理 weakref.ref 实例。
WeakValueDictionary 类实现的是一种可变映射,里面的值是对象的弱引用。被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从 WeakValueDictionary中删除。因此,WeakValueDictionary 经常用于缓存。如下例:
>>> class Cheese: ... def __init__(self, kind): ... self.kind = kind ... def __repr__(self): ... return ‘Cheese(%r)‘ % self.kind ... >>> import weakref >>> stock = weakref.WeakValueDictionary() >>> catalog = [Cheese(‘Red Leicester‘), Cheese(‘Tilsit‘), Cheese(‘Brie‘), Cheese(‘Parmesan‘)] >>> >>> for cheese in catalog: ... stock[cheese.kind] = cheese ... >>> sorted(stock.keys()) [‘Brie‘, ‘Parmesan‘, ‘Red Leicester‘, ‘Tilsit‘] >>> >>> del catalog >>> sorted(stock.keys()) [‘Parmesan‘] >>> >>> del cheese >>> sorted(stock.keys()) []
删除 catalog 之后,stock 中的大多数奶酪都不见了,这是 WeakValueDictionary的预期行为。为什么不是全部呢?这是因为for循环中的变量 cheese 是全局变量,除非显式删除,否则不会消失。
不是每个 Python 对象都可以作为弱引用的目标(或称所指对象)。基本的 list 和 dict实例不能作为所指对象,但是它们的子类可以轻松地解决这个问题:
>>> wref_to_a_list = weakref.ref([]) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: cannot create weak reference to ‘list‘ object class MyList(list): pass a_list = MyList(range(10)) # a_list可以作为弱引用的目标 wref_to_a_list = weakref.ref(a_list)
8:
如果所有 Python 对象都是不可变的,那么本章就没有存在的必要了。处理不可变的对象时,变量保存的是真正的对象还是共享对象的引用无关紧要。仅当对象可变时,对象标识才重要。
CPython 中的垃圾回收主要依靠引用计数,这容易实现,但是遇到引用循环容易泄露内存,因此 CPython 2.0实现了分代垃圾回收程序,它能把引用循环中不可获取的对象销毁。
以上是关于Python内存机制简介的主要内容,如果未能解决你的问题,请参考以下文章
14.VisualVM使用详解15.VisualVM堆查看器使用的内存不足19.class文件--文件结构--魔数20.文件结构--常量池21.文件结构访问标志(2个字节)22.类加载机制概(代码片段
Android 逆向类加载器 ClassLoader ( 类加载器源码简介 | BaseDexClassLoader | DexClassLoader | PathClassLoader )(代码片段