简介
数据结构基本上就是 – 可以将一些数据结合到一起的结构,换言之用于存储一组相关的数据。
python拥有4种内建数据结构 – 列表,元组(tuple),字典和集合。
我们将看到如何它们,它们又是怎样使我们的编程生涯变的惬意~
列表
列表是一种用于保存有序元素集合的数据结构,即你可以在列表中存储元素序列。
考虑一个购物清单,上面有你需要购买的物品列表,只不过你可能希望以行分隔它们而到了python变成了逗号。
这样想来就容易理解列表了吧。
列表元素应该被封闭在方括号中,这样python才会明白你指定的是一个列表。
一但列表创建完毕,你可以对其元素进行添加,删除和搜索。
正因为可以执行添加和删除操作,我们将列表称作可变类型,即这种类型可以被修改。
对象和类快速简介
尽管我一直推迟讨论对象和类,但现在需要对其进行少量的说明好让你更好的理解列表。后面会在相应的章节深入研究类和对象。
列表是使用对象和类的一个例子。当我们为变量i赋值时,例如赋值5,这相当于创建一个int类(类型)的对象(实例)i。
事实上你可以阅读help(int)的输出更好的理解它。
一个类同样可以拥有方法,即函数,而且它们只能应用于这个类。并且只有当你拥有一个类的对象时才能使用这些功能。
例如,python为列表类提供了一个append方法允许你将新的元素添加到列表尾。
举个例子,mylist.append(‘an item’)将字符串添加到列表mylist的尾部。注意要使用点号访问对象的方法。
一个类还可以拥有字段,而字段只不过是专门应用于一个类的变量而已。当你拥有对应类的对象时就能使用这些变量/名字了。
字段同样利用点号访问,例如mylist.field。
范例:
#!/usr/bin/python
# Filename: 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 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方法对列表进行排序,紧记sort会影响列表本身而不是返回一个被修改后的列表。
这与字符串的工作方式不同。这也是为什么说类标是可变类型而字符串是不可变类型的原因。
然后当在市场购买一样东西后,我们希望将其从列表中删除,del语句正是用武之地。
在这里我们指出希望删除列表中的哪个元素,del就将这个元素从列表中删除。
我们指定的是希望删除列表的第一个元素,因此我们使用del shoplist[0](回想一下,python的索引从0开始)。
如果你想知道list对象的所有方法,详见help(list)。
元组
元组用于保存各种各样的对象。它与列表很相似,但它缺少列表提供的大量功能。
列表的一个主要特点就象字符串一样,它是不可变类型,也就是说你不可以修改元组。
元组通过一组以逗号分隔的元素定义,并以一个可选的小括号闭合。
元组通常用于这样的情形,一个语句或一个用户定义的函数能够安全的假设其使用的一组值(即元组值)不会发生改变。
范例:
#!/usr/bin/python
# Filename: using_tuple.py
zoo = ('python', 'elephant', 'penguin') # 注意小括号是可选的
print('Number of animals in the zoo is', len(zoo))
new_zoo = ('monkey', 'camel', zoo)
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 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既包含已有的动物又包含从老动物园转移过来的新动物。
言归正传,注意一个包含在其它元组内的元组并不会丢失它的身份。
(注:包含元组会引用被包含元组,即在包含元组内对被包含元组的操作会反应到被包含元组自身之上,有点绕口。。。)
像列表一样,我们可以通过一对方括号指定元素的位置访问这个元素。这叫做索引操作符。
我们通过new_zoo[2]访问new_zoo的第三个元素,通过new_zoo[2][2]访问new_zoo的第三个元素的第三个元素。
一但你理解这种语言风格,这样的操作太安逸了。
小括号
虽然小括号是可选的,但我强烈建议你坚持使用小括号,这样一眼就能看出它是个元组,尤其还能避免出现歧义。
例如,print(1, 2, 3)和print((1, 2, 3))是不同的 – 前者打印3个数字而后者打印一个元组(包含3个数字)。
拥有0个或1个元素的元组
一个空元组通过空小括号创建,例如myempty = ()。
不过,指定一个单元素元组就不那么直观了。你必须在其仅有的一个元素后跟随一个逗号,这样python才能区分出
你要的是元组而不是一个被小括号包围的对象的表达式。例如你想要一个包含值为2的单元素元组,则必须写成singleton = (2, )
perl程序员请注意(注:对不起perl程序员,我是perl盲。。。不知道说的啥,以后可能补充翻译)
A list within a list does not lose its identity i.e. lists are not flattened as in Perl. The
same applies to a tuple within a tuple, or a tuple within a list, or a list within a tuple,
etc. As far as Python is concerned, they are just objects stored using another object,
that's all.
字典
字典就像通讯录,只要知道联系人的名字就能找到他的地址或详细信息。即我们将键(名字)与值(相关信息)联系到一起。
注意键必须是唯一的,这就像如果两个人同名你就没法找到正确的信息了。
还有字典的键必须是不可变对象(比如字符串),但字典的值可以是可变或不可变对象。基本上这意味着只能将简单的对象作为键。
字典中的键值对使用语法d = {key1 :value1, key2: value2}指定。
其中键和值由分号分隔而所有的键值对用逗号分隔,并且它们被括在一对大括号内。
记住字典中的键值对是无序的。如果你希望按照特定的顺序排列它们,你只能在使用前自己排序。
而你实际使用的字典是dict类的对象/实例。
范例:
#!/usr/bin/python
# Filename: using_dict.py
# 'ab'是'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'])
# 删除一个键值对
del ab['Spammer']
print('/nThere are {0} contacts in the address-book/n'.format(len(ab)))
for name, address in ab.items():
print('Contact {0} at {1}'.format(name, address))
# 添加一个键值对
ab['Guido'] = 'guido@python.org'
if 'Guido' in ab: # OR ab.has_key('Guido')
print("/nGuido's address is", ab['Guido'])
Output:
$ python 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方法访问字典的键值对,它会返回一个包含键值对元组的列表 – 值跟在键后面。
在for…in循环中我们检索每个键值对并将它们分别赋给变量name和address,之后在循环体中打印它们。
利用索引操作符访问一个键并对其赋予一个值我们可以增加一个新的键值对,就象本例中的Guido那样。
通过dict类的has_key可以检查字典中是否存在某个键值对。你可以执行help(dict)找到字典所有方法的列表。
关键字实参与字典
如果你已经在函数中使用过关键字实参,那么你也已经使用过字典了!
你可以这样理解 – 你在函数定义时的形参列表中指定了键值对,当你在函数中访问这些变量的时候只不过是在访问一个字典
(在编译器设计的术语中这被称作符号表)
序列
列表,元组和字符串都是序列的例子,但到底序列是啥呢?为什么它对我们的意义如此特别?
序列最主要的特点在于支持成员从属测试(即,表达式中的in和not in操作)和索引操作。
其中索引操作允许我们直接地获取序列中的指定元素。
以上说到的三种序列类型 – lists,tuples,strings还支持一种切片操作,允许我们得到序列的一个切片,即序列的部分。
范例:
#!/usr/bin/python
# Filename: 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[:])
Output:
$ python 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
代码如何工作:
首先我们看看如何使用索引得到序列的单个元素。这也被称作下标操作。
正如上面的代码,每当你在序列旁的方括号中指定一个数字的时候,python会获取这个索引所对应的序列元素。
回想一下,python的索引从0开始计算。因此shoplist[0]获取序列shplist的第一个元素,而shoplist[3]获取第四个元素。
索引也可以是负数,这时候位置将从序列尾开始计算。所以,shoplist[-1]引用序列的最后一个元素,shoplist[-2]为倒数第二个。
切片操作的使用方法是先指定序列名后跟一对方括号,其中包含一对可选的由分号分隔的数字。
注意这与你至今使用的索引操作非常相似。记住数字是可选的,但分号不可以省略。
切片操作中的第一个数字(分号前)指出切片的开始位置而第二个数字(分号后)指定将在哪个位置结束。
如果省略第一个数字则python将以序列的起点为开始处,而省略第二个数字时切片会停止在序列的结尾处。
注意切片将在开始处开始,结束于结尾处之前,即包括开始处但不包括结尾处。(注:比如a[1:10],返回的是a[1]到a[9]不包括a[10])。
因此,shoplist[1:3]开始于索引1,包括索引2但止于索引3,即返回一个包含两个元素的切片。与之类似shoplist[:]将返回整个序列的拷贝。
你还能以负索引切片。负数代表从序列的末尾开始反向计算位置。例如shooplist[: -1]返回整个序列,但不包括未末的元素。
另外你还可以为切片提供第三个实参,它代表步长(默认为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交互解释器(这样你能立即看到结果)尝试切片的各种用法吧。
序列类型最棒的地方在于你能够以相同的方式访问元组,列表,字符串!
集合
集合是简单对象的无序集合,适合当更关心集合中的元素是否存在而不是它们的顺序或是它们出现的次数的时候。
使用集合,你可以测试从属关系,是否一个集合是另一个集合的子集,或是寻找两个集合的交集等等。
>>> 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 # OR bri.intersection(bric)
{'brazil', 'india'}
代码如何工作:
代码几乎是自说明的,因为它涉及到的基础集合论知识我们已经在学校学过了。
引用
当你创建一个对象并将其赋给一个变量的时候,变量只是引用了这个对象,而变量并不代表这个对象本身!
换言之,变量名指向你的计算机内存的一部分,而这部分内存用于存储实际的对象。这叫做名字到对象的绑定。
通常你不用关心这些,但你应该知道由于引用造成的一些微妙的影响。
范例:
#!/usr/bin/python
# Filename: reference.py
print('Simple Assignment')
shoplist = ['apple', 'mango', 'carrot', 'banana']
mylist = shoplist # mylist只是指向相同对象的另一个名字
del shoplist[0] # 我购买了第一个水果,所以把它从清单中删除
print('shoplist is', shoplist)
print('mylist is', mylist)
# 注意列表shoplist和mylist打印了相同的内容,其中都不包括’apple’,因为它们指向的是相同的对象。
print('Copy by making a full slice')
mylist = shoplist[:] # 以全切片创造一个列表的完整拷贝
del mylist[0] # 删除第一个元素
print('shoplist is', shoplist)
print('mylist is', mylist)
# 注意现在两个列表指向不同的对象(注:回忆一下,切片操作会返回一个新的对象!)
Output:
$ python 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程序员请注意
记住对于列表的赋值语句并不会创建一个拷贝。必须使用分片操作创建序列的拷贝。
(注:实际上切片操作不是唯一的选择,内见的工厂函数比如list,typle, set等都能达到同样的目的)
关于字符串的更多知识
前面我们已经详细讨论过字符串了。在这里我们还会了解到什么呢?
呵呵,你知道字符串同样是一种对象并拥有很多方法吗? 从检查字符串的一部分到删除其中的空格应有尽有!
你在程序中使用的所有字符串都是str类的对象。下面的例子会演示str类中的一些有用的方法。全部方法的列表,参见help(str)。
范例:
#!/usr/bin/python
# Filename: str_methods.py
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))
Output:
$ python 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方法用来寻找给定的字符串在字符串中的位置,如果没找到对应的子串则返回-1。
str类还有一个简洁的连接序列中每个字符串并返回连接后的字符串的方法join,其中每个字符串都将以指定的字符串分隔。
小结
我们已经详细研究了python中的各种内建数据结构。这些数据结构对于编写合理规模大小的程序是必须可少的。
现在我拥有了许多的python的基础知识,下一步我们会看到如何设计和编写一个实用的python程序。