Part1浅谈Python序列类型的本质——列表类(list)、元组类(tuple)和字符串类(str)的本质。不知道你发现没有,这些类有一个明显的共性,它们可以用来存储多个数据元素,主要作用是:每个类都支持下标(索引)访问序列的元素,比如使用语法Seq[i]。实际上,上面的每一个类都是用一个简单的数据结构来表示的,比如数组。但是熟悉Python的读者可能知道,这三种数据结构之间存在一些区别:比如元组和字符串不能修改,而列表是可以修改的。1.计算机内存中的数组结构在计算机体系结构中,我们知道计算机主存是由信息位组成的,这些位通常被归类为更大的单元,而这些单元又依赖于精确的系统架构。一个典型的单位是字节,相当于8位。计算机系统有大量的字节存储,那么我们如何找出我们的信息驻留在哪个字节呢?答案就是大家平时都知道的存储地址。可以根据存储地址有效地访问主存储器中的任何字节。事实上,存储的每个字节都与一个唯一的二进制数相关联,该二进制数充当其地址。在下图中,每个字节都分配了一个存储地址:一般来说,编程语言记录了一个标识符和存储其关联值的地址之间的关系。例如,当我们声明标识符x可能与内存中的某个值相关联,而标识符y可能与其他值相关联。一组相关的变量可以一个接一个地存储在计算机内存的一个连续区域中。我们称这种方法为数组。我们来看一个Python中的例子,一个文本字符串HELLO以字符序列的形式存储,假设该字符串的每个Unicode字符需要两个字节的存储空间。底部的数字是字符串的索引值。正如我们所见,数组可以存储多个值,而无需构造多个具有特定索引的变量来指定其中的每一项,并且几乎在所有编程语言(如C、Java、C#、C++)中都有使用,但是Python更有优势。Python构建列表的时候,熟悉的读者可能知道不需要预先定义数组或列表的大小。相反,在Python中,列表具有动态性,我们可以不断地往列表中添加我们想要的数据元素。接下来我们来看一下Python列表的知识(已经熟悉的读者可以快速浏览或跳过)。2.Python列表操作Python列表创建列表的语法格式:[ele1,ele2,ele3,ele4,...]创建元组的语法格式:(ele1,ele2,ele3,ele4,...)元组比列表多内存效率高,因为元组是不可变的,因此无需创建具有备用空间的动态数组。我们先在PythonIDE中创建一个列表,然后对列表的内置操作有个大概的了解。我们先创建一个名为test_list的列表,然后修改(插入或删除)元素,反转或清空列表,如下:>>test_list=[]#创建一个名为test_list的空列表>>>test_list.append("Hello")>>>test_list.append("World")>>>test_list['Hello','World']>>>test_list=["Hello","Array",2019,"轻松学习","DataStructure"]#重新赋值test_list>>>len(test_list)#求列表长度5>>>test_list[2]=1024#修改列表元素>>>test_list['Hello','Array',1024,'easylearning','DataStructure']>>>>>>test_list.insert(1,"Ilove")#insertintolist在指定位置插入一个元素>>>test_list['Hello','Ilove','Array',1024,'easylearning','DataStructure']>>>test_list.append(2020)#在列表末尾添加一个元素>>>test_list['Hello','Ilove','Array',1024,'易学','DataStructure',2020]>>>>>>test_list.pop(1)#删除指定位置的元素'我爱'>>>test_list.remove(2020)#删除指定元素>>>>>>测试_list.index('Hello')#查找一个元素的索引值0>>>test_list.index('hello')#如果查找的元素不在列表中,则返回ValueErrorTraceback(mostrecentcalllast):File"",line1,intest_list.index('hello')ValueError:'hello'isnotinlist>>>>>>test_list.reverse()#反转整个列表>>>test_list['DataStructure','easylearning',2019,'Array','Hello']>>>test_list.clear()#清空列表>>>test_list[]我们看上面的代码,可以看到列表的相关操作——增删改查已经很强大了,还有一些内置的方法这里就不一一展示了,留给读者自己去发现和体验Python列表内存分配背后的基础知识。因此,让我们通过编码练习和内存分配。查看数组的实际大小与给定大小之间的关系,以查看演示的这个额外空间。前往Jupyternotebook进行练习。或者使用您选择的任何编辑器或开发环境。复制下面写的代码。#导入sys模块方便我们使用gestsizeof函数importsys#setnn=20#setemptylistlist=[]foriinrange(n):a=len(list)#调用getsizeof函数给Python中存储对象的实际字节数b=sys.getsizeof(list)print('Length:{0:3d};Sizeofbytes:{1:4d}'.format(a,b))#增加长度一list.append(n)运行代码,可以看到如下输出:现在,随着我们增加列表的长度,字节数也增加了。我们来分析一下,当Length:1处的元素被填充到列表中时,字节数从64跳到96,增加了32字节。因为这个实验是在64位机器上运行的,这说明每个内存地址都是64位(即8字节)。添加的32个字节是分配用于存储4个对象引用的数组的大小。添加第二个、第三个或第四个元素时,内存使用量根本没有变化。字节数96可以提供对4个对象的引用。96\=\64\+\8\\times\4当Length:10时,字节数从一开始的64跳到192,可以存储16个对象引用,192\=\64\+\8\\times\16untilLength:17然后跳转,所以理论上264个字节应该可以存储25个对象264\=\64\+\8\\times\25但是因为我们在代码中设置了n=20,并且程序终止。我们可以看到Python内置的list类很聪明,知道在需要额外空间分配数据的时候,会给他们额外的size,那么这个具体是怎么实现的呢?嗯,答案是动态数组。说到这里,不知道大家在学习Python列表的时候是不是这样想的——列表很简单,就是list()类,用方括号[]括起来,然后指导书本上的各种方法或者documentstoappend,insert,pop...操作了各种IDE,是的,我想我已经学会了。但实际上,其背后的原理还真的不简单。比如我给大家举个例子:如何实现A[-1]的运算?如何实现列表分片功能?怎么自己写pop()默认删除列表最右边的元素(popleft就是简单的删除最左边的元素)?...这些功能用起来很爽,但是自己实现起来真的太难了(还在学习中,请轻喷!)如果能够学习和理解,绝对可以加强我们对数组结构的理解。3.什么是动态数组?动态数组是一块连续的内存区域,其大小随着新数据的插入而动态增长。在静态数组中,我们需要在分配时指定大小。在定义数组时,计算机已经分配了内存用于存储。事实上,我们无法扩展数组,因为它的大小是固定的。例如:如果我们分配一个大小为10的数组,我们不能插入超过10个项目。但是动态数组会在需要时自动调整大小。这有点像我们使用的Python列表,它可以存储任意数量的项目,而无需在分配时指定大小。所以实现动态数组的关键是——如何扩充数组?当列表list1的大小已满,此时有新的元素要添加到列表中,我们将执行以下步骤来克服其大小限制的缺点:分配一个更大容量集合的新数组list2list2[i]=list1[i](i=0,1,2,...,n-1),其中n为当前项集的个数list1=list2,即list2作为a新数组引用我们的新列表。然后,只需将新元素插入(添加)到我们的列表list1中。下一个要考虑的问题是,新数组应该有多大?通常我们必须这样做:新数组的大小是已满的旧数组大小的两倍。我们将以编程方式在Python中实现动态数组的概念,并创建一个不如Python强大的简单代码。实现动态数组的Python代码在Python中,我们使用ctypes自带的库来创建自己的动态数组类,因为ctypes模块提供了对原始数组的支持,为了更快的学习数组,所以知识ctypes的可以查看官方文档进行学习。关于Python的公有和私有方法,我们在方法名前使用双下划线**__**来保持隐藏。代码如下:#-*-coding:utf-8-*-#@Time:2019-11-0117:10#@Author:yuzhou_1su#@ContactMe:https://blog.csdn.net/yuzhou_1shu#@File:DynamicArray.py#@Software:PyCharmimportctypesclassDynamicArray:"""类似于简化Python列表的动态数组类。"""def__init__(self):"""创建一个空数组。"""self.n=0#计数实际元素self.capacity=1#默认数组容量self.A=self._make_array(self.capacity)#低级数组defis_empty(self):"""如果数组为空则返回True"""returnself.n==0def__len__(self):"""返回数组中存储的元素数。"""returnself.ndef__getitem__(self,i):"""返回索引i处的元素."""ifnot0<=i=self.norindex<0:raiseValueError('invalidindex')foriinrange(index,self.n-1):self.A[i]=self.A[i+1]self.A[self.n-1]=Noneself.n-=1defremove(self,value):"""删除数组中第一次出现的值。"""forkinrange(self.n):ifself.A[k]==value:forjinrange(k,self.n-1):self.A[j]=self.A[j+1]self.A[self.n-1]=Noneself.n-=1returnraiseValueError('valuenotfound')def_print(self):"""Printthearray."""foriinrange(self.n):print(self.A[i],end='')print()测试动态数组上面的Python代码我们已经实现了一个动态数组类,相信大家都很兴奋,那我们来测试一下,看看能不能成功羊毛布?在同一个文件下,编写测试代码如下:defmain():#Instantiatemylist=DynamicArray()#Appendnewelementmylist.append(10)mylist.append(9)mylist.append(8)#Insertnewelementingivenpositionmylist.insert(1,1024)mylist.insert(2,2019)#检查长度print('数组长度为:',mylist.__len__())#打印数组print('打印数组:')mylist._print()#索引print('该元素atindex1is:',mylist[1])#Removeelementprint('Remove2019inarray:')mylist.remove(2019)mylist._print()#在给定位置弹出元素print('Poppos2inarray:')#mylist.pop()mylist.pop(2)mylist._print()if__name__=='__main__':main()测试结果在激动人心的时刻公布,测试结果如下,请结合测试代码和数组结构进行理解,如有遗漏,请指出。数组长度为:5打印数组:101024201998索引为1的元素为:1024Remove2019inarray:10102498Poppos2inarray:1010248Part2总结通过上面的介绍,我们知道了数组有静态和动态类型。本篇博客,我们重点介绍什么是动态数组,并通过Python代码实现。希望你能从这里以复杂的方式学习数组。综上所述,其实操作越简单,背后的实现原理就越复杂。