数据结构

数据结构(data structure)基本上就是——它们是可以将一些数据组织在一起的结构。换句话说,它们用于存储一组相关的数据。

Python 中有四种内置的数据结构——list(列表)、tuple(元组)、dict(字典)和 set(集合)。我们将了解如何使用它们中的每一个,以及它们如何让我们的生活更轻松。

列表(List)

list 是一种保存有序项目集合的数据结构,即你可以在列表中存储一个项目的序列。如果你能想到一个购物清单,上面列出了你要购买的物品,这就很容易想象了,只不过你的购物清单上每个物品可能占一行,而在 Python 中你用逗号将它们分隔开。

项目列表应该用方括号括起来,以便 Python 理解你在指定一个列表。一旦创建了列表,你就可以添加、删除或搜索列表中的项目。由于我们可以添加和删除项目,我们说列表是一种可变的(mutable)数据类型,即这种类型可以被修改。

对象与类的快速入门

虽然我一直在推迟讨论对象和类,但现在需要做一些简单的解释,以便你能更好地理解列表。我们将在后面的章节中详细探讨这个主题。

列表是使用对象和类的一个例子。当我们使用变量 i 并给它赋值,比如整数 5 时,你可以把它想象为创建了一个 int 类(即类型)的对象(即实例)i。实际上,你可以阅读 help(int) 来更好地理解这一点。

类还可以有方法(method),即为该类专门定义的函数。你只有拥有该类的对象时才能使用这些功能。例如,Python 为 list 类提供了一个 append 方法,它允许你在列表末尾添加一个项目。例如,mylist.append('an item') 会将该字符串添加到列表 mylist 中。请注意使用点号表示法来访问对象的方法。

类还可以有字段(field),它们不过是为该类专门定义的变量。你只有拥有该类的对象时才能使用这些变量/名称。字段也通过点号表示法来访问,例如 mylist.field

示例(保存为 ds_using_list.py):

# This is my shopping list
shoplist = ['apple', 'mango', 'carrot', 'banana']

print('I have', len(shoplist), 'items to purchase.')

print('These items are:', end=' ')
for item in shoplist:
    print(item, end=' ')

print('\nI also have to buy rice.')
shoplist.append('rice')
print('My shopping list is now', shoplist)

print('I will sort my list now')
shoplist.sort()
print('Sorted shopping list is', shoplist)

print('The first item I will buy is', shoplist[0])
olditem = shoplist[0]
del shoplist[0]
print('I bought the', olditem)
print('My shopping list is now', shoplist)

输出:

$ python ds_using_list.py
I have 4 items to purchase.
These items are: apple mango carrot banana
I also have to buy rice.
My shopping list is now ['apple', 'mango', 'carrot', 'banana', 'rice']
I will sort my list now
Sorted shopping list is ['apple', 'banana', 'carrot', 'mango', 'rice']
The first item I will buy is apple
I bought the apple
My shopping list is now ['banana', 'carrot', 'mango', 'rice']

工作原理

变量 shoplist 是一个要去市场的人的购物清单。在 shoplist 中,我们只存储了要购买的物品名称的字符串,但你可以向列表中添加任何类型的对象,包括数字甚至其他列表。

我们还使用了 for..in 循环来遍历列表中的项目。到现在为止,你一定已经意识到列表也是一种序列。序列的特殊性将在后面的章节中讨论。

请注意在调用 print 函数时使用了 end 参数,表示我们希望以空格而不是通常的换行符来结束输出。

接下来,我们使用列表对象的 append 方法向列表中添加一个项目,正如之前已经讨论过的。然后,我们通过将列表直接传递给 print 函数来打印列表的内容,确认项目确实已添加到列表中,print 函数会整洁地打印出来。

然后,我们使用列表的 sort 方法对列表进行排序。重要的是要理解这个方法会影响列表本身,而不是返回一个修改后的列表——这与字符串的工作方式不同。这就是我们说列表是可变的(mutable)而字符串是不可变的(immutable)的含义。

接下来,当我们在市场买完一个物品后,我们想把它从列表中删除。我们使用 del 语句来实现这一点。在这里,我们指定要删除列表中的哪个项目,del 语句就会帮我们从列表中删除它。我们指定要删除列表中的第一个项目,因此使用 del shoplist[0](请记住 Python 从 0 开始计数)。

如果你想了解列表对象定义的所有方法,请查看 help(list) 获取详情。

元组(Tuple)

元组(tuple)用于将多个对象组合在一起。你可以把它们想象成类似于列表,但没有列表类提供的丰富功能。元组的一个主要特点是它们像字符串一样是不可变的(immutable),即你不能修改元组。

元组通过在可选的一对圆括号中指定用逗号分隔的项目来定义。

元组通常用于语句或用户自定义函数可以安全地假定值的集合(即使用的值元组)不会改变的情况。

示例(保存为 ds_using_tuple.py):

# I would recommend always using parentheses
# to indicate start and end of tuple
# even though parentheses are optional.
# Explicit is better than implicit.
zoo = ('python', 'elephant', 'penguin')
print('Number of animals in the zoo is', len(zoo))

new_zoo = 'monkey', 'camel', zoo    # parentheses not required but are a good idea
print('Number of cages in the new zoo is', len(new_zoo))
print('All animals in new zoo are', new_zoo)
print('Animals brought from old zoo are', new_zoo[2])
print('Last animal brought from old zoo is', new_zoo[2][2])
print('Number of animals in the new zoo is',
      len(new_zoo)-1+len(new_zoo[2]))

输出:

$ python ds_using_tuple.py
Number of animals in the zoo is 3
Number of cages in the new zoo is 3
All animals in new zoo are ('monkey', 'camel', ('python', 'elephant', 'penguin'))
Animals brought from old zoo are ('python', 'elephant', 'penguin')
Last animal brought from old zoo is penguin
Number of animals in the new zoo is 5

工作原理

变量 zoo 引用一个项目的元组。我们看到 len 函数可以用来获取元组的长度。这也表明元组也是一种序列

我们现在将这些动物转移到一个新的动物园,因为旧的动物园要关闭了。因此,new_zoo 元组包含了一些已经在那里的动物以及从旧动物园带来的动物。回到现实,请注意元组中的元组不会丢失其身份。

我们可以通过在方括号中指定项目的位置来访问元组中的项目,就像我们对列表所做的那样。这被称为索引(indexing)运算符。我们通过指定 new_zoo[2] 来访问 new_zoo 中的第三个项目,通过指定 new_zoo[2][2] 来访问 new_zoo 元组中第三个项目内的第三个项目。一旦你理解了这个用法,这就非常简单了。

包含 0 或 1 个项目的元组

空元组通过一对空圆括号构造,例如 myempty = ()。但是,只有一个项目的元组就没那么简单了。你必须在第一个(也是唯一的)项目后面加一个逗号,以便 Python 能够区分元组和表达式中包围对象的一对圆括号,即你必须指定 singleton = (2 , ) 如果你想要一个包含项目 2 的元组。

给 Perl 程序员的提示

列表中的列表不会丢失其身份,即列表不会像 Perl 中那样被展平。这同样适用于元组中的元组、列表中的元组、元组中的列表等。就 Python 而言,它们只是存储在另一个对象中的对象,仅此而已。

字典(Dictionary)

字典就像一个地址簿,你可以通过知道一个人的姓名来找到他/她的地址或联系方式,即我们将(key)(姓名)与(value)(详细信息)关联起来。请注意,键必须是唯一的,就像你不能在有两个完全同名的人的情况下找到正确的信息一样。

请注意,字典的键只能使用不可变对象(如字符串),但字典的值可以使用不可变或可变对象。这基本上意味着你应该只使用简单的对象作为键。

键值对通过 d = {key1 : value1, key2 : value2 } 的表示法在字典中指定。请注意,键值对之间用冒号分隔,对与对之间用逗号分隔,所有这些都包含在一对花括号中。

请记住,字典中的键值对没有任何顺序。如果你想要特定的顺序,那么你必须在用之前自己对它们进行排序。

你将使用的字典是 dict 类的实例/对象。

示例(保存为 ds_using_dict.py):

# 'ab' is short for 'a'ddress'b'ook

ab = {
    'Swaroop': 'swaroop@swaroopch.com',
    'Larry': 'larry@wall.org',
    'Matsumoto': 'matz@ruby-lang.org',
    'Spammer': 'spammer@hotmail.com'
}

print("Swaroop's address is", ab['Swaroop'])

# Deleting a key-value pair
del ab['Spammer']

print('\nThere are {} contacts in the address-book\n'.format(len(ab)))

for name, address in ab.items():
    print('Contact {} at {}'.format(name, address))

# Adding a key-value pair
ab['Guido'] = 'guido@python.org'

if 'Guido' in ab:
    print("\nGuido's address is", ab['Guido'])

输出:

$ python ds_using_dict.py
Swaroop's address is swaroop@swaroopch.com

There are 3 contacts in the address-book

Contact Swaroop at swaroop@swaroopch.com
Contact Matsumoto at matz@ruby-lang.org
Contact Larry at larry@wall.org

Guido's address is guido@python.org

工作原理

我们使用已经讨论过的表示法创建了字典 ab。然后我们通过使用索引运算符指定键来访问键值对,如我们在列表和元组的上下文中讨论的那样。请注意这个简单的语法。

我们可以使用我们的老朋友——del 语句来删除键值对。我们只需指定字典和要删除的键的索引运算符,并将其传递给 del 语句。此操作不需要知道键对应的值。

接下来,我们使用字典的 items 方法来访问字典的每个键值对,该方法返回一个 tuple 列表,其中每个 tuple 包含一对项目——键后跟值。我们使用 for..in 循环获取每一对,并将其分别赋给变量 nameaddress,然后在 for 块中打印这些值。

我们可以简单地通过使用索引运算符访问一个键并赋值来添加新的键值对,就像我们在上面为 Guido 所做的那样。

我们可以使用 in 运算符来检查键值对是否存在。

关于 dict 类的方法列表,请查看 help(dict)

关键字参数与字典

如果你在函数中使用了关键字参数,那么你已经使用了字典!想一想——键值对是你在函数定义的参数列表中指定的,当你在函数内部访问变量时,它只是对字典的键访问(这在编译器设计术语中称为符号表)。

序列(Sequence)

列表、元组和字符串都是序列的例子,但什么是序列,序列有什么特别之处呢?

主要特性是成员测试(即 innot in 表达式)和索引操作(indexing),后者允许我们直接获取序列中的特定项目。

上面提到的三种序列——列表、元组和字符串,还有一种切片(slicing)操作,它允许我们检索序列的一个切片,即序列的一部分。

示例(保存为 ds_seq.py):

shoplist = ['apple', 'mango', 'carrot', 'banana']
name = 'swaroop'

# Indexing or 'Subscription' operation #
print('Item 0 is', shoplist[0])
print('Item 1 is', shoplist[1])
print('Item 2 is', shoplist[2])
print('Item 3 is', shoplist[3])
print('Item -1 is', shoplist[-1])
print('Item -2 is', shoplist[-2])
print('Character 0 is', name[0])

# Slicing on a list #
print('Item 1 to 3 is', shoplist[1:3])
print('Item 2 to end is', shoplist[2:])
print('Item 1 to -1 is', shoplist[1:-1])
print('Item start to end is', shoplist[:])

# Slicing on a string #
print('characters 1 to 3 is', name[1:3])
print('characters 2 to end is', name[2:])
print('characters 1 to -1 is', name[1:-1])
print('characters start to end is', name[:])

输出:

$ python ds_seq.py
Item 0 is apple
Item 1 is mango
Item 2 is carrot
Item 3 is banana
Item -1 is banana
Item -2 is carrot
Character 0 is s
Item 1 to 3 is ['mango', 'carrot']
Item 2 to end is ['carrot', 'banana']
Item 1 to -1 is ['mango', 'carrot']
Item start to end is ['apple', 'mango', 'carrot', 'banana']
characters 1 to 3 is wa
characters 2 to end is aroop
characters 1 to -1 is waroo
characters start to end is swaroop

工作原理

首先,我们看到如何使用索引来获取序列的各个项目。这也被称为下标操作(subscription operation)。每当你在方括号中指定一个数字给序列时,如上所示,Python 会为你获取序列中对应位置的项目。请记住 Python 从 0 开始计数。因此,shoplist[0] 获取第一个项目,shoplist[3] 获取 shoplist 序列中的第四个项目。

索引也可以是负数,在这种情况下,位置从序列的末尾开始计算。因此,shoplist[-1] 引用序列中的最后一个项目,shoplist[-2] 获取序列中的倒数第二个项目。

切片操作通过指定序列名称,后面跟方括号中用冒号分隔的一对可选数字来使用。请注意,这与你一直使用的索引操作非常相似。请记住数字是可选的,但冒号不是。

切片操作中冒号前的第一个数字表示切片开始的位置,冒号后的第二个数字表示切片停止的位置。如果未指定第一个数字,Python 将从序列的开头开始。如果省略第二个数字,Python 将在序列的末尾停止。请注意,返回的切片从起始位置开始,在结束位置之前结束,即起始位置包含在内,但结束位置不包含在序列切片中。

因此,shoplist[1:3] 返回从位置 1 开始的序列切片,包含位置 2 但在位置 3 停止,因此返回了两个项目的切片。类似地,shoplist[:] 返回整个序列的副本。

你也可以使用负数位置进行切片。负数用于从序列末尾开始的位置。例如,shoplist[:-1] 将返回一个序列切片,其中排除了序列的最后一个项目,但包含其他所有内容。

你还可以为切片提供第三个参数,即切片的步长(step)(默认情况下,步长大小为 1):

>>> shoplist = ['apple', 'mango', 'carrot', 'banana']
>>> shoplist[::1]
['apple', 'mango', 'carrot', 'banana']
>>> shoplist[::2]
['apple', 'carrot']
>>> shoplist[::3]
['apple', 'banana']
>>> shoplist[::-1]
['banana', 'carrot', 'mango', 'apple']

请注意,当步长为 2 时,我们获取位置为 0、2 的项目。当步长为 3 时,我们获取位置为 0、3 的项目,等等。

在交互式 Python 解释器中尝试各种切片组合,这样你可以立即看到结果。序列的妙处在于你可以用相同的方式访问元组、列表和字符串!

集合(Set)

集合是简单对象的无序集合。当对象在集合中的存在性比顺序或出现次数更重要时,就会使用集合。

使用集合,你可以测试成员关系、判断是否是另一个集合的子集、求两个集合的交集等等。

>>> bri = set(['brazil', 'russia', 'india'])
>>> 'india' in bri
True
>>> 'usa' in bri
False
>>> bric = bri.copy()
>>> bric.add('china')
>>> bric.issuperset(bri)
True
>>> bri.remove('russia')
>>> bri & bric # 或 bri.intersection(bric)
{'brazil', 'india'}

工作原理

如果你还记得学校里学过的基本集合论数学,那么这个例子就不言自明。如果不记得,你可以在网上搜索"集合论"和"韦恩图"来更好地理解我们在 Python 中对集合的使用。

引用(References)

当你创建一个对象并将其赋值给一个变量时,该变量只是引用(refer)该对象,并不代表对象本身!也就是说,变量名指向计算机内存中存储该对象的位置。这被称为将名称绑定(binding)到对象。

通常,你不需要担心这个问题,但由于引用而产生的微妙效果是你需要注意的:

示例(保存为 ds_reference.py):

print('Simple Assignment')
shoplist = ['apple', 'mango', 'carrot', 'banana']
# mylist is just another name pointing to the same object!
mylist = shoplist

# I purchased the first item, so I remove it from the list
del shoplist[0]

print('shoplist is', shoplist)
print('mylist is', mylist)
# Notice that both shoplist and mylist both print
# the same list without the 'apple' confirming that
# they point to the same object

print('Copy by making a full slice')
# Make a copy by doing a full slice
mylist = shoplist[:]
# Remove first item
del mylist[0]

print('shoplist is', shoplist)
print('mylist is', mylist)
# Notice that now the two lists are different

输出:

$ python ds_reference.py
Simple Assignment
shoplist is ['mango', 'carrot', 'banana']
mylist is ['mango', 'carrot', 'banana']
Copy by making a full slice
shoplist is ['mango', 'carrot', 'banana']
mylist is ['carrot', 'banana']

工作原理

大部分解释都在注释中。

请记住,如果你想复制一个列表或这类序列或复杂对象(而不是整数等简单的对象),你必须使用切片操作来创建副本。如果你只是将变量名赋给另一个名称,它们都将''引用''同一个对象,如果你不小心,这可能会带来麻烦。

给 Perl 程序员的提示

请记住,列表的赋值语句不会创建副本。你必须使用切片操作来创建序列的副本。

更多关于字符串的内容

我们之前已经详细讨论过字符串。还有什么可以了解的呢?嗯,你知道字符串也是对象,并且有方法可以执行从检查字符串的一部分到去除空格等各种操作吗?事实上,你已经在使用字符串方法了……就是 format 方法!

你在程序中使用的字符串都是 str 类的对象。这个类的一些有用方法在下一个示例中展示。有关此类方法的完整列表,请查看 help(str)

示例(保存为 ds_str_methods.py):

# This is a string object
name = 'Swaroop'

if name.startswith('Swa'):
    print('Yes, the string starts with "Swa"')

if 'a' in name:
    print('Yes, it contains the string "a"')

if name.find('war') != -1:
    print('Yes, it contains the string "war"')

delimiter = '_*_'
mylist = ['Brazil', 'Russia', 'India', 'China']
print(delimiter.join(mylist))

输出:

$ python ds_str_methods.py
Yes, the string starts with "Swa"
Yes, it contains the string "a"
Yes, it contains the string "war"
Brazil_*_Russia_*_India_*_China

工作原理

在这里,我们看到许多字符串方法的实际应用。startswith 方法用于查找字符串是否以给定的字符串开头。in 运算符用于检查给定的字符串是否是字符串的一部分。

find 方法用于定位给定子字符串在字符串中的位置;如果 find 未能找到子字符串,则返回 -1。str 类还有一个很方便的 join 方法,它用字符串作为序列中每个项目之间的分隔符来连接序列的项目,并返回由此生成的一个更大的字符串。

小结

我们已经详细探索了 Python 的各种内置数据结构。这些数据结构对于编写合理规模的程序是必不可少的。

现在我们已经掌握了 Python 的许多基础知识,接下来我们将学习如何设计和编写一个实际的 Python 程序。

results matching ""

    No results matching ""