前言
介绍了四个帮助函数, dir(),help(),type(),id(), 通过 id()函数进一步分析了 python 在申请内存方面的效率问题, 提到的基本类型有 string,list,queue 和 deque
四个帮助函数
dir()函数
dir()函数是查看函数或模块内的操作方法都有什么, 输出的是方法列表
dir('str')
也可以查看自己定义的函数
help()函数
help()函数是查看函数或模块用途的详细说明, 例:
help('str')
type()函数
这个很简单了, 返回其类型, 略
id()函数
对一个对象的引用调用 id()函数, 可以得到该对象的标识符 (dentity). 该标识符是一个整数, 它保证在该对象的生命周期内是唯一的和恒定的. 具有不重叠生命周期的两个对象具有相同的 id() 值.
PS: 在 CPython 实现细节: 标识符 (dentity) 为对象在内存中的地址. 在 Python 中一切皆对象, 变量中存放的是对象的引用. 字符串常量和整型常量都是对象.
举个例子:
- >>> a = 1
- >>> b=2
- >>> c =1
- >>> id(a)
- 1521120064
- >>> id(b)
- 1521120096
- >>> id(c)
- 1521120064
不知道, 在看这个 dentity 的时候, 有没有发现 a 与 c 的地址是相同的!!!
从 id()函数看 python 内存地址申请机制
看个例子:(以下内容来源 @Unname_Bao 关于 python3 中整数数组转 bytes 的效率问题)
如果还是没有理解的话, 接下来看我本地进行的一个非常简单的一个演示脚本测试:
- import time
- t1 = time.time()
- astr = 'a'
- for x in range(1,2000000):
- astr = astr + str(x)
- astr
- t2 = time.time()
- print(t2-t1)
- #10.028913259506226
- #[Finished in 10.2s]
- import time
- t1 = time.time()
- astr = list('1'*2000000)
- for x in range(1,2000000):
- astr[x]=str(x)
- bstr = str(astr)
- t2 = time.time()
- print(t2-t1)
- #0.8323781490325928
- #[Finished in 1.1s]
为什么差距如此之大呢? 这就回到了我们最初提到的 a,b, 当值改变, 会重新去申请内存空间 (id 改变) 在这第一个例子中, 我们不停地改变 astr 的值, astr 即不停地申请内存空间, 此过程消耗了大量的时间!!! 第二个例子中, 我们一次申请了够所有变量使用的内存空间地址, 免去了每次申请, 所以大大加快了运行速度!!!
感谢 @
一个闲散之人的闲散
更进一步的分析,
影响其效率问题的核心根本在于 list 到底是基于链表的数据结构还是基于线性表的数据结构线性表的话为了腾出足够连续空间需要改变表头的内存位置, 也就造成了 id 的改变, 对于链表而言, 则只需要申请一个结点大小的内存量, 没必要对表头的内存位置动手脚
关于 list 的数据结构, 从知乎上 get 到的结果是线性表形式的数据结构, 于是乎我又做了以下 3 个测试:
1 不提前申请空间的 queue
- import time
- import queue
- t1 = time.time()
- astr = queue.Queue()
- for x in range(1,2000000):
- astr.put(str(x))
- bstr = str(astr)
- t2 = time.time()
- print(t2-t1)
- # 4.525705337524414
- # [Finished in 4.8s]
2 不提前申请空间的 deque
- import collections
- import time
- t1 = time.time()
- astr = collections.deque()
- for x in range(1,2000000):
- astr.append(str(x))
- bstr = str(astr)
- t2 = time.time()
- print(t2-t1)
- # 0.938164234161377
- # [Finished in 1.3s]
3 不提前申请空间的 list
- import time
- t1 = time.time()
- astr = []
- for x in range(1,2000000):
- astr.append(str(x))
- bstr = str(astr)
- t2 = time.time()
- print(t2-t1)
- # 0.9456796646118164
- # [Finished in 1.2s]
另做个测试:
- import collections
- import queue
- print("Deque ID:")
- astr1 = collections.deque()
- for x in range(1,5):
- astr1.append(str(x))
- print(id(astr1))
- print("Queue ID:")
- astr2 = queue.Queue()
- for x in range(1,5):
- astr2.put(str(x))
- print(id(astr2))
- print("list ID:")
- astr3 = []
- for x in range(1,5):
- astr3.append(str(x))
- print(id(astr3))
- # Deque ID:
- # 1206229307464
- # 1206229307464
- # 1206229307464
- # 1206229307464
- # Queue ID:
- # 1206225595416
- # 1206225595416
- # 1206225595416
- # 1206225595416
- # list ID:
- # 1206229266760
- # 1206229266760
- # 1206229266760
- # 1206229266760
- # [Finished in 0.2s]
queuedeque 和 list 其实可以很明显看出, 其均是依靠 c 的链表进行开发的(不需要提前申请空间), 其地址亦不变化
当我们使用 deque 时, 可以很明显看到, 我们的时间消耗已经差距很小了, 与未提前申请空间的 list 接近一致, 但经多次运行, 还是可以发现, 最快的依旧是已经申请了空间的 list, 我么进一步去了解 queue 和 deque 可以发现其无法提前申请空间 (可以理解为其职能分别从一端和两段进行增加值, 减值), 及无法像 list 一样可以通过 list[下标] 直接取值, 所以综上所述, list 无疑是最快的~
补充, list 的扩张空间机制
- >>> test = []
- >>> test.__sizeof__()
- 40
- >>> test.append('a')
- >>> test.__sizeof__()
- 72
- >>> test.append('a')
- >>> test.__sizeof__()
- 72
- >>> test.append('a')
- >>> test.__sizeof__()
- 72
- >>> test.append('a')
- >>> test.__sizeof__()
- 72
- >>> test.append('a')
- >>> test.__sizeof__()
- 104
- >>>
参考链接: Python 中 list 的内存分配 http://www.cnblogs.com/ruizhang3/p/6823470.html
来源: https://blog.csdn.net/wy_97/article/details/79741244