相信你已经接触过C语言中数组的概念。类似C语言中的数组,Python中有序列的概念。序列是一个集合概念,即,你可以把它理解成泛指Python中一些数据的集合,对于这些数据集合,其数据存储是有先后顺序的,你可以按顺序取出其中的每一个元素。例如,字符串是一个序列,对字符串name可以执行下面的操作
for c in name:
print(c)
你猜猜它的运行结果是什么?
Python中的序列包括字符串,元组,列表,前面我们已经介绍过字符串,下面让我们了解一下列表和元组。
5.1 列表
C语言中的数组是一组固定数量的同种类型元素的集合。比如,下面的语句定义了两个数组:
int a[10]; // 定义一个int型定长数组,含10个int元素
Int b[ ]={ 1, 2, 3, 4 }; // 用可变长方式定义一个数组,
// 元素类型和数组长度根据给出的值猜测
// 此处是一个含4个int元素的数组,定义时可变长,
// 定义好后就变为定长
我们知道,C语言中的数组有很大的局限性:
- 首先,它的元素类型必须相同,比如,int类型数组元素不能存放字符串类型元素
- 其次,它是定长的,我们不能运行时改变它的长度,即不能运行时向它添加或删除元素
Python中的列表解决了以上两个问题,首先,Python中的列表可以存储任意类型的对象;其次,Python中的列表可以在运行时添加或删除元素。
5.1.1 创建列表
1. 创建一个空列表
以下两种方式都可以创建一个空列表:
L1=list() # 调用列表类型的默认构造函数,可以创建一个空列表
L2=[ ] # [ ]表示一个空列表,直接将它赋给一个变量即可
2. 创建一个含有元素的列表
L3 = [ 1, 2, 3 ] # 中括号中用逗号分隔元素,即可创建一个含有元素的列表
L4 = list("Jack") # 把另一个序列作为参数传递给列表类型的构造函数,
# 就可创建一个含有元素的列表
# 你可以查看一下这个代码的结果
构造函数是面向对象程序设计中,为某个类定义的用于创建类实例的函数。使用它可以凭空( 使用不带参数或带默认参数的默认构造函数 )创建一个该类的实例,或通过调用带参数的构造函数,用参数指定的值构造一个类实例,其中的参数说明了该类实例的属性。
3. 列表的元素可以是任意类型
L1=[ 1, 2, 3, 4] # 元素类型相同没有问题
L2=[ 1, 2, "Jack"] # 元素类型不同毫无压力
L3=[ 1,2, 3, [4, 5] ] # 元素可以是复合类型
def adder(x, y):
return x+y
def multiplier(x, y):
return x*y
def divider(x, y):
return x/y
def remainder(x, y):
return x%y
L_func=[adder, multiplier, divider, remainder] # 元素甚至可以是函数
x=1,y=3
for func in L_func:
print(func(x, y)) # 在C语言中也能实现这个功能,但要复杂得多
# 在GUI程序设计中,这个功能对于简化同一菜单下不
# 同菜单项的处理代码具有很好的作用
5.1.2 下标操作和切片
在字符串那一章中,我们看到了对字符串可以执行下标操作得到指定下标处的字符,以及使用切片得到子字符串。对列表也可以使用类似的操作:
>>> L=["Jack","Jane","Henry"]
>>> L[0]
"Jack" # 下标从0开始计算
>>> L[0][2] # L[0]得到"Jack", 对它可以再次运用
# 下标操作,得到结果为'c'
'c'
>>> L[-1] # 可以使用负下标,负下标从-1开始计算
# 可以把列表想象成元素按顺序从左到右排列,
# 从最左边开始计算下标时从0开始
# 从最右边开始计算下标时从-1开始
"Henry"
>>> L[0:2] # 切片时,冒号左侧下标位置包含在内,
# 冒号右侧下标位置排除在外
["Jack", "Jane"]
>>> L[-2: ] # 可以使用负下标,省略冒号右侧的数字,
#表示截取到包括尾部元素在内的元素
["Jane", "Henry"]
>>> L[:2] # 省略冒号左边的数字,表示从包括
# 头部元素开始的位置截取和L[0:2]结果相同
['Jack', 'Jane']
>>> L[-2, -1] # 这样不能截取到最后一个下标所在的元素,
# 因为它不包括在内
["Jane"]
>>> L[0, -1] # 可以混合使用正下标和负下标
['Jack', 'Jane']
5.1.3 列表解析
对于序列,可以使用一个称为列表解析东东,通过对原始序列进行变换,得到我们想要的一个列表:
>>> L=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> L2=[ i*2 for i in L ]
>>> L2
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
>>> L3=[ i for i in L2 if i%2==0 ] # 可以应用条件语句进行选择
>>> L3
[2, 4, 6, 8, 10]
5.1.4 动态添加或删除元素
列表有多个成员函数( 或称方法 )可用于向列表中动态添加元素,我们常用的是向列表尾部添加元素,这需要调用列表的append( ) 方法:
>>> L=[]
>>> L.append(1) # append是一个函数,通过L.appned(...)的形式调用
>>> L.append(2) # 说明它是定义在列表类中的一个成员函数,
>>> L.append(3) # 而不是一个普通的全局函数,在全局范围内不存在这个函数
# 它只能通过一个列表对象(或称变量)+.append(...)的形式调用
>>> L
[1, 2, 3]
删除元素可以使用.pop( ) 方法,该方法从列表对象尾部弹出一个元素,并将该元素的值作为返回值返回。
>>> L=[1, 2, 3]
>>> a=L.pop() # a==3
>>> L
[1, 2]
>>> b=L.pop() # b==2
>>> L
[1]
>>> c=L.pop() # c==1
>>> L
[] # 现在列表L为空
名字的作用域
计算机代码中的名字,包括类名、函数名、变量名等都有作用域,可以简单地把它理解为它在程序中可见并且起作用的范围。
首先看全局作用域: 在所有函数和类外部定义的名字具有全局作用域,它从定义它的位置开始可见并起作用;然后看一下函数作用域,只在函数内部起作用的变量称为具有函数作用域,函数结束则该作用域结束。
name="Jack" define printName(): global name # 不像C语言,Python语言在局部作用域,如函数内部,必须 # 使用global声明一下全局变量的名字,才能使用它 print(name) define printInnerName(): name="Henry" # 这个name的变量名和全局变量的名字相同,但 # 具有局部作用域,它可以看做另一个仅在 # 该函数内部可见并可用的变量,外部的name变量 # 被它屏蔽了,到该函数结束,它的作用域结束 # 这被称为具有函数作用域 print(name) print(name) # 请问打印的是哪个变量,全局的,还是printInnerName()中的
类作用域是仅用于面向对象程序设计中的类所应用的作用域,还记得我们讨论过类和对象的联系和区别吗? 实际上,面向对象程序设计是给对象添加表示对象状态的属性变量以及该类对象可以执行的操作。
比如,一支笔具有墨水颜色、墨水容量、笔壳颜色、有无笔帽、有无按钮等属性,它是通过使用类中定义变量的形式实现的;也有更换笔芯、用笔书写等操作,对象的操作也称对象的方法,对象的操作是通过在类中定义函数实现的:
# 我们的自定义类相当于Python语言的内置类型, # 我们将创建一个类的对象(或称变量)称为实例化一个对象 # 类的实例化是通过调用类的__init__(...)方法得到的 # 类中的函数称为方法,这是面向对象程序设计的术语 # 类中可以有函数名相同,但参数数量不同的函数,从语义上讲,这些同名函数的作用应该是相似的 # 这些同名但参数数量不同的函数形成了函数的重载, # 函数重载的作用是采用不同的方式完成相似的任务 # 比如,对于类的构造函数来说: # 要制作一支笔,可以用另外一支笔作为模板 # def __init__(self, pen=pen): # something # 也可以直接指定笔芯、笔壳颜色,重新制作一支笔: # biXin=BiXin() # def __init__(self, biXin=biXin, shellColor="white"): # something class BiXin(object) { # 每个类中都有__init__()函数,它是一个特殊的函数, # 它必须定义,用于创建一个该类的对象 def __init__(self, color="black", amount=10): # 类的每个成员函数第一个参数都必须是self # 它表示所讨论的变量本身,它是一个隐含参数,调用时不需指定 # 它就是调用该函数时.左侧的对象名 # 上面用默认函数指定默认笔芯的油墨颜色为black,油墨量为10 # 用不同的油墨颜色和油墨量作为参数调用该函数可以创建不同的笔芯 self.color=color # self.color是对象的属性, # 等号右侧的color是构造函数中传入的参数 self.amount=amount # 含义相似 } class Pen(object) { # BiXin()调用BiXin类的__init__()构造函数创建了一个默认笔芯 # 它不带参数,但由于定义该函数时使用了默认参数,实际上它的参数是: # color="black" , amount=1000 def __init__(self, biXin=BiXin(), shellColor='white'): self.biXin=biXin self.shellColor=shellColor def changeBiXin(self, biXin=BiXin()): self.biXin=biXin def write(self): # 调用一次write(),笔芯油墨量减1 print("writing sth.") self.amount-=1 if(self.amount==0): print("笔芯用光了,请更换笔芯") } # 下面使用上面的类创建一些对象,并且使用它们 biXinDefault=BiXin() biXinRed=Bixin(color="red") biXinBlackSmall=BiXin(color='black', amount=500) biXinRedLarge=BiXin(color='red', amount=2000) pen1=Pen() # 默认笔芯,默认笔壳颜色 pen2=(biXin=biXinRed) # 红色笔芯,默认笔壳颜色 pen2.changeBixin(biXinDefault) # 更换笔芯 for i range(0, 10): pen2.write() # 输出10次 "writing sth." # 输出1次"笔芯用光了,请更换笔芯"
现在你能猜猜如何理解类作用域和对象作用域了吗?
5.2 元组
下面讲元组,你可以将元组理解成不可变的列表,除了不能修改元素项以外,元组支持和列表相似的大部分方法。
Python中,元组是用小括号括起来的用逗号分隔的元素集合。
t1=(1, 2, 3, 4, 5)
L=[1, 2, 3, 4, 5]
t2=tuple(L) # tuple是元组的意思,它将序列L转换成元组
请你用python的help()、dir()探索一下元组支持的方法。
5.3 元组的用法
5.3.1 用在print函数中
使用格式字符串对输出进行格式化
Python3的print( ) 函数支持传统的格式字符串,这和C语言中的printf( )函数支持的格式字符串相似。所谓格式字符串也是一个字符串,其中既有常规输出的字符串,也嵌入了一些格式字符,当print()输出时碰到该格式字符时,会用格式字符串后面的变量列表中对应位置的变量替换该格式字符输出,其输出格式由该格式字符指定。如:
# welcome.py names=("Jack", "Jane", "Henry") for name in names: print("%s: welcome!" % name) # %s表示用字符串内容替换这个格式字符 # 第二个%前面的字符串就是格式字符串 # 它后面的是等待输出的变量 # 你猜这段程序的输出是什么 # 这段代码可用作应用程序的登录代码, # 不同的账号登录后,显示针对性的消息
答案:
$ python3 welcome.py Jack: welcome! Jane: welcome! Henry: welcome!
当我们需要使用格式字符串输出多个变量时,可不可以呢? 比如下面的代码:
# welcome2.py
names=("Jack", "Jane", "Henry")
places=("home","school","store","play ground")
L=list(zip(names,places)) # zip 函数把names和places中的元素两两组合
# list()把zip()的返回值转换成一个列表,形如
# [("Jack","home"), ("Jack", "school") ...]
# 我们看到这个列表的每个元素是一个二元组
for name, place in L:
print("%s: welcome to the %s" % name, place) # 请问这样行不行呢
答案是不行。这是因为print( )中的逗号把它前后的内容分成了两个部分,这两个部分分别看作print()的一个参数,print()将分别输出这两个参数,但第一个参数的格式字符串要求输出两个变量,但这个参数没有提供第二个变量,只有name。
这时,需要我们用元组把name和place组合起来,作为格式字符串的待输出变量。上面代码改成:
print("%s: welcome to the %s" % (name, place)) # 这样就行了,输出多个变量时
# 就用多元组
你运行一下这段程序,看看输出结果是什么?
5.3.2 用作函数的返回值
通常函数的返回值只是一个值,可以是整数、浮点数、字符串等,但只有一个。使用Python中的高级数据类型,可以起到将返回值扩展为多个的作用。由于元组是不可变的,而我们通常不希望改变函数的返回值,所以二元组或多元组常常是函数返回值的优质选项。
知识点: 元组的解包
下面的代码,将元组解包,把它里面的元素值赋给单个的变量:
t=(1,2) i, j = t # 经此赋值后,i=1, j=2 ,这种形式对多元组也适用 print(i, j)
下面是的函数求取斐波那契数列第n位和第n+1位的数字:
def fib(n=0):
if n > 0: # fib(n)
i, j = fib(n-1)
return((j, i+j))
else # fib(0)
return(0, 1)
print(fib(10)) # n=10
下面解释这段代码,这段代码运用了两个技术
-
一是递归函数
知识点: 递归函数
递归函数是在代码中调用了函数自身的函数。比如,此处求fib(n)用到了fib(n-1)的结果,就调用fib(n-1),然后用其结果计算fib(n),再把这个计算结果作为返回值返回。想象一下,fib(n-1)是一个中间值,还要对它进行计算,因此,要继续展开,求fib(n-1)要用到fib(n-2)的结果,逐层递归,直到fib(0),而fib(0)有确切的结果,即(0, 1),递归展开至此,计算结束。
根据上述分析,递归函数有两个要点。一是计算第n项时的通项如何用代码表示,二是计算到起点时(即第1项时),对起点有确定的结果,不可再用通项表示。
-
二是将二元组作为函数的返回值,这很好理解
假如我们想要只求斐波那契数列的第n项数字,可以把代码改成:
def fib(n=0):
if n > 1:
i, j = fib(n-2), fib(n-1) # 第n项为通项
return (i+j)
elif n== 1: # 第1项和第0项是边界,它们不能用通项求取
return 1
else:
return 0
网友评论