TOC

元祖(不可变类型)

    元祖Tuple也是有序的元素组成的集合类型,使用()表示,并且元祖是不可变的,当元祖只有一个元素的时候,必须以逗号结尾,并且元祖和列表差不多,内部的元素可以是任何合法的数据类型;
    Python的元组与列表类似,不同之处在于元组的元素不能修改,元组使用小括号,列表使用方括号,元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可;
    虽然说元祖不可修改,这一点没毛病,但是如果元祖内部含有一个可变的数据类型,我们是可以修改的,因为这个元素只是一个在这个元祖里面只是一个引用;
    从数据结构上来讲,元祖比列表设计得比较轻巧,因为它舍弃掉很多的东西,所以在有的时候,我们明确的知道元素不会进行改变,那么这个时候就可以直接使用tuple,也可以防止被修改,类似于常量;
元素访问
    Tuple元素的访问和List的一样的,索引不可超界,超界会引发IndexError;
正索引:从左至右,从0开始;
负索引:从右至左,从-1开始;
元祖方法
    因为元祖不可变的特性,它和列表不同,列表所有可修改新增类操作元祖都没有,但是其他的是有的;
index(value,[start[stop]]):通过value,从指定区间查找元素是否匹配,返回一个匹配后元素的索引号,其中可选参数start和stop,为起始区和结束区间;
count(value):返回列表中匹配value的次数;

命名元祖

    命名元祖,即可以命名的元祖,它实际上是一个继承于Tuple定义的类,即namedtuple,它用元祖作为父类的原因是,元祖元祖有一个好处在于,元祖的元素一旦定义了,就不可变,所以说命名元祖也是一样,一旦定义好了,就不允许改变,namedtuple本身是一个tuple的子类,使用namedtuple可以创建出一个新类,这个新类是tuple的子类,然后我们用这个新类就可以实例化出几个不可变的对象出来;
    namedtuple接受两个参数,第一个参数是生成的新类的类名,第二个参数为这个类有的数据属性的名称,当创建好新类之后,我们就可以对这个类进行实例化,实例化成一个具有tuple特性对对象;
from collections import namedtuple

People = namedtuple('P', ['name','age'])
# Point为一个接受新类的标识符,换而言之,就是Point指向了新类在内存中的地址;
# P为新类的名称,也就是通过namedtuple创建出来的类的类名;
# 'x,y'则是创建出的新类的数据属性的名称;

p1=People("cce",18) # 实例化创建出的新类
print(type(p1)) # <class '__main__.P'>  打印p1对象所属的类
print(isinstance(p1,People)) # True  可以看到p1就是People类的实例,其实People就是P
print(p1) # P(name='cce', age=18)  仅用于显示该实例具有哪些数据属性
print(p1.name) # cce
print(p1.age) # 18

bytes(不可变数据结构)

    bytes从名称上直接理解的话就上字节,bytes指的其实就上在内存中连续排放的字节序列,它主要是解决字符串的问题,因为字符串是字符序列,也是在内存中连续排放的字符序列,因为字符串是字符序列,那么使用字符串去描述类型中文这种数据类型的时候,实际上就是用的多字节序列,那么多字节序列就不能用一个字节来描述,所以它就是一个字符序列;
    本质上来看计算机里面放置都是0和1,一个0或者1都是一个位,每8位都是一个字节,所有东西都可以当字节序列理解,这没问题,但是人不容易理解,人在使用字符的时候,希望的是一个个字符,这样实际上就跟字节序列就有了差别;
    那么对于计算机的字符序列,计算机不会管到底是字符还是什么,因为计算机都是一个字节一个字节理解的,不管是字符串还是整形,比如我们要用计算机表示一个十进制60000,那我们只能用多个字节,因为一个字节描述不了,两个字节就可以描述了,所以这个时候,我们往往使用的都是多字节序列;
    那么对于字符串在计算机中存储也需要多个字节,所以多个字节,我们是必须使用到的,如果在内存中,不加区分,计算机根本不知道到底是数字还是字符串,因为在内存中都是0和1,所以说,为什么高级语言有数据类型呢,内存中都是0和1,我们只有按照某种数据类型去理解它,它才有这个意思了,否则的话,它就是简单的0和1组成的字节而已;
    所以在Python3中提供了一种字节序列的用法,字节序列分两种一种是不可变的bytes字节序列和可变的bytearray字节数组,bytearray其实和列表一样;
bytes与字符串
    字符串是字符组成的有序序列,那么字符序列放在内存中还是得有一个一个字节的0和1组成,也就是说还是得由一个字节组成,说到底还是bytes,但是有字符就得有编码;
bytes与编码
    编码是为字符串服务的,编码的作用就是,在计算机中拿几个字节,去理解这个数值,拿10个字节还是拿2个字节去理解这个数据,那么bytes呢,就不管是什么值,整数也罢、字符串也罢哪怕是个列表也罢,反正对于bytes来说,都是0和1组成的,8位8位的连续的字节序列;
bytes与字符串的转换
    比如说我们有个字符串,要得到它的bytes那么直接使用encode方法即可获取到,并且在内部我们可以传入使用什么编码将字符串转换为bytes,python3默认使用的编码是utf8类型,最终得到了一个b开头的bytes值,这个b只是显示的告诉我们,它是一个bytes,bytes是不可变的,一旦定义好了就是一个常量;
    那么我们希望将一个bytes转换为字符串,使用decode即可,即可返回一个当前语言的合法数据类型,一般是字符串,这种转来转去的操作,在互联网上大量的使用;
print("蔡大爷".encode(encoding="utf8")) # b'\xe8\x94\xa1\xe5\xa4\xa7\xe7\x88\xb7'
print("蔡大爷".encode(encoding="gbk")) # b'\xb2\xcc\xb4\xf3\xd2\xaf'
print(b'\xb2\xcc\xb4\xf3\xd2\xaf'.decode("gbk")) # 蔡大爷

编码表

    最经典的编码也就是ASCII,ASCII码表如下,称之为美国信息交换表,ASCII码表刚开始是一个单字节编码表,因为计算机里面放的只能是0和1,0和1天生就是用来描述数据的,那么这个时候遇到ABC这样字母就犯难了;
    因为数字在计算机中天生可以使用0和1表示,这个没问题,那么对于ABC这样的字符呢,就无法表示了,所以有人就写了一张表,也就是码ASCII表,用一个数字代表一个字符,所以就直接将A-Za-z加入了这个表,也就是下面的表,就拿一个数字代指一个字符,比如9就是\t,A就用41表示,只要我们在内存里面放一个十六进制的41就代表A,十进制的41那么就是),当然,前提是我们需要告诉它是什么表;
    表是用来查的,如果放置的不是数字,而是字符串,就需要用这个数字查询这种表,也就是说我们无法去描述字符,所以就建立了一张编码表,然后我们将数据存储到内存时,因为内存天生就支持整形类型,我们就用它天生支持的数字告诉它,数据是字符串就需要去查表,比如这个数字就是13,那么得到的结果就是回车符;

    那现在突然有一个要求了,我们想要描述一个A,那么我们就需要将这个有A的编码表给它建立出来,所以说这样一张ASCII码表就建立出来了,它表示了128种类型也就是10000000,因为单个字节能够描述256个状态,也就是11111111,从0到255,所以前128个状态用来描述ACSII码表,后面那个128-255也就是01111111就用来设计了扩展ASCII码表,但是扩展ASCII码表就不是谁说了算了,比如欧洲某些国家,就把这块重新定义了;
    那也就是说,内存中的一个数字,我们想把它转换成一个字符,我们就得告诉它使用哪张表,因为不同的表的扩展表,实现了不同的数字和映射,比如一个我们自己定义的表,将十进制41换成了B,如果我们不指定使用哪张表的话,那么我们就无法将这个41转换成B,所以说内存中一个字节,我们想要把它对应到一个字符上去,我们就需要明确的告诉计算机使用哪张编码表;
    编码实现的是内存中的数字与字符的对应关系,但是编码表太多了,每个国家都可以根据字节的实际情况,然后定义自己的编码表,但是呢,大多数在设计编码表的时候,绝大多数都兼容ASCII码表,也就是说,大多数的编码表的0-127全部保留不动,留给ASCII码表,我们现在看到的编码类型,也全部都将0-127都留给了标准的ASCII码表,然后128-255就各有不同了;
    然后由于中文的表示方法128-255是不够用的,所以就引入了双字节,这样的话中国就引入了一个编码表,GB2312,后来由于GB2312对繁体字并不支持,所以对其进行了扩展,也就是GBK,又称GBK大字符集,简而言之就是将所有亚洲文字的双字节字符,包括简体中文,繁体中文,日语,韩语等,都使用一种格式编码,兼容所有平台的上的语言。GBK大字符集包含的汉字数量比GB2312和BIG5多,使得汉字兼容足够使用;
    由于各个国家使用的表不一样,那么这就带来问题了,如果我们除了像ASCII码表以外,我们既想使用中文又想日文的话,那就有问题了,因为一般一篇文章我们只能指定一个编码格式,所以一旦我们指定了一种编码, 那么这篇文章所有的字符都将使用指定的这张编码表,我们想在里面描述一个日文,是无法做到的;
    所以当时为了方便,我们中文编码的时候,像ASCII码一样,留下了一段区域,可以用于扩展,但是我们也不能把全球的语言都包含,那么这个时候, 世界上就出现一种标准编码,全球编码,这个编码就是unicode,统一了全球的编码,将全球的所有文字,全部涵盖在内,利用2个字节来描述全球编码;
    utf-8是unicode之后衍生出来的编码方式,这种编码方式是一种多字节的编码方式,它和unicode之间会做一次转换,utf-8可以将unicode字符转换成1-6个字节,目前我们常看到的转换是1-3个字节,中文大多数转换完之后是3个字节,目前最主流的也就是utf-8,unicode虽然兼容来ASCII码表,但是它是双字节的,如果我们要表示字母A,用unicode表示就是\x41,\x表示是十六进制,41表示对应的数,用2个字节来描述A,会浪费空间,而utf-8用来描述字符的,A只会使用一个字节,说白了utf-8会根据不同的语言来进行不同长度大小的存储;
    我们为了解决内存中的数据能够描述字符,我们必须将内存中的数据给它设定一个类型,这个类型往往是一个字符串,并且还需要给定一个编码类型,如果说我们要做的是国际化的,全球通用的,这个时候,往往我们得选择unicode或者utf-8,因为他们是全球编码的,全球各个地方的语言都能够解码,编码说到底就是如何理解内存中的数据;
bytes定义
    bytes一旦定义,就不允许更改;        
print(bytes()) # b''
print(b"") # b''
print("".encode()) # b''
# 创建长度为10的bytes,用0填充
print(bytes(10)) # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
# 查询十进制对应的字符
print(bytes([13])) # \r

bytearray(可变数据结构)

    bytearray是可变的,类似列表,列表有的方法,bytearray也有,只不过在插入修改等操作的时候,需要传入十进制数值或者十六进制ASCII码的表示法;

切片

    利用python解决问题的过程中,经常会遇到从某个对象中抽取部分值的情况。“切片”操作正是专门用于实现这一目标的有力武器。理论上,只要条件表达式得当,可以通过单次或多次切片操作实现任意目标值切取。切片操作的基本语法比较简单,但如果不彻底搞清楚内在逻辑,也极容易产生错误,而且这种错误有时隐蔽得较深,难以察觉。本文通过详细例子总结归纳了切片操作的各种情形,下文均以list类型作为实验对象,其结论可推广至其他可切片对象;
切片索引方式
    如图所示,这就是Python的切片索引方式,它包括正负索引两部分;

    一个完整的切片,应该包含":",用于分割三个参数(start_index,stop_index,step),当只有一个":"时,默认第三个参数,step为1,当一个":"都没有时,start_index=end_index,表示切取start_index指定的那个元素;
step : 正负数均可,其绝对值大小决定了切取数据时的"步长",而正负号决定了"切取方向",正表示"从左往右"取值,负表示"从右往左"取值。当step省略时,默认为1,即从左往右以步长1取值,切取方向非常重要!

start_index : 表示起始索引,该参数省略时,表示从对象"端点"开始取值,至于是从"起点"还是从"终点"开始,则由step参数的正负决定,step为正从"起点"开始,为负从"终点"开始。

end_index : 表示终止索引,该参数省略时,表示一直取到数据"端点",至于是到"起点"还是到"终点",同样由step参数的正负决定,step为正时直到"终点",为负时直到"起点"。
    切片在日常开发中也是使用较为频繁的操作,其中针对切片来说,个人总结三大原则,如下;
原则一:索引为0开始,并非为1开始;
原则二:step意为步进,默认情况步进为正整数1,当step为负整数-1时,从右往左取值,是取值,并非反转;
原则三:当start_index为空,表示从起始开始取,至于这个起始是开头还是结尾,主要是看step是正数还是负数、stop_index为空时,就表示直接取到此方向的终点,并非取到0;
切取单个值
    切取单个值,只需要在start_index、stop_index这两个参数中,任意给定一个参数即可,遵循上面的正负原则;
container = [1, 2, 3, 4, 5, 6]
# 正负都有不同的表示,正数表示从起点开始,负数表示从终点开始
print(container[1], container[-1])  # 2 6   索引是从0开始的,所以正数1表示容器里面的第二个数
切取完整对象
    切取完整对象分多种情况,给定一个或者两个":"表示切取从头到尾所有的值,另外,step默认为1,当step为-1时,那么将返回一个倒序排列的结果;
container = [1, 2, 3, 4, 5, 6]

# 给定一个":",即切取从开头到结尾所有元素
print(container[:])  # [1, 2, 3, 4, 5, 6]
# 两个"::"也是一样的,也表示切取从开头到结尾所有元素
print(container[::])  # [1, 2, 3, 4, 5, 6]

# 因为step默认为正整数1,那么当step为负数时,表示从右往左取值,并且step为step的值,如下step为-1,即容器倒序,如果为-2,那么倒序排列,并且步进为2
print(container[::-1])  # [6, 5, 4, 3, 2, 1]

# 浅拷贝
print(container.copy())  # [1, 2, 3, 4, 5, 6]
切取奇偶数
    我们也可以利用step步进这个参数来切取奇偶数,因为step表示的就是当取出第一个值的时候,切取下一个值需要距离现在值的索引距离是几。
# 取奇数
print(container[::2])  # [1, 3, 5]

# 取偶数
print(container[1::2])  # [2, 4, 6]
切片进阶操作
    对于进阶操作,可能比较绕,涉及到正数、负数针对三个参数的轮换,较为复杂,但是,但是先要知道一点,就是step就两种变化,正数即正序步进,负数即列表反转加步进,如下示例;
container = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
均为正整数的情况
    均为正数,即start_index、stop_index和step三个参数,都是正数的情况,正常逻辑取之即可,直接从左到右取值,如下;
# 从左往右包前不包后
print(container[1:5])  # [1, 2, 3, 4]

# step=-1,决定了从右往左取值,而start_index=1到end_index=6决定了从左往右取值,两者矛盾,所以为空。
print(container[1:5:-1])  # []  输出为空列表,说明没取到数据

# step默认等于正数1,那么step=1,决定了从左往右取值,而start_index=6到end_index=2决定了从右往左取值,两者矛盾,所以为空。
print(container[6:2])  # [] 同样输出为空列表。

# step=1,表示从左往右取值,而start_index省略时,表示从端点开始,因此这里的端点是“起点”,即从“起点”值0开始一直取到end_index=10(该点不包括),即使end_index超出索引,也无碍。
print(container[:10])  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# step=-1,从右往左取值,而start_index省略时,表示从端点开始,因此这里的端点是“终点”,即从“终点”值9开始一直取到end_index=6(该点不包括)。
print(container[:6:-1])  # [9, 8, 7]

# step=1,从左往右取值,从start_index=6开始,一直取到“终点”值9。
print(container[6:])  # [6, 7, 8, 9]

# step=-1,从右往左取值,从start_index=6开始,一直取到“起点”0。
print(container[6::-1])  # [6, 5, 4, 3, 2, 1, 0]
均为负整数的情况
    均为负整数,即start_index、stop_index和step三个参数,都是负数的情况,一旦加入负数就稍微复杂起来了,因为存在反向取值,从右到左取值,如下;
# step=1,从左往右取值,而start_index=-1到end_index=-6决定了从右往左取值,两者矛盾,所以为空。
print(container[-1:-6])  # []

# step=-1,从右往左取值,start_index=-1到end_index=-6同样是从右往左取值。
print(container[-1:-6:-1])  # [9, 8, 7, 6, 5]

# step=1,从左往右取值,而start_index=-6到end_index=-1同样是从左往右取值。
print(container[-6:-1])  # [4, 5, 6, 7, 8]

# step=1,从左往右取值,从“起点”开始一直取到end_index=-6(该点不包括)。
print(container[:-6])  # [0, 1, 2, 3]

# step=-1,从右往左取值,从“终点”开始一直取到end_index=-6(该点不包括)。
print(container[:-6:-1])  # [9, 8, 7, 6, 5]

# step=1,从左往右取值,从start_index=-6开始,一直取到“终点”。
print(container[-6:])  # [4, 5, 6, 7, 8, 9]

# step=-1,从右往左取值,从start_index=-6开始,一直取到“起点”。
print(container[-6::-1])  # [4, 3, 2, 1, 0]
混合索引的情况
    混合索引,即start_index、stop_index和step三个参数,同时存在负数和正数的情况,这种情况可能更加的复杂,如下;
# start_index=1在end_index=-6的左边,因此从左往右取值,而step=1同样决定了从左往右取值,因此结果正确
print(container[1:-6])  # [1, 2, 3]

# start_index=1在end_index=-6的左边,因此从左往右取值,但step=-则决定了从右往左取值,两者矛盾,因此为空。
print(container[1:-6:-1])  # []

# start_index=-1在end_index=6的右边,因此从右往左取值,但step=1则决定了从左往右取值,两者矛盾,因此为空。
print(container[-1:6])  # []

# start_index=-1在end_index=6的右边,因此从右往左取值,而step=-1同样决定了从右往左取值,因此结果正确。
print(container[-1:6:-1])  # [9, 8, 7]

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注