流畅的Python学习笔记(2)序列

        Python标准库用C语言实现了丰富的序列类型,例如扁平序列、容器序列。

        扁平序列可以存放一种简单类型的项,例如str、bytes,他更紧凑;

        容器序列可以存放不同类型的项,例如列表、字典。他存放的是所包含对象的引用,对象可以是各种类型。

        也可以分为可变序列和不可变序列,前者继承了后者所有的方法并且还有自己的方法。例如list是可变容器。

列表推导式和生成器表达式

symbols = 'abc#$%^'
codes = []
for symbol in symbols:
    print(symbol)
    codes.append(ord(symbol))
print(codes)

        ord()可以将字符转化为所需要的ASCII码,这是很简单的输出列表的做法,用for循环跟append。还有一个简单的方法,直接方便又快捷,这就是列表推导式如下:

symbols = 'abc#$%^'
codes = [ord(symbol) for symbol in symbols]
print(codes)

        列表推导式可以根据多个可迭代对象的笛卡尔积构建列表。笛卡尔积的每一项是一个元组,由各个输入的可迭代对象的项组成,得到的列表长度是各个输入长度的乘积。列表推导式作用单一只有构建列表(生成元组、数组啥的占内存太大),生成其他序列需要生成器表达式。

colors = ['black', 'white', 'yellow']
sizes = ['S', 'M', 'L', 'XL', 'XXL']
output = [(color, size) for color in colors for size in sizes]
for color in colors:
    for size in sizes:
        print((color,size))

        生成器表达式的用法和列表推导式的区别在于[]换成了()。生成器表达式如果是函数的唯一参数,则不需要额外加(),但是array接受了两个参数,就要在生成器表达式上加(),array第一个参数指定了数组中数值的存储类型。

import array
symbols = 'abc#$%^'
output1 = tuple(ord(symbol) for symbol in symbols)
print(output1)
output2 = array.array('I', (tuple(ord(symbol) for symbol in symbols)))
print(output2)

        如果用生成器表达式去写笛卡尔积,它可以大量节省内存,如果输入两个长度是1000的列表,列表生成器需要先创建长度是1000*1000的列表再给for循环,而生成器表达式一次产出一项再给for循环。

colors = ['black', 'white', 'yellow']
sizes = ['S', 'M', 'L', 'XL', 'XXL']
for tshirt in (f'{c} {s}' for c in colors for s in sizes):
    print(tshirt)

       元组不仅仅是不可变

        元组两个作用:1.不可变列表。2.用作没有字段名称的记录。

        作用1:元组长度不可变;如果跟列表长度相同,占用内存更小。但是元组的不可变性仅仅是针对元组的引用来说,元组的引用不可删除不可替换。如果引用的是可变对象,改变其对象,那么元组也可变。把元组当做列表的不可变体使用时,元组支持列表除涉及增删外的所有方法

a = (10, 'abu', [1, 3])
b = (10, 'abu', [1, 3])
print(a ==b)
b[2].append(22)
print(a == b)

如果我们想判断一个元组的值是否固定,可以用内置的hash函数定义以下的函数:

def fixed(tuple):
    try :
        hash(tuple)
    except TypeError:
        return False
    return True

a = (10, 'abu', [1, 3])
b = (10, 'abu', (1, 3))
print(fixed(a))
print(fixed(b))

        作用2:用元组存放记录,元组的每一项对应一个字段的数据,项的位置决定了数据的意义。我们把元组当做字段的容器时候,字段很重要,顺序也很重要。在这时候,元组不能排序,否则信息的含义会遭到破坏。元组拆包,是一个赋值表达式,效果是,将=(赋值操作符)右边的元组的元素拆开,赋值给=左边的多个变量。这里有一个要求,那就是=左边的变量个数必须与元素的长度一致。

city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)

序列和可迭代对象拆包

        拆包不用我们用索引去提取元素,拆包的目标是所有可迭代对象,拆包对可迭代的对象唯一要求是一次只能产出一项。最明显的拆包是并行赋值,还可以对调两个变量的值。

a = (1,2)
a1, a2 = a
print(a1)
print(a2)
a2, a1 = a1, a2
print(a1)
print(a2)

        拆包为函数返回多个值提供了一种快捷方式。例如os.oath.split()根据输入的系统路径构建元组(path,last_part)。

import os
_, file_name = os.path.split('D:\pythonProject2\外星人大战\alien.py')
print(_)
print(file_name)

        Python一个经典特性用*arg获得剩下的任意数量的参数,这个也可以用在并行赋值上。并行赋值中*只可以用于一个变量上,不过位置任意。

a, b, *rest = range(5)
print(a)
print(b)
print(rest)
a, b, *rest = range(2)
print(a)
print(b)
print(rest)
a, *b, rest = range(5)
print(a)
print(b)
print(rest)

        在函数调用中多次使用*拆包。

def fun(a, b, c, *rest):
    return a, b, c, rest
print(fun(*[1,2], 3, *range(4, 7)))
print(type(fun(*[1,2], 3, *range(4, 7))))

(1, 2, 3, (4, 5, 6))
<class 'tuple'>

        嵌套拆包,获取经度;

metro_areas = [
('Tokyo','JP',36.933,(35.689722,139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]
print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.'))      # 格式化输出, :后面表示条件 :15即表示占用15个长度字符位置 :^9表示占用9个字符长度,并居中
fmt = '{:15} | {:9.4f} | {:9.4f}'                               # .4f 表示取浮点数后四位
for name, cc, pop, (latitude, longitude) in metro_areas:
    if longitude <= 0:  # 只要西半球
        print(fmt.format(name, latitude, longitude))

序列模式匹配

        match/case语句实现的模式匹配,通用语法如下,match关键字后边的表达式是匹配对象subject,也就是各个case子句中的模式尝试匹配的数据:

match subject:
    case <pattern_1>:
        <action_1>
    case <pattern_2>:
        <action_2>
    case <pattern_3>:
        <action_3>
    case _:
        <action_wildcard>

        则重构上述例子如下:

metro_areas = [
('Tokyo','JP',36.933,(35.689722,139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]
print('{:15} | {:^9} | {:^9}'.format('', 'latitude', 'longitude'))      # 格式化输出, :后面表示条件 :15即表示占用15个长度字符位置 :^9表示占用9个字符长度,并居中
fmt = '{:15} | {:9.4f} | {:9.4f}'  # .4f 表示取浮点数后四位
for record in metro_areas:
    match record:
        case [name, _, _, (latitude, longitude)] if longitude <= 0:
            print(fmt.format(name, latitude, longitude))

切片

        切片和区间排除最后一项是Python风格约定,能够判断并控制切片长度。

l = [10, 20, 30, 40, 50, 60, 70]
print(l[:2])
print(l[2:])

        众所周知,我们可以用s[a:b:c]来指定步距c,c可正可负,为负则为返回项。a:b:c表示法只在[]有用,表示索引或者下标运算符,得到的是一个切片对象slice(a,b,c),如果我们要处理切片那种纯文本数据,可以用切片命名商品信息。

l = [10, 20, 30, 40, 50, 60, 70]
print(l[2::2])

l = [0, 10, 20, 30, 40, 50, 60, 70]
a = slice(2,7,2)
print(l[a])

          []运算符还能接受多个索引或切片,用逗号分割。负责处理[]运算符的特殊方法_getitem_和_setitem_把接受到的a[i,j]中的索引当做元组,也就是为了求解a[i,j],Python调用了a._getitem_((i,j))。二维数组可以通过一维切片获得数组中的行元素,也可以用二维切片获得小的二维数组。

import numpy as np
a=np.random.randint(0,10,size=[4,5])
print(a)
print(a[2:4])
print(a[...,2:4])
print(a[2:4,2:4])

[[5 2 3 0 0]
 [4 7 8 3 6]
 [6 6 2 8 5]
 [2 9 2 6 5]]
[[6 6 2 8 5]
 [2 9 2 6 5]]
[[3 0]
 [8 3]
 [2 8]
 [2 6]]
[[2 8]
 [2 6]]

        在赋值语句中使用切片,可以移植、切除、更改可变序列:

l = list(range(10))
print(l)
l[2:5] = [1]
print(l)
del l[1:3]
print(l)
l[0]=1
print(l)
l[2::3] = [0, 0]
print(l)

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 1, 5, 6, 7, 8, 9]
[0, 5, 6, 7, 8, 9]
[1, 5, 6, 7, 8, 9]
[1, 5, 0, 7, 8, 0]

使用*或者+处理序列

        +的两个序列必须是同一种序列,且不可修改。

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [0, 1, 1, 5, 6, 7, 8, 9]
print(a+b)

        *的是多次拼接同一个序列。

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(5*a)

        有时候我们需要初始化内部嵌套一定数量列表的列表,最简单的办法就是列表推导式:

board = [['_']*5 for i in range(4)]
print(board)
board[2][0] = 1
print(board)

[['_', '_', '_', '_', '_'], ['_', '_', '_', '_', '_'], ['_', '_', '_', '_', '_'], ['_', '_', '_', '_', '_']]
[['_', '_', '_', '_', '_'], ['_', '_', '_', '_', '_'], [1, '_', '_', '_', '_'], ['_', '_', '_', '_', '_']]

        但是不能这么写,二者的区别在于第一种情况时每次迭代创造一个新的['_', '_', '_', '_', '_'],追加到board上;第二种情况就是同一个['_', '_', '_', '_', '_']向board中追加了三次

board = [['_']*5]*5
print(board)
board[2][0] = 1
print(board)

[['_', '_', '_', '_', '_'], ['_', '_', '_', '_', '_'], ['_', '_', '_', '_', '_'], ['_', '_', '_', '_', '_'], ['_', '_', '_', '_', '_']]
[[1, '_', '_', '_', '_'], [1, '_', '_', '_', '_'], [1, '_', '_', '_', '_'], [1, '_', '_', '_', '_'], [1, '_', '_', '_', '_']]

        也可以用增量赋值运算符处理序列,例如+=,*=。以下代码说明对于可变序列,乘法运算之后列表还是同一个对象,只是追加了几项。

l = [1, 2, 3]
print(id(l))
l*=2
print(l)
print(id(l))

2534041362176
[1, 2, 3, 1, 2, 3]
2534041362176

        而对于不可变序列来说,创建了一个新的元组,对象发生了改变。

l = (1, 2, 3)
print(id(l))
l*=2
print(l)
print(id(l))

1308885227008
(1, 2, 3, 1, 2, 3)
1308929650592

list.sort与内置函数sorted

        list.sort就地排序列表,它不创建副本,返回值为None,而sorted不一样,他能接受认可可迭代对象作为参数,包括不可变序列和生成器,他返回始终创建新的列表。

a = ['www', 'google', 'com']
print(sorted(a))
print(a)
print(sorted(a,reverse=True))
print(sorted(a,reverse=True,key=len))
print(a.sort())
print(a)

['com', 'google', 'www']
['www', 'google', 'com']
['www', 'google', 'com']
['google', 'www', 'com']
None
['com', 'google', 'www']

当列表不适用时

        如果一个列表只有数值,那么使用array.array更高效。数组支持所有可变序列的操作,还有迅速加载项和保存项的方法。接下来创建、保存和加载一个大型浮点型数数组。

        使用 array.array 函数可以创建一个数组对象,如下:array.array(typecode[, initializer])其中:typecode 是数组中元素的类型代码,表示数组中的元素类型。常用有:'b''B''u''h''H''i''I''l''L''f''d' 等,分别表示有符号字节、无符号字节、Unicode字符、有符号短整数、无符号短整数、有符号整数、无符号整数、有符号长整数、无符号长整数、浮点数和双精度浮点数。initializer 是可选参数,用于初始化数组。可以是一个可迭代对象,也可以是一个字符串,如果提供该参数,则会使用提供的数据初始化数组;如果不提供,则创建一个空的数组。

  tofilefromfilearray.array 对象的两个方法,用于将数组数据存储到文件和从文件中读取数据。tofile(file) 方法将数组中的数据按照二进制格式写入到一个文件中。其中,file 是一个打开的文件对象,需要以二进制模式(.bin)打开。fromfile(file, count) 方法从一个打开的文件对象中读取二进制数据,并将其加载到数组对象中。其中,file 是一个打开的文件对象,需要以二进制模式打开;count 是要读取的元素个数。

from array import array
from random import random
flaots = array('d',(random() for i in range(10**7)))
print(flaots[-1])
fp = open('floats.bin', 'wb')
flaots.tofile(fp)
fp.close()
flaots2 = array('d')
fp = open('floats.bin', 'rb')
flaots2.fromfile(fp,10**7)
fp.close()
print(flaots2[-1])

        内置的memoryview类是一种共享内存的序列类型,可以在不复制字节的情况下处理数组的切片,memoryview.cast能够改变读写多字节单元的方式,不需要移位,他返回另外一个memoryview对象,而且始终共享内存。分别以1x6、2x3、3x2的矩阵处理6字节内存,这证明了octets、m1、m2、m3的内存是共享的。tolist() 方法可以将 NumPy 数组转换为与其相对应的 Python 列表,转换后的列表将保留原始数组的维度和元素顺序。:

from array import array
from random import random
octets = array('B', range(6))
print(octets)
m1 = memoryview(octets)
print(m1.tolist())
m2 = m1.cast('B', [2, 3])
print(m2.tolist())
m3 = m1.cast('B', [3, 2])
print(m3.tolist())
m3[1, 1] = 22
m2[1, 1] = 33
print(octets)

array('B', [0, 1, 2, 3, 4, 5])
[0, 1, 2, 3, 4, 5]
[[0, 1, 2], [3, 4, 5]]
[[0, 1], [2, 3], [4, 5]]
array('B', [0, 1, 2, 22, 33, 5])

        memoryview也可以修改数组中某一项的字节,memv.cast('B')把memv的元素转化为字节:

from array import array
numbers = array('h', range(-2, 3))
memv = memoryview(numbers)
print(len(memv))
print(memv[-1])
memv_cot = memv.cast('B')
print(memv_cot.tolist())
memv_cot[0] = 4
print(numbers)

5
2
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
array('h', [-252, -1, 0, 1, 2])

        collections.deque类实现一种线程安全的双端序列,能够快速在两端加入或删除项。如果要保留最后几项,双端序列是唯一的选择,因为deque对象长度固定。deque.rotate()函数用于将deque中的元素进行循环移动。具体来说,它接受一个参数n,表示旋转的步数。当n为正数时,deque中的元素向右循环移动;当n为负数时,deque中的元素向左循环移动。dq.append(item) 的作用是将元素 item 添加到双端队列 dq 的末尾。这样,队列中原有的元素顺序不变,而新的元素被添加在队列的最后面,dq.extend(iterable)是将可迭代对象iterable中的所有元素依次添加到双端队列dq的右侧(队尾),dq.extendleft(iterable)是将可迭代对象iterable中的所有元素依次添加到双端队列dq的左侧(队首)。有界的deque对象填满之后,从一端添加新项就要从另一端删除旧项,处理一个deque对象如下:

from collections import deque
dq = deque(range(10), maxlen=10)
print(dq)
dq.rotate(3)
print(dq)
dq.rotate(-4)
print(dq)
dq.append(-1)
print(dq)
dq.extend([1, 1, 1])
print(dq)
dq.extendleft([10, 15])
print(dq)

deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
deque([2, 3, 4, 5, 6, 7, 8, 9, 0, -1], maxlen=10)
deque([5, 6, 7, 8, 9, 0, -1, 1, 1, 1], maxlen=10)
deque([15, 10, 5, 6, 7, 8, 9, 0, -1, 1], maxlen=10)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值