Python也从ABC那里继承了用统一的风格去处理序列数据这一特点。不管是哪种数据结构,字符串、列表、字节序列、数组、XML元素,抑或是数据库查询结果,它们都共用一套丰富的操作:迭代、切片、排序,还有拼接。

内置序列类型概览

容器序列(存放不同类型的数据)

  • list
  • tuple
  • collections.deque

扁平序列(只能容纳一种类型)

  • str
  • bytes
  • bytearray
  • memoryview
  • array.array

容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是而不是引用。换句话说,扁平序列其实是一段连续的内存空间。由此可见扁平序列其实更加紧凑,但是它里面只能存放诸如字符、字节和数值这种基础类型。

序列类型还能按照能否被修改类分类:

可变序列

  • list

  • bytearray

  • array.array

  • collections.deque

  • memoryview

不可变序列

  • tuple
  • str
  • bytes

可变序列(MutableSequence)和不可变序列(Sequence)的差异

上图显示了可变序列(MutableSequence)和不可变序列(Sequence)的差异,同时也能看出前者从后者那里继承了一些方法。

列表推导和生成器表达式

列表推导是构建列表(list)的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列。

列表推导和可读性

把一个字符串变成Unicode码位的列表:

symbols = 'kevin'
codes = []
for symbol in symbols:
    #返回值是对应的十进制整数
    codes.append(ord(symbol))

print(codes) #[107, 101, 118, 105, 110]

把字符串变成Unicode码位的另外一种写法:

symbols = 'kevin'
codes = [ord(symbol) for symbol in symbols]

print(codes) #[107, 101, 118, 105, 110]
x = 'ABC'
dummy = [ord(x) for x in x]
print(dummy) #[65, 66, 67]
  • x的值被保留了。

  • 列表推导也创建了正确的列表。

列表推导可以帮助我们把一个序列或是其他可迭代类型中的元素过滤或是加工,然后再新建一个列表。

列表推导同filtermap的比较

用列表推导和map/filter组合来创建同样的表单

symbols = 'shy_kevin'
beyond_ascii = [ord(s) for s in symbols if ord(s) > 110]
print(beyond_ascii) #[115, 121, 118]

beyond_ascii = list(filter(lambda c:c >110,map(ord,symbols)))
print(beyond_ascii) #[115, 121, 118]

笛卡儿积

使用列表推导计算笛卡儿积

colors = ['black','white']
sizes = ['S','M','L']
tshirts = [(colors,sizes) for color in colors for size in sizes]
print(tshirts)

for color in colors:
    for size in sizes:
        print((color,size))

输出内容:

[(['black', 'white'], ['S', 'M', 'L']), (['black', 'white'], ['S', 'M', 'L']), (['black', 'white'], ['S', 'M', 'L']), (['black', 'white'], ['S', 'M', 'L']), (['black', 'white'], ['S', 'M', 'L']), (['black', 'white'], ['S', 'M', 'L'])]

列表推导的作用只有一个:生成列表。如果想生成其他类型的序列,生成器表达式就派上了用场。

生成器表达式

生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里。

生成器表达式的语法跟列表推导差不多,只不过把方括号换成圆括号而已。

symbols = 'shy_kevin'
print(tuple(ord(symbol) for symbol in symbols))
#(115, 104, 121, 95, 107, 101, 118, 105, 110)

元组不仅仅是不可变的列表

元组不仅是“不可变列表”,还可以用于没有字段名的记录。

元组和记录

元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。

把元组用作记录

lax_coordinates = (33.9425,-118.408056)
city,year,pop,chg,area = ('kevin',2003,3425,0.66,8014)
traveler_ids = [('USA','3732'),('BRA','CE342567'),('ESP','XDA205856')]

for passport in sorted(traveler_ids):
    print('%s/%s'%passport)

for country,_ in traveler_ids:
    print(country)

for循环可以分别提取元组里的元素,也叫作拆包(unpacking)。因为元组中第二个元素对我们没有什么用,所以它赋值给“_”占位符。

元组拆包

lax_coordinates = (33.9425,-118.408056)
latitude,longitude = lax_coordinates #元组拆包
print('{0}::{1}'.format(latitude,longitude))

在Python中,函数用*args来获取不确定数量的参数

a,b,*rest = range(5)
print(a,b,rest) #0 1 [2, 3, 4]

a,b,*rest = range(2)
print(a,b,rest) #0 1 []

嵌套元组拆包

用嵌套元组来获取经度

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.'))
fmt = '{:15} | {:9.4f} | {:9.4f}'
for name, cc, pop, (latitude, longitude) in metro_areas:
    if longitude <= 0:
        print(fmt.format(name, latitude, longitude))
  • 每个元组内有4个元素,其中最后一个元素是一对坐标。
  • 我们把输入元组的最后一个元素拆包到由变量构成的元组里,这样就获取了坐标。
  • if longitude <=0:这个条件判断把输出限制在西半球的城市。
metro_areas = [('东','小一',(1,2)),
               ('南','小二',(3,4)),
               ('西','小三',(5,6)),
               ('北','小四',(7,8)),]

for direction,name,(latitude, longitude) in metro_areas:
    print("{0},{1},{2}".format(direction,name,(latitude, longitude)))

具名元组

collections.namedtuple是一个工厂函数,它可以用来构建一个带字段名的元组和一个有名字的类。

如何用具名元组来记录一个城市的信息:

from collections import namedtuple
#创建一个具名元组需要两个参数,一个是类名,另一个是类的各个字段的名字。后者可以是由数个字符串组成的可迭代对象,或者是由空格分隔开的字段名组成的字符串。
City = namedtuple('City','name country population coordinates')

#存放在对应字段里的数据要以一串参数的形式传入到构造函数中(注意,元组的构造函数却只接受单一的可迭代对象)。
tokyo = City('Tokyo','JP',36.933,(35.689722,139.691667))
print(tokyo) #City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 139.691667))

#通过字段名或者位置来获取一个字段的信息。
print(tokyo.population)
print(tokyo.coordinates)

作为不可变列表的元组

列表或元组的方法和属性

切片

为什么切片和区间会忽略最后一个元素

  • 当只有最后一个位置信息时,我们也可以快速看出切片和区间里有几个元素:range(3)my_list[:3]都返回3个元素。
  • 当起止位置信息都可见时,我们可以快速计算出切片和区间的长度,用后一个数减去第一个下标(stop-start)即可。
l = [10,20,30,40,50,60]
#在下标2的地方分割
print(l[:2]) #[10, 20]
print(l[2:]) #[30, 40, 50, 60]
#在下标3的地方分割
print(l[:3]) #[10, 20, 30]
print(l[3:]) #[40, 50, 60]

对对象进行切片

s = 'bicycle'
print(s[::3]) #bye
print(s[::-1]) #elcycib
print(s[::-2]) #eccb

纯文本文件形式的收据以一行字符串的形式被解析

invoice = """
0.....6................................40........52...55........
1909  Pimoroni PiBrella                    $17.50    3    $52.50
1489  6mm Tactile Switch x20                $4.95    2     $9.90
1510  Panavise Jr. - PV-201                $28.00    1    $28.00
1601  PiTFT Mini Kit 320x240               $34.95    1    $34.95
"""
SKU = slice(0, 6)
DESCRIPTION = slice(6, 40)
UNIT_PRICE = slice(40, 52)
QUANTITY = slice(52, 55)
ITEM_TOTAL = slice(55, None)
line_items = invoice.split('\n')[2:]
print(line_items)
for item in line_items:
    print(item[UNIT_PRICE], item[DESCRIPTION])

给切片赋值

l = list(range(10))
print(l) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l[2:5] = [20,30]
print(l) #[0, 1, 20, 30, 5, 6, 7, 8, 9]

对序列使用+和*

l = [1,2,3]
print(l*5)  #[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
print(5*'kevin') #kevinkevinkevinkevinkevin

序列的增量赋值

t = (1,2,[30,40])
t[2] += [50,60]
print(t)

# 
# line 2, in <module>
#     t[2] += [50,60]
# TypeError: 'tuple' object does not support item assignment
  • 不要把可变对象放在元组里面。
  • 增量赋值不是一个原子操作。
  • 它虽然抛出了异常,但还是完成了操作。

list.sort方法和内置函数sorted

list.sort方法会就地排序列表,也就是说不会把原列表复制一份。:如果一个函数或者方法对对象进行的是就地改动,那它就应该返回None,好让调用者知道传入的参数发生了变动,而且并未产生新的对象。

用bisect来管理已排序的序列

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

bisect来搜索

根据一个分数,找到它所对应的成绩



本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

Scrapy突破反爬虫的限制 上一篇
构建高可用MVC 下一篇