python进阶用法2 【从帮助函数看python内存申请机制】

前言

介绍了四个帮助函数,dir()help()type()id(),通过id()函数进一步分析了python在申请内存方面的效率问题,提到的基本类型有stringlistqueuedeque

四个帮助函数

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其实可以很明显看出,其均是依靠c的链表进行开发的(不需要提前申请空间),其地址亦不变化。更改一点之前的错误理解,python的list实现不是链表,而是动态数组

当我们使用deque时,可以很明显看到,我们的时间消耗已经差距很小了,与未提前申请空间的list接近一致,但经多次运行,还是可以发现,最快的依旧是已经申请了空间的list,我么进一步去了解queuedeque可以发现其无法提前申请空间(可以理解为其职能分别从一端和两段进行增加值,减值),及无法像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的内存分配

猜你喜欢

转载自blog.csdn.net/wy_97/article/details/79741244