第二章--序列构成的数组

第二章–序列构成的数组

2.1 python内置序列类型

  • 按存储类型来分
容器系列 list、tuple、collections.deque 能存放不同类型的数据
扁平系列 str、bytes、bytearray、memoryview、array.array 只能存放特定的一种类型的数据


容器系列存放的是对象的引用,扁平系列存放的是值,是一段连续的内存空间
  • 按能否被修改来分
可变序列 list、bytearray、array.array、collections.deque 和 memoryview
不可变序列 tuple、str 和 bytes
                             
 
 Sequence是不可变序列,MutableSequence是可变序列,图中列举了两者的一些方法

2.2 生成列表与生成器表达式

生成列表略 把列表推到的[]改成()就成了生成器表达式,前者返回整个列表,后者返回生成器

2.3 元祖与拆包

  • 拆包(unpacking)
    
  1. 拆包可以用于任何可迭代对象上,但对字典进行拆包智能拿到key值
  2. 拆包时要注意变量数目左右要一直
  3. *arr 可以对arr进行拆包 (命令行下不能用)
  4. 函数中有一种特殊的拆包fun(a,*args,**kargs) args是元祖,接受多余的无命名参数,kargs是字典,接受多余的命名参数
  5. 可以用*来接收剩下的元素
a,b,*rest=range(5) #0 1 [2, 3, 4]
a,*rest,b,c=range(5)# 0 [1, 2] 3 4
  • 两位数交换可以用 a,b=b,a
  • 可以用collections.namedtuple代替元祖
    namedtuple的一些使用函数 _fields _make _asdict
from collections import namedtuple

Student=namedtuple('Student',['name','grader'])

stu=Student('jason',2)
stu.name #jason
Student._fields # ('name', 'grader')
stu1=Student._make(('mike',3)) # Student(name='mike', grader=3)
stu1._asdict() # OrderedDict([('name', 'mike'), ('grader', 3)])

2.4 切片

  • 多维切片
    []运算符中可以使用逗号分开的多个索引或者切片,numpy就用了这个特性
    二维的 numpy.ndarray 就可以用 a[i, j] 这种形式来获取,抑或是用 a[m:n, k:l]
    的方式来得到二维切片
    对象的特殊方法 __getitem__ 和__setitem__ 需要以元组的形式来接收 a[i, j] 中的索
    引。
    python内置的序列类型都是一维的,只支持单一索引

  • 给切片赋值,等式右边必须是一个可迭代对象

arr=[1,2,3,4,5,6]
arr[2:4]=[999]*3
print(arr)
arr[2:4]=[888]
print(arr)
arr[2::3]=[777,666]
print(arr)
arr[2:3]=777 # raise exception
#[1, 2, 999, 999, 999, 5, 6]
# [1, 2, 888, 999, 5, 6]
# [1, 2, 777, 999, 5, 666]

2.5 对序列使用+和*

对序列使用+ 和* 不会修改原来的序列进行修改,会返回新的序列结果
注意嵌套列表的重复引用问题

arr=[[0]*3]*3
arr[0][0]=1
print(arr) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
arr=[[0]*3 for i in range(3)]
arr[0][0]=1
print(arr) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

2.6 序列的增量赋值

增量赋值运算符 += 和 = 的表现取决于它们的第一个操作对象
+=跟
=的概念是一样的,只介绍+=
+= 背后的特殊方法是__iadd__ 如果一个类调用+=时没有实现iadd,python会调用它的add
ex: a+=b 如果a类没有实现iadd方法,就会调用add方法


class ClassA():
    def __init__(self,num=1):
        self.num=num
    def __add__(self, other):
        return ClassA(self.num+other)
    # def __iadd__(self, other):
    #     self.num+=other
    #     return self

class ClassB():
    def __init__(self,num=1):
        self.num=num
    def __add__(self, other):
        return ClassB(self.num+other)

    def __iadd__(self, other):
        self.num+=other
        return self

#a 只实现了add方法,可以看到调用a+=1前后a的id不一样,说明调用了add方法
a=ClassA()
print(a.num, id(a))
a+=1
print(a.num, id(a))
# 1 2335603349376
# 2 2335603349488

#b实现了iadd 所以直接用iadd的方法,没有用add,所以b+=1前后id一致
b=ClassB()
print(b.num,id(b))
b+=1
print(b.num,id(b))
# 1 2335603349432
# 2 2335603349432

对不可变序列进行拼接操作效率会很低 比如对元祖t 执行t*=2 t+=(2,3)不会直接在t内进行修改,而是计算得出结果保存到新变量,再把新变量赋值给t 并没有就地赋值
但str是一个例外,str是不可变序列,但经常有s+=‘abc’这种操作,所以CPython进行了优化,让str支持就地赋值,在str初始化内存时留出额外的内存空间,这样可以一定程度上避免str的移动

一个很神奇的问题
在这里插入图片描述
在这里插入图片描述
用dis模块查看了s[a]+=b的字节码,可以找到原因
在这里插入图片描述

从这个问题学到的经验

  1. 不要把可变对象放到不可变序列中
  2. 用dis查看python字节码对调试非常有帮助
  3. 不可变序列的增量赋值不是原子操作

list.sort方法和内置函数sorted

python一些对数据进行就地修改的函数会返回None值,提醒你不会返回新的数据,比如list.sort() random.shuttle()

sorted相反,它最终会返回一个排好序的新列表,它接受任何可迭代对象作为参数,并一直返回新列表

两个排序函数都有的常用参数有reverse跟key

bisect–二分查找算法

bisect模块包含两个主要函数bisect和insort,两个函数都利用二分查找算法在有序序列中查找插入元素

bisect其实是bisect_right,还有对应的biset_left
bisect_left 返回的插入位置是原序列中跟被插入元素相等的元素的位置,
也就是新元素会被放置于它相等的元素的前面,而 bisect_right 返回的则是跟它相等的元素
之后的位置。这可能会导致不同,因为两个元素相等但不一定相同,比如1跟1.0

def grade(score,breakpoints=[60,70,80,90],grade='FDCBA'):
    import bisect
    i=bisect.bisect(breakpoints,score)
    return grade[i]

print(list(map(grade,[33, 99, 77, 70, 89, 90, 100])))#['F', 'A', 'C', 'C', 'B', 'A', 'A']

insort也有对应的insort_left,都实现对有序序列的插入后保持序列有序

###列表的其他替代品

  • array模块
    如果我们只需要存储一种类型的序列化数据,array.array比list更高效,array.array支持所有list相关的操作,并且提供更快的文件读取方法如.frombytes .tofile
    创建array时需要提供类型码,这个类型码用于表示底层c语言的数据类型,数据存储是连续的
import array

arr=array.array('i',[0,1,1,3])
print(arr) # array('i', [0, 1, 1, 3])
print(arr.typecode) # i
print(arr.itemsize) # 4
print(arr.buffer_info()) #(2050462494912, 4) 返回的是数组的开头地址跟数据个数
print(arr.count(1))#1
arr.pop(3)
print(arr) # array('i', [0, 1, 1])
arr.remove(0)
print(arr) # array('i', [1, 1])
print(array.typecodes) #bBuhHiIlLqQfd

  • memoryview

memoryview() 函数返回给定参数的内存查看对象(Momory view)。所谓内存查看对象,是指对支持缓冲区协议的数据进行包装,在不需要复制对象基础上允许Python代码访问。
memoryview是内置函数,它能让用户在不复制内容的情况下操作同一个数组的不同切片
http://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/

内存视图其实是泛化和去数学化的 NumPy 数组。它让你在不需要复制内容的前提下,
在数据结构之间共享内存。其中数据结构可以是任何形式,比如 PIL 图片、SQLite
数据库和 NumPy 的数组,等等。这个功能在处理大型数据集合的时候非常重要。

memoryview.cast能用不同的方式读写同一块内存数据,而且内容字节不会移动

import array

numbers=array.array('h',[-2,-1,0,1,2]) #h表示短整型有符号整数 两个字节一个数
memv=memoryview(numbers)
print(len(memv)) # 5
print(memv[0])# -2
memv_oct=memv.cast('B') # B表示无符号字符,一个字节
print(memv_oct.tolist()) # [254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
memv_oct[5]=4 # 低字节的在数据前面,高字节在后面,所以index=5的是numbers[2]的高位字节
print(numbers) # array('h', [-2, -1, 1024, 1, 2])
  • 队列
  1. 双向队列 collections.deque 线程安全
  2. queue模块 线程安全 提供了Queue、LifoQueue 和 PriorityQueue 三者都有一个参数maxsize,限定队列的大小,满员时不会扔掉元素,而是会锁住当前线程,等待其他线程移除了元素腾出位置
  3. multiprocessing模块 实现了用于多进程通信的Queue,还有一个multiprocessing.JoinableQueue 允许项目的使用者发送通知通知队列的生成者数据已经被处理
    用的是task_done函数
  4. asyncio 主要实现了协程层面的队列 Queue、LifoQueue、PriorityQueue 和 JoinableQueue
  5. heapq 提供heappush heappop方法,让用户把可变序列当做堆序列或优先队列来使用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章