Python指南:组合数据类型

  • 1、序列类型
    • 1.1 元组
      • 1.1.1 元组的创建
      • 1.1.2 元组索引和分片
      • 1.1.3 元组方法
      • 1.1.4 元组运算符
      • 1.1.5 元组的删除
      • 1.1.6 无关闭分隔符
    • 1.2 命名的元组
    • 1.3 列表
      • 1.3.1 列表的创建
      • 1.3.2 列表索引和分片
      • 1.3.3 列表方法
      • 1.3.4 拆分操作符
      • 1.3.5 删除
      • 1.3.6 列表内涵
  • 2、集合类型
    • 2.1 集合
      • 2.1.1 集合的创建
      • 2.1.2 集合方法与操作符
      • 2.1.3 集合内涵
    • 2.2 固定集合
  • 3、映射类型
    • 3.1 字典
      • 3.1.1 字典的创建
      • 3.1.2 字典方法
      • 3.1.3 字典内涵
      • 3.2 默认字典
      • 3.3 有序字典
    • 4、组合数据类型的迭代与复制
      • 4.1 迭代子、迭代操作与函数
    • 4.2 组合类型的复制

本章我们将学习如何使用Python的组合数据类型将数据项集合在一起,以便在程序设计时有更多的选项。

1、序列类型

Python提供了5中内置的序列类型:bytearraybytesliststrtuple,序列类型支持成员关系操作符(in)、大小计算函数(len())、分片([]),并且是可可迭代的。

1.1 元组

元组是个有序序列,包含0个或多个对象引用,使用小括号包裹。元组是固定的,不能替换或删除其中包含的任意数据项。

1.1.1 元组的创建

使用()创建一个元组:

  • 括号内不包含内容,则创建一个空元组
  • 括号内包含使用逗号分隔的数据项,创建一个非空元组

也可以使用tuple()创建一个元组:

  • 不指定参数时,返回一个空元组
  • 使用tuple作为参数时,返回该参数的浅拷贝
  • 其他参数时,尝试将给定的对象转换为tuple类型

1.1.2 元组索引和分片

语法 描述
tup[1] 读取第二个元素
tup[-2] 反向读取;读取倒数第二个元素
tup[1:] 截取元素
tup = ('first', 5, 'white', 'dog')

print(tup[1])
print(tup[-2])
print(tup[1:])

[out]
5
white
(5, 'white', 'dog')

1.1.3 元组方法

元组只提供两种方法:

语法 描述
t.count(x) 返回对象x在元祖t中出现的次数
t.index(x) 返回对象x在元组t中出现的最左边位置
tup = ('1', 'first', '1', '1', '2')
print('count of "1":',tup.count('1'))
print('index of "2":',tup.index('2'))

[out]
count of "1": 3
index of "2": 4

1.1.4 元组运算符

与字符串一样,元组之间可以使用 + 号和 * 号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组。

语法 描述
len(t) 返回元组t中元素个数
+ 连接
* 复制
in 元素是否存在
for … in …: 迭代
比较运算符
<、<=、>、>=、==、!=
逐项进行比较

1.1.5 元组的删除

元组中的元素值是不允许删除的,但我们可以使用del删除整个元组:

tup = ('python', 'hello', 1997, 2000);
print(tup)

del tup
print("After deleting tup : ")
print(tup)

[out]
('python', 'hello', 1997, 2000)
After deleting tup : 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
1-a12bbf13863f> in ()
      4 del tup
      5 print("After deleting tup : ")
----> 6 print(tup)

NameError: name 'tup' is not defined

1.1.6 无关闭分隔符

当元组出现在二进制操作符的左边或出现在unary语句的右边时,可以不使用圆括号。

a,b = (1,2) # left of binary operator
for x,y in ((1,2),(3,4),(5,6)): # left of binary operator
    print(x,y)

del a,b # right of unary statement
def f(x):
    return x,x**2 # right of unary statement

1.2 命名的元组

命名的元组(namedtuple)与普通元组一样,有相同的表现特征,其添加的功能就是可以根据名称引用元组中的项

collections模块提供了namedtuple()函数,用于创建自定义的元组数据类型。该函数的第一个参数是想要创建的自定义元组数据类型的名称,第二个参数是一个字符串,其中包含使用空格分隔的名称,每个名称代表该元祖数据类型中的一项。该函数返回一个自定义的类,可用于创建命名的元组。

import collections
Sale = collections.namedtuple('Sale', 'productid customerid data quantity price')
sales = list()
sales.append(Sale(432,921,"2018-04-01",3,8.2))
sales.append(Sale(543,879,"2018-03-31",6,8.1))
print(sales)

[out]
[Sale(productid=432, customerid=921, data='2018-04-01', quantity=3, price=8.2), Sale(productid=543, customerid=879, data='2018-03-31', quantity=6, price=8.1)]

这里我们创建了包含两个Sale项的列表,我们可以使用索引位置来引用元组中的项,也可以使用名称进行引用,后者正式命名的元组的特点:

total = 0
for sale in sales:
    total += sale.quantity * sale.price
print('Total ¥{0:.2F}'.format(total))

[out]
Total ¥73.20

1.3 列表

列表是包含0个或多个对象引用的有序序列,支持与字符串以及元组一样的分片与步距语法,列表是可变的,因此我们可以对列表中的项进行删除或替换,插入、替换或删除列表中的分片也是可能的。

1.3.1 列表的创建

使用[]创建一个元组:

  • 括号内不包含内容,则创建一个空列表
  • 括号内包含使用逗号分隔的数据项,创建一个非空列表

也可以使用list()创建一个列表:

  • 不指定参数时,返回一个空列表

  • 使用list作为参数时,返回该参数的浅拷贝

  • 其他参数时,尝试将给定的对象转换为list类型

1.3.2 列表索引和分片

语法 描述
lst[1] 读取第二个元素
lst[-2] 反向读取;读取倒数第二个元素
lst[1:] 截取元素
lst = ['first', 5, 'white', 'dog']

print(lst[1])
print(lst[-2])
print(lst[1:])

[out]
5
white
[5, 'white', 'dog']

1.3.3 列表方法

下表中,L为列表。

语法 描述
L.append(x) 将数据项x追加到L的末尾
L.count(x) 统计元素x在L中出现的次数
L.extend(m)
L += m
将iterable m的项追加到L的末尾
L.index(x, start, end) 返回数据项x在L中(或L的start: end分片中)最左边出现的索引位置,如果没找到x,则产生ValueError异常
L.insert(i, x) 在索引位置i处插入元素x
L.pop() 移除L最右边的数据项,并返回该元素的值
L.pop(i) 移除L索引位置i处的数据项,并返回该元素的值
L.remove(x) 从L中移除最左边的数据项x,如果没找到x产生ValueError异常
L.reverse() 对L进行反转
L.sort(…) 对L进行排序,与内置的sorted()函数一样,可以接受可选的key与reverse参数
L = [5, 'python', (1,2), 5, 'today']
L.append(9)
print('列表追加项:', L)
print('列表中5出现的次数:', L.count(5))

L.extend('hello')
print('追加迭代器中的项:',L)
print('"python"最左边索引值:', L.index('python'))

L.insert(1, 'insert')
print('在索引位置1处插入:', L)
pop_item = L.pop()
print('L末尾数据项:', pop_item)
print('移除末尾数据项后的结果:', L)

L.remove((1,2))
print('移除(1,2)后的列表:', L)

L.reverse()
print('反转后的列表:', L)

[out]
列表追加项: [5, 'python', (1, 2), 5, 'today', 9]
列表中5出现的次数: 2
追加迭代器中的项: [5, 'python', (1, 2), 5, 'today', 9, 'h', 'e', 'l', 'l', 'o']
"python"最左边索引值: 1
在索引位置1处插入: [5, 'insert', 'python', (1, 2), 5, 'today', 9, 'h', 'e', 'l', 'l', 'o']
L末尾数据项: o
移除末尾数据项后的结果: [5, 'insert', 'python', (1, 2), 5, 'today', 9, 'h', 'e', 'l', 'l']
移除(1,2)后的列表: [5, 'insert', 'python', 5, 'today', 9, 'h', 'e', 'l', 'l']
反转后的列表: ['l', 'l', 'e', 'h', 9, 'today', 5, 'python', 'insert', 5]

1.3.4 拆分操作符

任意可迭代的(列表、元组等)数据类型都可以使用序列拆分操作符进行拆分,即*。用于赋值操作符左边的两个或多个变量时,其中一个使用*进行引导,数据项将赋值给该变量,而所有剩下的数据项将给带星号的变量。

first, *rest, last = [1, 2, 3, 4, 5]
print(first, rest, last)

[out]
1 [2, 3, 4] 5

1.3.5 删除

由于列表是可变的,我们可以对其数据项进行删除。

  • 删除单个数据项

    
    # 删除一个数据项
    
    L = [5, 'python', (1,2), 5, 'today']
    del L[1]
    print('使用del删除一项:', L)
    
    L = [5, 'python', (1,2), 5, 'today']
    L.pop(1)
    print('使用pop删除一项:', L)
    
    [out]
    使用del删除一项: [5, (1, 2), 5, 'today']
    使用pop删除一项: [5, (1, 2), 5, 'today']

  • 删除分片

    
    # 删除分片
    
    L = [5, 'python', (1,2), 5, 'today']
    del L[1:3]
    print('使用del删除分片:', L)
    
    L = [5, 'python', (1,2), 5, 'today']
    L[1:3] = []
    print('使用[]删除分片:', L)
    
    [out]
    使用del删除分片: [5, 5, 'today']
    使用[]删除分片: [5, 5, 'today']

1.3.6 列表内涵

列表内涵是一个表达式,也是一个循环,该循环有一个可选的、包含在方括号中的条件,作用是为列表生成数据项,并且可以使用条件过滤掉不需要的数据项,可以使用表达式,也可以使用附加条件。常见语法:

  • [expression for item in iterable]
  • [expression for item in iterable if condition]

在没有列表内涵时,我们找出1900~1940年之间所有的闰年,可能会这么写:

# 普通方法找1900~1940年之间的闰年
leaps = list()
for year in range(1900, 1940):
    if (year%4 == 0 and year%100 != 0) or (year%400 == 0):
        leaps.append(year)
print(leaps)

[out]
[1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936]

学习了列表内涵之后我们可以简化程序:

# 列表内涵找1900~1940年之间的闰年
leaps = [year for year in range(1900, 1940) if (year%4 == 0 and year%100 != 0) or (year%400 == 0)]
print(leaps)

[out]
[1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936]

两种方法等效,得到同样的结果。

2、集合类型

set也是一种组合数据类型,支持成员关系操作符(in)、对象大小计算操作符(len()),并且也是iterable。Python提供了两种内置的集合类型:可变的set类型,固定的frozenset类型。进行迭代时,集合类型以任意顺序提供其数据项。

只有可哈希运算的对象可以添加到集合中。所有的内置固定数据类型(比如float、frozenset、int、str、tuple)都是可哈希运算的,可以添加到集合中。内置的可变数据类型(比如dict、list、set)都不是可哈希运算的,不能添加到集合中。

2.1 集合

集合是0个或多个对象引用的无序组合。集合是可变的,因此可以很容易的添加和移除数据项,但是由于其中的项是无序的,因此没有索引位置的概念,也不能分片或按步距分片。

2.1.1 集合的创建

使用set()创建一个集合:

  • 不指定参数时,返回一个空集合
  • 使用set作为参数时,返回该参数的浅拷贝
  • 其他参数时,尝试将给定的对象转换为集合

集合中包含的每个数据项都是独一无二的——添加重复的数据项固然不会引发问题,但是也毫无意义。比如,下面产生的三个集合是一样的:set(‘apple’)、set(‘aple’)、{‘e’, ‘p’, ‘l’, ‘a’}。鉴于此,集合常用于删除重复的数据项。比如,x是一个字符串列表,在执行x=list(set(x))之后,x中的每个字符串都是独一无二的,存放顺序是任意的。

2.1.2 集合方法与操作符

s、t为集合,x为数据项。

语法 描述
s.add(x) 将x添加到s中——如果s中尚未包含x
s.clear() 清空s
s.copy() 返回s的浅拷贝
s.difference(t)
s-t
返回一个新集合,其中包含在s中但不在t中的所有数据项
s.difference_update(t)
s-=t
移除每一个在t中但不在s中的项
s.discard(x) 如果x在s中,则移除x
s.intersection(t)
s&t
返回一个新集合,其中包含所有同时包含在s和t中的数据项
s.intersection_update(t)
s&=t
使得s包含自身与t的交集
s.isdisjoint(t) 如果s与t没有相同的项,返回True
s.issubset(t)
s<=t
如果s与t相同,或s是t的子集,返回True;使用s
s.issupset(t)
s>=t
如果s与t相同,或s是t的超集,返回True
s.pop() 返回并移除s中的一个随机项,如果s为空,就产生一个KeyError
s.remove(x) 从s中移除x,如果s中不包含x,就产生KeyError
s.symmetric_difference(t)
s^t
返回一个新集合,其中包含s与t中的每个数据项,但不包含同时在这两个集合中的数据项
s.symmetric_difference_update(t)
s^=t
使得s只包含其自身与t的对称差
s.union(t)
s|t
返回一个新集合,其中包含集合s中的所有数据项以及在t中而不在s中的数据项
s.update(t)
s|=t
将t中每个s中不包含的数据项添加到集合s中

2.1.3 集合内涵

除了调用set()创建集合,或使用集合字面值创建集合外,我们可以使用集合内涵创建集合。集合内涵是一个表达式,也是一个带有可选条件的循环,支持的语法:

  • {expression for item in iterable}
  • {expression for item in iterable if condition}

2.2 固定集合

固定集合是指那种一旦创建就不能修改的集合,只能使用frozenset数据类型函数创建,不带参数调用时,frozenset()返回一个空的固定集合,带一个frozenset参数时,将返回改参数的 浅拷贝,对于任何其他类型的参数,都尝试将给定的对象转换为一个forzenset。

3、映射类型

映射是键-值数据项的组合,并提供了存取数据项及其键、值的方法。Python3.0支持两种无序的映射类型——内置的dict类型以及标准库中的collections.defaultdict类型。Python3.1引入了一种新的、有序的映射类型collections.OrderedDict,该类型是一个字典,与内置的dict有相同的方法和属性,但在存储数据项时以插入顺序进行。

3.1 字典

dict是一种无序的组合数据类型,其中包含0个或多个键-值对。

3.1.1 字典的创建

可以使用{}创建:

  • 空的花括号创建一个空的字典
  • 包含一个或多个逗号分隔的键值对,创建一个非空字典

也可以使用dict()函数创建:

  • 不带参数,创建一个空的字典
  • 带有dict类型的参数,返回该参数的浅拷贝
  • 键值对组合的参数,创建非空字典

字典的键值是独一无二的,因此,如果向字典中添加一个已存在的键值项,实际效果是新值替换旧值。

3.1.2 字典方法

d为字典

语法 描述
d.clear() 移除d中所有项
d.copy() 返回d的浅拷贝
d.fromkeys(s, v) 返回一个dict,该字典的键为序列s中的项,值为None或V
d.get(k) 返回键k关联的值,如果d中不存在k则返回None
d.get(k, v) 返回键k关联的值,如果d中不存在k则返回v
d.items() 返回d中所有(key, value)对的视图
d.keys() 返回d中所有键的视图
d.pop(k) 返回键k的关联值,并移除键为k的项,如果k不包含在d中就产生KeyError
d.pop(k, v) 返回键k的关联值,并移除键为k的项,如果k不包含在d中就返回v
d.popitem() 返回并移除d中任意一个(key, value)对,如果d为空就产生KeyError
d.setdefault(k, v) 与d.get()方法一样,不同之处在于,如果k没有包含在d中就插入一个键为k的新项,其值为None或v
d.update(a) 将a中每一个尚未包含在d中的(key, value)对添加到d中,对同时包含在d与a中的每个键,使用a中对应的值替换d中对应的值——a可以是字典,也可以是(key, value)对的一个iterable或关键字参数
d.values() 返回d的所有值的视图

上面提到了“视图”概念,其相对于通常的iterables有两个不同点:

  • 如果该视图引用的字典发生变化,那么视图将反映该变化。
  • 键视图与项视图支持一些类似于集合的操作:
    • v & x # Intersection
    • v | x # Union
    • v - x # Difference
    • v ^ x # Symmentric difference

注:两种通过键取值方式的比较

我们可以通过d[k]d.get()两种形式来取值,比如我们进行词频统计时,使用words[word]+=1words[word] = words.get(word, 0) + 1 都可以进行加1操作,但是如果单词第一次出现,第一种形式会产生KeyValue错误,第二种则会正确运行。

3.1.3 字典内涵

字典内涵是一个表达式,也是一个循环,该循环带有一个可选条件。语法:

  • {keyexpression: valueexpression for key, value in iterable}
  • {keyexpression: valueexpression for key, value in iterable if condition}

例:

# 使用字典内涵创建字典,其中每个键是当前目录中文件的文件名,值则为以字节计数的文件夹大小
import os
file_sizes = {name: os.path.getsize(name) for name in os.listdir('.')}
print(file_sizes)

[out]
{'.ipynb_checkpoints': 0, '第三章组合数据类型.ipynb': 12387}

3.2 默认字典

默认字典也是一种字典——这种字典包含普通字典所提供的所有操作符与方法,与其不同的是可以对遗失的键进行处理

创建默认字典时,我们可以传入一个工厂函数,这样就会为遗失的键创建默认值。看下面例子

import collections
words = collections.defaultdict(int)
x = words['a']
print(x)

[out]
0

上面我们创建的默认字典words永远不会产生KeyError异常,如果遇到没有的键,其值通过工厂函数(int())设置为0。

3.3 有序字典

有序字典collections.OrderedDict是以数据项的插入顺序进行存储。

import collections
d = collections.OrderedDict([('first', 1), ('second', 2), ('third', 3)])
print(d.keys())

[out]
odict_keys(['first', 'second', 'third'])

可以看出我们通过二元组列表创建有序字典后,获取去键视图也为有序的。

有序字典另一种稍专业一些的用途是生成排序字典。给定一个字典d,可以按如下方式转换为排序字典:d=collections.OrderedDict(sorted(d.items()))

4、组合数据类型的迭代与复制

4.1 迭代子、迭代操作与函数

iterable数据类型每次返回其中的一个数据项。任意包含__iter__() 方法的对象或任意序列(也即包含__getitem__()方法的对象)都是一个iterable,并可以提供一个迭代子。迭代子是一个对象,该对象可以提供__next__()方法,该方法依次返回每个相继的数据项,并在没有数据项时产生StopIteration异常。

常见的迭代操作符与函数(s与t为序列):

语法 描述
s+t 返回一个序列,该序列是s与t的连接
s*n 返回一个序列,该序列是s的n个副本的连接
x in i 如果x出现在iterable i中,返回True
all(i) 如果iterable i中的每一项都评估为True,就返回True
any(i) 如果iterable i中的任意项评估为True,就返回True
emumerate(i, start) 通常用于for… in 循环中,提供一个(index, item)元组序列,其中索引其实值为0或start
len(x) 返回x的“长度”
max(i, key) 返回iterable i中的最大的项,如果给定的是key函数,就返回key(item)值的最大项
min(i, key) 返回iterable i中的最小的项,如果给定的是key函数,就返回key(item)值的最小项
range(start, stop, step) 返回一个整数迭代子,使用一个参数(stop)时,迭代子的取值范围从0到stop-1;使用两个参数(start与stop)时,迭代子取值范围从start到stop-1;使用三个参数时,迭代子取值范围从start到stop-1,每两个值之间间隔step
reversed(i) 返回一个迭代子,该迭代子以反序从迭代子i中的返回项
sorted(i, key, reverse) 以排序后顺序从迭代子i返回项,key用于提供DSU(修饰、排序、反修饰)排序,如果reverse为True,则排序以反序进行
sum(i, start) 返回iterable i中项的和,加上start(默认为0),i可以包含字符串
zip(i1, …, iN) 返回元组的迭代子,使用迭代子i1到iN

数据项返回的顺序依赖于底层的iterable。对列表和元组等情况,数据项的返回值通常从第一个数据项开始依次返回,而对于字典与集合,迭代子是任意顺序的返回项。

4.2 组合类型的复制

由于数据片总是曲子某个数据项的一个单独副本,所以获取一个列表的副本可以通过下面方式:

lst = ['apple', 'dog']
copy_of_lst = lst[:]
print(copy_of_lst)

[out]
['apple', 'dog']

对于字典和集合,可以使用dict.copy()set.copy()来实现。此外,copy模块还提供了copy.copy() 函数,该函数返回给定对象的一个副本。

在以上各种组合数据类型创建的时候,提到可以使用工厂方法来创建一个组合数据类型的副本:

# 工厂方法创建副本
d = {'first':'hello', 'second':'world'}
L = ['hello', 'world']
s = {'hello', 'world'}

copy_of_dict = dict(d)
copy_of_list = list(L)
copy_of_set = set(s)

注意:以上的复制都是浅拷贝,也就是说,复制的知识对象引用而非对象本身。对于固定数据类型(数字、字符串等),这与复制的效果是相同的,但对于可变的数据类型,比如嵌套的组合类型,这意味着相关对象同时被原来的组合与复制得来的组合引用。请看下面代码:

# 浅拷贝实例
x = [12, 34, ['hello', 'world']]
y = x[:]
print(x, y)

y[0] = 56
y[2][1] = 'boy'
print(x, y)

[out]
[12, 34, ['hello', 'world']] [12, 34, ['hello', 'world']]
[12, 34, ['hello', 'boy']] [56, 34, ['hello', 'boy']]

从输出结果可以看出,前两项固定数据类型并没有同时改变,而列表中的列表同时变化,说明x与y的第三项都指向的同一列表的引用。我们可以使用深拷贝来避免此类问题:

import copy
# 浅拷贝实例
x = [12, 34, ['hello', 'world']]
y = copy.deepcopy(x)
print(x, y)

y[0] = 56
y[2][1] = 'boy'
print(x, y)

[out]
[12, 34, ['hello', 'world']] [12, 34, ['hello', 'world']]
[12, 34, ['hello', 'world']] [56, 34, ['hello', 'boy']]

从输出结果看,x与y已经完全独立了。

你可能感兴趣的:(Python-之路)