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内存机制简介的更多相关文章

  1. python内存机制与垃圾回收、调优手段

    目录 一.python的内存机制 二.python的垃圾回收 1. 引用计数 1.1 原理: 1.2 优缺点: 1.4 两种情况: 2. 标记清除 2.1 原理: 2.2 优缺点: 3. 分代回收 3 ...

  2. java 内存机制简介

    java内存回收机制 不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址.java中对象是采用new或者反射的方法创 建的,这些对象的创建都是在堆中分配,所 ...

  3. python内存管理机制

    主要分为三部分: (1)内存池机制(2)引用计数(3)垃圾回收 (1)内存池机制对于python来说,对象的类型和内存都是在运行时确定的,所以python对象都是动态类型简单来说,python内存分为 ...

  4. 解读Python内存管理机制

    转自:http://developer.51cto.com/art/201007/213585.htm 内存管理,对于Python这样的动态语言,是至关重要的一部分,它在很大程度上甚至决定了Pytho ...

  5. python与java的内存机制不一样;java的方法会进入方法区直到对象消失 方法才会消失;python的方法是对象每次调用都会创建新的对象 内存地址都不i一样

    python与java的内存机制不一样;java的方法会进入方法区直到对象消失 方法才会消失;python的方法是对象每次调用都会创建新的对象 内存地址都不i一样

  6. Python内存管理机制及优化简析(转载)

    from:http://kkpattern.github.io/2015/06/20/python-memory-optimization-zh.html 准备工作 为了方便解释Python的内存管理 ...

  7. 【python测试开发栈】python内存管理机制(一)—引用计数

    什么是内存 在开始进入正题之前,我们先来回忆下,计算机基础原理的知识,为什么需要内存.我们都知道计算机的CPU相当于人类的大脑,其运算速度非常的快,而我们平时写的数据,比如:文档.代码等都是存储在磁盘 ...

  8. 【python测试开发栈】—python内存管理机制(二)—垃圾回收

    在上一篇文章中(python 内存管理机制-引用计数)中,我们介绍了python内存管理机制中的引用计数,python正是通过它来有效的管理内存.今天来介绍python的垃圾回收,其主要策略是引用计数 ...

  9. python变量的内存机制

    python变量的内存机制 作为一门简单易用的语言,且配备海量的库,python可谓是程序员手中的掌中宝,编程本身就是一种将人类思维转化为计算机思维的技术,如果不需要去追求极致的运行效率同时又不限制于 ...

随机推荐

  1. PAT甲级——A1011 World Cup Betting

    With the 2010 FIFA World Cup running, football fans the world over were becoming increasingly excite ...

  2. Java文件写入

    一,FileWritter写入文件 FileWritter, 字符流写入字符到文件.默认情况下,它会使用新的内容取代所有现有的内容,然而,当指定一个true (布尔)值作为FileWritter构造函 ...

  3. 新手必踩坑之display: inline-block

    今日励志语 往日不可追,来日犹可期,祝大家2019年继往开来 迷之间隙 我们创建一个导航列表,并将其列表 item 设置为 inline-block,主要代码如下: <div class=&qu ...

  4. vim编辑shell

      vi编辑 u撤销 i输入 dd删除游标所在的那一整行(常用) yy复制游标所在的那一行(常用) p 为将已复制的数据在光标下一行贴上 nyy n 为数字.复制光标所在的向下 n 行,例如 20yy ...

  5. git与github建立链接(将本次项目与网络GitHub同步)(二)

    第一步:我们需要先创建一个本地的版本库(其实也就是一个文件夹). 你可以直接右击新建文件夹,也可以右击打开Git bash命令行窗口通过命令来创建. 现在我通过命令行在桌面新建一个TEST文件夹(你也 ...

  6. python中str的常用方法汇总(1)

    a = 'strABC' # Strabc : 首字母大写,其他全部小写 b = a.capitalize() print(b) # STRABC : 全部大写 c = a.upper() print ...

  7. FFT初步代码分析和逼近曲线

    FFT:快速傅里叶变换 文章从两个方面来写,一个是FFT的基础知识,也就是将时域信号转换为频域信号,另一个是合成时域信号. 将时域信号转换为频域信号 代码来源于http://bigsec.net/b5 ...

  8. The web application [ROOT] appears to have started a thread named [spring.cloud.inetutils] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:

    前景提要:启动SpringBoot项目报错 原因: DeliveryPointServiceFallBack上面没有加 @Component-_-!

  9. CentOS7配置中文支持与部署GitLab服务器

    给你的 CentOS 7 安装中文支持 1.首先需要中文字体以便支持命令行终端的中文显示需求: yum groupinstall "fonts" 碰到提示输入 y 回车继续安装,大 ...

  10. maven打包时无法识别lombok中@Data生成的get set方法

    开发中使用了lombok,在使用maven编译打包时发现识别不了lombok通过注解@Data在实体类中生成的get,set方法.通过在网上的一篇博客找到了解决的办法,将maven-compiler- ...