首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

后台编程Python3-元组与列表

本节是第五讲的第五小节,上节为大家介绍了Python语言的字符串类型包括(常用字符串操作方法、分片与分距、format格式化等),从本节开始为大家介绍复杂数据类型,本节介绍常用的元组与列表。

序列类型(Sequence Types)

序列类型支持成员关系操作符(in)、大小计算函数(len())、分片([]),并且是可以迭代的。Python提供了 5种内置的序列类型:bytearray、bytes、list、str与tuple, 前两种将在后面单独介绍。Python标准库中还提供了其他一些序列类型,最值得注意的是collections.namedtuple。进行迭代时,这些序列都将依序提供其中包含的项。前面已经介绍了字符串,在本节中,我们将介绍元组、命名的元组与列表。

元组(tuples)

元组是个有序的序列,其中包含0个或多个对象引用。元组支持与字符串一样的分片与步距的语法,这使得从元组中提取数据项比较容易。与字符串类似,元组也是固定的,因此,不能替换或删除其中包含的任意数据项。如果需要修改有序序列,我们应该使用列表而非元组。如果我们有一个元组,但又需要对齐进行修改,那么可以使用list()转换函数将其转换为列表,之后在产生的列表之上进行适当修改。

tuple数据类型可以作为一个函数进行调用,tuple()——不指定参数时将返回一个空元组,使用tuple作为参数时将返回该参数的浅拷贝,对其他任意参数,将尝试把给定的对象转换为tuple类型。该函数最多只能接受一个参数。元组也可以使用tuple() 函数创建,空元组是使用空圆括号()创建的,包含一个或多个项的元组则可以使用逗号分隔进行创建。有时,元组必须包含在圆括号中,以避免语义的二义性。例如,如果需要将元组1,2,3传递给一个函数,就应该写成function((1,2,3))的形式。

如图展示了元组 t = "venus", -28, "green", "21", 19.74,以及元组内各项的索引位置。字符串可以同样的方式进行索引, 不过字符串每个索引位置上的项为字符, 元组的每个位置则为对象引用。

元组只提供了两种方法:t.count(x),返回对象x在元组中出现的次数;t.index(x), 返回对象在元组t中出现的最左边位置--在元组中不包含x时,则产生ValueError 异常。(这些方法对列表也是可用的。)

此外,元组可以使用操作符+ (连接)、* (赋值)与[](分片),也可以使用in与 not in来测试成员关系。并且,虽然元组是固定对象,但+=与*=这两个增强的赋值运算符也可以使用——实际上是Python创建了新元组,用于存放结果,并将左边的对象引用设置为指向新元组。这些操作符应用于字符串时,釆用的技术是相同的。元组可以使用标准的比较操作符()进行比较,这种比较实际是逐项进行的(对嵌套项,比如元组内的元组,递归进行处理)。

#下面给出几个分片实例,最初是提取一个项,之后提取项分片,这些处理过程对字符串、列表以及任意其他序列类型都是一样的

>>> hair = "black", "brown", "blonde", "red"

>>> hair[2]

'blonde'

>>> hair[-3:] # same as: hair[1:]

('brown', 'blonde', 'red')

>>> hair[:2],"gray",hair[2:]

(('black', 'brown'), 'gray', ('blonde', 'red'))

>>> hair[:2] + ("gray",) + hair[2:]

('black', 'brown', 'gray', 'blonde', 'red')

#这里我们本来是想创建一个新的5元组,但结果是一个三元组,其中包含两个二元组,之所以会这样,是因为我们在3个项(一个元组,一个字符串,一个元组) 之间使用了逗号操作符。要得到一个单独的元组,并包含所需项,我们必须对其进行连接,要构成一个一元组,逗号是必需的

在写元组时,我们将使用一种特定的编码风格。当元组出现在二进制操作符的左边时,或出现在unary(一元)语句的右边时,我们不需要使用圆括号, 对其他所有情况,则需要使用圆括号。

a,b=(1,2)   # left of binary operator

del a, b  # right of unary statement

def f(x):

return x, x ** 2   # right of unary statement

for  x, y in ((1, 1), (2, 4), (3, 9)):  # left of binary operator

print(x, y)

当然,没有要求必须遵循这种编码风格。有些程序员更愿意始终使用圆括号——这与元组的表象形式也是一致的,而其他程序员只有在确实需要的时候才使用圆括号。

>>> eyes = ("brown", "hazel", "amber", "green", "blue", "gray")

>>> colors = (hair, eyes)

>>> colors[1][3:-1]

('green', 'blue')

上面的实例中,在一个元组内有两个嵌套的元组。任何嵌套层次的组合类型都可以类似于上面的方式进行创建,而不需要格式化处理。

#分片操作符[]可以应用于一个分片,必要的时候可以使用多个,比如:

>>> things = (1, -7.5, ("pea", (5, "Xyz"), "queue"))

>>> things[2][1][1][2]

'z'

我们逐步来查看上面的代码,开始处的things[2]表示的是元组的第三项(因为第 一项索引值为0),该项本身是一个元组,即("pea”, (5, "Xyz"), "queue")。表达式things[2][1]表示things[2]元组的第二项,该项也是一个元组,即(5, "Xyz");表达式things[2][1] [1]表示things[2][1]的第二项,即“Xyz”;最后,表达式things[2][1][1][2]表示的是字符串中的第三项(字符),即“z”。

元组可以存放任意数据类型的任意项,包括组合类型,比如元组与列表,实际上存放的是对象引用。但是使用这样复杂的嵌套数据结构很容易造成混淆,一个解决的办法是为特定的索引位置指定名字,例如:

>>> MANUFACTURER, MODEL, SEATING = (0, 1, 2)

>>> MINIMUM, MAXIMUM = (0, 1)

>>> aircraft = ("Airbus", "A320-200", (100, 220))

>>> aircraft[SEATING][MAXIMUM]

220

显然,这种写法比直接写aircraft[2][1]更容易理解,但是需要创建大量的变量,形式上也相当难看。下一小节中我们将讲述一种替代的解决方法。

在“aircraft”代码段的头两行,两条语句都赋值给元组。在赋值操作(这里是赋值给元组)的右边是序列、左边是元组的情况下,我们称右边被拆分。序列拆分可用于交换值,例如:a, b = (b, a)

命名的元组(Named Tuples)

命名的元组与普通元组一样,有相同的表现特征,其添加的功能就是可以根据名称引用元组中的项,就像根据索引位置一样,这一功能使我们可以创建数据项的聚集。

#collections模块提供了 namedtuple()函数,该函数用于创建自定义的元组数据类型

import collections

Sale = collections.namedtuple("Sale","productid customend date quantity price")

collections.namedtuple()的第一个参数是想要创建的自定义元组数据类型的名称, 第二个参数是一个字符串,其中包含使用空格分隔的名称,每个名称代表该元组数据类型的一项。第一个参数以及第二个参数中空格分隔开的名称必须都是有效的Python 字符串。该函数返回一个自定义的类(数据类型),可用于创建命名的元组。

>>>sales =[]

>>>sales.append(Sale(432, 921, "2008-09-14", 3, 7.99))

>>>sales.append(Sale(419, 874, "2008-09-15", 1, 18.49))

>>>sales

[Sale(productid=432, customend=921, date='2008-09-14', quantity=3, price=7.99), Sale(productid=419, customend=874, date='2008-09-15', quantity=1, price=18.49)]

这里,我们创建了包含两个Sale项的列表,也就是包含两个自定义元组。我们可以使用索引位置来引用元组中的项——比如,第一个销售项的价格sales[0][-1]  (也就是7.99)——但我们也可以使用名称进行引用,并且这样会更加清晰:

total = 0

for sale in sales:

total += sale.quantity * sale.price

print("Total $".format(total))           # prints: Total $42.46

>>> Aircraft = collections.namedtuple("Aircraft", "manufacturer model seating")

>>> Seating = collections.namedtuple("Seating", ''minimum maximum")

>>> aircraft = Aircraft("Airbus","A320-200", Seating(100, 220))

220

#在涉及提取命名的元组项以便用于字符串时,可以釆用:

>>> print(" ".format(aircraft.manufacturer, aircraft.model))

Airbus A320-200

#使用一个单独的位置参数,并在格式化字符串中使用命名的元组属性

>>>" ".format(aircraft)

注意,面向对象程序设计中,通过这种方式创建的每个类都是tuple的子类名作为字段名。与只是使用位置参数相比,这种做法要更清晰,但遗憾的是,我们必须指定位置值。命名的元组有几个私有方法--也就是那些名称以下划线开始的方法。其中有一个namedtuple._asdict()方法非常有用,我们稍后将展示该方法的应用。

>>>" ".format(**aircraft._asdict())

私有方法namedtuple._asdict()返回的是键-值对的映射,其中每个键都是元组元素的名称,值则是对应的值。我们使用映射拆分将映射转换为str.format()方法的键-值参数。

列表(Lists)

列表是包含0个或多个对象引用的有序序列,支持与字符串以及元组一样的分片与步距语法,这使得从列表中提取数据项很容易实现。与字符串以及元组不同的是, 列表是可变的,因此,我们可以对列表中的项进行删除或替换,插入、替换或删除列表中的分片也是可能的。

list数据类型可以作为函数进行调用,list()—不带参数进行调用时将返回一个空列表;带一个list参数时,返回该参数的浅拷贝;对任意其他参数,则尝试将给定的对象转换为列表。该函数只接受一个参数的情况。列表也可以不使用list()函数创建, 空列表可以使用空的方括号来创建,包含一个或多个项的列表则可以使用逗号分隔的数据项(包含在[]中)序列来创建。另一种创建列表的方法是使用列表内涵。由于列表中所有数据项实际上都是对象引用,因此,与元组一样,列表也可以存放任意数据类型的数据项,包括组合数据类型,比如列表与元组。列表可以使用标准的比较操作符(<、<=、==、!=、>=、>)进行比较,这种比较实际是逐项进行的(对嵌套项,比如列表内的元组或列表,递归进行处理)。

给定赋值操作 L = [-17.5, "kilo", 49, "V", ["ram", 5, "echo"], 7],

我们可以获得的列表如下图所示。

#对列表L,我们可以使用分片操作符(如果必要,可以重复使用)来存取列表中的数据项,如下面所示:

L[0]==L[-6]==-17.5

U[1] == L[-5] == 'kilo'

L[1][0] == L[-5][0] == k

L[4][2] == L[4][-1] == L[-2][2] == L[-2][-1] == 'echo'

L[4][2][1]== L[4][2][-3] == L[-2][-1][1] == L[-2][-1 ][-3] == 'c'

和元组类似,列表也支持嵌套、迭代、分片等操作。实际上,在前面小节中的所有元组操作实例中,如果将其中的元组替换为列表,那么都可以完全相同的方式进行处理。列表支持使用in与not in进行成员关系测试,使用+进行连接,使用+=进行扩展(即将右边操作数代表的所有项附加到列表中),使用*与*=进行复制等操作。列表也可以用于内置的len()函数以及del语句,del语句在“使用del语句删除项”工具条中进行描述。

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的startend分片中)最左边出现的索引位置,否则会产生 一个 ValueError 异常

L.insert(i, x)在索引位置int i处将数据项x插入列表L

L.pop()返回并移除list L最右边的数据项

L.pop(i)返回并移除L中索引位置int i处的数据项

L.remove(x)从list L中移除最左边出现的数据项x,如果找不到x就产生ValueError异常

L.reverse()对列表L进行反转

L.sort(…)对列表L进行排序,与内置的sorted()函数一样,这一方法可以接受可选的key与reverse 参数

尽管可以使用分片操作符存取列表中的数据项,但在有些情况下,我们需要一次提取两个或更多个数据项,可以使用序列拆分实现。任意可迭代的(列表、元组等) 数据类型都可以使用序列拆分操作符进行拆分,即*。用于赋值操作符左边的两个或多个变量时,其中的一个使用*进行引导,数据项将赋值给该变量,而所有剩下的数据项将赋值给带星号的变量,

#以这种方式使用序列拆分操作符时,表达式*rest以及类似的表达式称为带星号的表达式。

>>> first, *rest = [9, 2, -4, 8, 7]

>>> first, rest

(9, [2, -4, 8, 7])

>>> first, *mid, last = "Charles Philip Arthur George Windsor".split()

>>> first, mid, last

('Charles', ['Philip', 'Arthur1, 'George1], 'Windsor')

>>> *directories, executable = "/usr/local/bin/gvim".split("/")

>>> directories, executable

(['', 'usr', 'local', 'bin'], 'gvim')

#Python还有一个相关的概念:带星号的参数。

def product(a, b, c):

return a * b * c    # here, * is the multiplication operator

#我们可以使用3个参数来调用该函数,也可以使用带星号的参数:

>>> product(2, 3, 5)

30

>>>L=[2,3,5]

>>> product(*L)

30

>>> product(2, *L[1:])

30

在第一个调用中,我们提供了通常的3个参数。在第二个调用中,我们使用了一 个带星号的参数——这里,列表的3个数据项被*拆分,因此,调用函数时,函数将获得这3个参数,使用三元组也可以完成同样的任务。在第三个调用中,第一个参数采用常规的方式进行传递,另外两个参数则是通过对列表L中一个包含两个数据项的数 据分片拆分而来。

虽然从名称上看,del语句代表的是删除,但是实际上del语句的作用并不一定是删除数据。应用于某些对象引用(引用的是非组合类型的数据项)时,del语句的作用是取消该对象引用到数据项的绑定,并删除对象引用。

>>> x = 8143 # object ref. 'x' created; int of value 8143 created

>>> x

8143

>>> del x # object ref. 'x1 deleted; int ready for garbage collection

>>> x

Traceback (most recent call last):

NameError: name 'x' is not defined

对象引用被删除后,如果该对象引用所引用的数据项没有被其他对象引用进行引用,那么该数据项将进入垃圾收集流程。在垃圾收集是否自动进行不能确定时(依赖于Python的实现),如果需要进行清理,就必须自己手动进行处理。对垃圾收集的不确定性,Python提供了两种方案,一种是使用try ... finally语句块,确保垃圾收集得以进行;另一种是使用with语句,将在后面进行讲述。

用于组合数据类型(比如元组或列表)时,del语句删除的只是对组合类型的对象引用。如果该对象引用所引用的组合类型数据没有被其他对象引用进行引用,那么该组合类型数据及其项(对于本身也是组合类型的项进行递归处理)将进入垃圾收集流程。

对可变的组合数据类型,比如列表,del可应用于单个数据项或其中的数据片 — 两种情况都需要使用分片操作符[]。如果引用的单个或多个数据项从组合类型数据中移除,并且没有其他对象引用对其进行引用,就进入垃圾收集流程。

操作符*是用作多复制操作符还是序列拆分操作符并不会产生语义上的二义性。当 *出现在赋值操作的左边时,用作拆分操作符,出现在其他位置(比如在函数调用内) 时,若用作单值操作符,则代表拆分操作符;若用作二进制操作符,则代表多复制操作符。

我们已经知道,对列表中的数据项,可以在其上进行迭代处理,使用的语法格式是for item in L:如果需要该列表中的数据项,那么使用的惯用方法如下:

for i in range(len(L)):

L[i] = process(L[i])

内置的range。函数返回一个迭代子,其中存放一个整数。给定一个整数参数n, 则迭代子range()会生成并返回0, 1,...n-1.

由于列表支持分片,因此在几种情况下,使用分片或某种列表方法可以完成同样 的功能。比如,给定列表woods = ["Cedar", "Yew", "Fir"],我们可以以如下的两种方式 扩展列表:

woods += ["Kauri", "Larch"]

woods.extend(["Kauri", "Larch"])

woods[2:2] = ["Pine"]

woods.insert(2, "Pine")

对上面两种方法,所得结果都是列表['Cedar','Yew', 'Fir', 'Kauri', 'Larch']。使用list.append()方法,可以将单个数据项添加到列表尾部。使用list.insert()方法 (或者赋值给一个长度为0的分片),可以将数据项插入到列表内的任何索引位置。比如,给定列表woods = ["Cedar", "Yew", "Fir", "Spruce"],我们可以在索引位置2处插入 一个新的数据项(也就是作为该列表的第三项),上面两种方法所得的结果都是列表['Cedar', 'Yew', 'Pine', 'Fir', 'Spruce']。

通过对特定索引位置处的对象进行赋值,可以对列表中的单个数据项进行替换,假定有一个列表L =["A", "B", "C", "D", "E", "F"],现在将一个iterable (这里是 一个列表)赋值给L的一个分片,使用的代码是L[2:5] = ["X", "Y"]。首先,该分片将被移除,因此,列表实际上变为['A','B','F']之后,该iterable的所有项都将插入到该分片的起始位置,因此,最终列表变为['A','B','X', 'Y, 'F']。

还有很多种方法也可以移除列表中的数据项,我们可以不带参数使用list.pop(), 作用是移除列表中最右边的数据项并返回该数据项。类似地,我们可以带一个整数型的索引位置参数来调用list.pop(),其作用是移除(并返回)特定索引位置处的数据项。另一种方法是以待移除项作为参数调用list.remove()方法。del语句也可用于移除单个的数据项(比如,del woods[4])或移除多个数据项构成的数据片。通过将分片赋值为空列表,可以移除分片,下面的两个代码段是等价的:.

woods[2:4] = []

del woods[2:4]

在左边的代码段中,我们将数据片赋值为一个iterable (一个空列表),因此,在处理时,首先移除该数据片,因为要插入的iterable为空,所以并不进行实际的插入操作。

假定有列表x = [1,2, 3, 4, 5, 6, 7, 8, 9, 10],需要将每个奇数索引项(即x[1]、x[3]等)设置为0。通过步距,可以每隔两个数据项进行存取,比如x[::2],但这样得到的数据项的索引位置实际上是0、2、4等。通过指定一个起始的索引位置,可以解决这一问题,比如x[1::2],会返回包含我们所需要的数据项的数据片。如果需要将这些数据项都设置为0,我们需要一列0,并且其中0的个数与该数据片中包含的数据项个数要严格相等。

这里给出对上面问题的完整的解决方案:x[1::2] = [0] * len(x[1::2]) 。执行后,原 列表将变为[1,0, 3, 0,5, 0,7, 0,9,0]。我们使用了复制操作符*,以便生成一个包含一系列0的列表,其中0的个数与数据片长度(即数据项的个数)相等。有趣的一面是将列表[0, 0, 0, 0, 0]赋值给带步距的数据片时,Python会正确地使用第一个0替换x[1] 的值,使用第二个0替换x[3]的值,依此类推。

列表内涵(List Comprehensions)

小列表通常可以使用列表字面值直接创建,但长一些的列表,通常则需要使用程序进行创建。对一系列整数,我们可以使用list(range(n))创建,或者如果只需要一个整数迭代子,使用range()就足以完成任务,但对更复杂一些的列表,使用for...in循环 创建是一种更常见的做法。比如,假定需要生成给定时间范围内的润年列表,可以使用如下的语句:

leaps =[]

for year in range(1900,1940):

if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):

leaps.append(year)

在为内置的range()函数指定两个整数参数n与m时,该函数返回的迭代子iterator 将生成整数n, n + 1,.... m - 1.

当然,如果我们预先知道准确的范围,则可以使用列表字面值进行创建,比如, leaps = [1904,1908, 1912, 1916, 1920,1924,1928,1932,1936]。

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

[item for item in iterable]

上面的语句将返回一个列表,其中包含iterable中的每个数据项,在语义上与 list(iterable)是一致的。有两个特点使得列表内涵具有更强大的功能,也更能引起使用者的兴趣,一个是可以使用表达式,另一个是可以附加条件——由此带来如下两种实现列表内涵的常见语法格式:

[expression for item in iterable]

[expression for item in iterable if condition]

#第二种语法格式实际上等价于:

temp =[]

for item in iterable:

if condition:

temp.append(expression)

通常,上面的语法中,expression”或者是数据项本身,或者与数据项相关。当然, 列表内涵不需要for...in循环中使用的temp变量。

现在,我们可以使用列表内涵编写代码,以便生成列表leaps,我们分三个阶段开发这段代码,首先,生成一个列表,其中包含给定时间范围内的所有年份:

leaps = [y for y in range(1900, 1940)]

上面的任务也可以使用leaps = list(range( 1900, 1940))语句完成。接下来,为该语句添加一个简单的条件,以便每隔4年获取一次:

leaps = [y for y in range(1900, 1940)  if y % 4 == 0]

最后,给出完整的代码:

leaps = [y for y in range(1900, 1940)    if(y % 4 == 0 and y % 100 != 0) or (y % 400 == 0)]

以上内容部分摘自视频课程05后端编程Python-5元组与列表,更多实操示例请参照视频讲解。跟着张员外讲编程,学习更轻松,不花钱还能学习真本领。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20210306A04QK200?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券