Python指南:组合数据类型

本章我们将学习如何使用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)
<ipython-input-1-a12bbf13863f> in <module>()
      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已经完全独立了。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C与Python实战

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值