您的位置:首页 > 编程语言 > Python开发

Python基础--第4章 变量与操作——编写代码的基石

2018-03-07 09:47 411 查看


学过数学方程的同学都知道:利用方程来解应用题时,常常会设个x来代表应用题中的某一个数。在编写代码中也是类似,会定义一个x(或其他名字)来指代某个数字对应的意义,这个名字就叫做变量。
 
在实际应用中,变量可以指代输入,也可以指代结果。一旦编好程序后,计算机就会把变量指代的具体数代入到内存里,并进行运算。
 
变量是编程语言中最基本的术语,代指计算机中存放的可变数据,它可以用来指定各种数据,如整数,小数、字符或被调用的一片内存空间。如:
Val= “hello”
上面的代码在执行时,系统中就会有一个变量叫Val,它指向内存中存放字符串 “hello”所在的位置。如图4-1。

图4-1 变量
可以说程序中最基本的就是变量。定义了变量之后,才可以通过编写具体的逻辑对变量进行操作计算,从而实现想要的功能。
 

4.1  变量规则

在实际情况中,程序需要处理不同类型的变量,所以在Python内部,变量是要分为各种类型的。比如,整型变量就是只可以代表整数变量,浮点型变量就是只可以代表带小数点的数字变量,字符型变量就是只可以代表字符变量。
编写Python代码过程中,定义变量与变量赋值必须在一步完成。Python内部会根据所赋数值的类型来创建该变量。例如:定义整型变量a等于5,可以写成a=5。这时Python内部就会在内存中创建一个类型为整型的变量a,并让这个a的值为5。在后面的代码中只要用到a就相当于用到了数值5。

4.1.1  变量的本质——对象

Python语言可以根据赋值的语句来自动创建对应类型的变量,它是怎么实现的呢?
这是因为Python内部使用了一个对象模型,这个对象模型用来储存变量及其对应的数据。在Python语言中,任何类型的变量都会被翻译成一个对象,这就是变量的本质。
Python内部的对象模型由3部分组成:身份、类型和值,具体意义如下:
l 身份:用来标识对象自身的唯一的标识。通过调用函数id可以得到它。这个身份标识值可以被理解成该对象的内存地址。
l 类型:用来表明对象可以存放的类型。具体的类型限制了该对象保存的内容、可以进行的操作、遵循的规则。想要查看某个对象的类型可以调用函数type。
l 值:对象所存储的具体数值。

4.1.2  同时定义多个变量

实际写代码中,可以通过一条语句定义多个变量,彼此之间用逗号隔开即可。例如,在一条语句中为多个变量同时赋值,可以写成如下样子:
var1,var2,var3=value1,value2,value3
这时,系统会根据value1、value2、value3的值类型,分别定义var1、var2、var3三个变量,并为它们赋值。

4.1.3  变量类型介绍

不同的Python版本对类型归类会略有差异,这里以Python 3为主。在Python 3中,所划分的类型有六个:Numbers(数字)、String(字符串)、List(列表)、Tuple(元组)、Sets(集合)、Dictionaries(字典)。
其中,Numbers类型是一个数值类型的合集,具体又可以细分为int(整型)、float(浮点型)、bool(布尔型)、complex(复数)等类型。同时每个类型都有其自身的特点和规则,在后文中会一一介绍。

4.1.4  类型的内置函数

Python将自身所支持的对象类型定义成了一个个的类,任何类型(Numbers、String、List……)都是一个类。每个类都有自己的方法和属性,通过类的方法和属性来规范该类型数值的操作。
同时Python中也内置了两个帮助函数,方便查看和学习某个类型的用法,具体如下:
l dir :用来查询一个类或者对象的所有属性,如dir(list)。
l help :用来查询具体的说明文档,如help(int)。
在阅读下文之前,读者可以使用这两个函数对Python中的类型进行查询,看看系统是如何介绍具体类型的。

4.2  Numbers(数字)类型

 Python3中的Numbers类型与大多数编程语言类似,它是一个数值类型的合集,具体又可以细分为int(整型)、float(浮点型)、bool(布尔型)、complex(复数)等类型。

4.2.1  获取对象类型

在4.1.1节中介绍过,每个对象都可以通过内置的函数type来查询该变量所指的对象类型。这里使用如下代码来演示type函数的使用:
a,b, c, d = 32.6, 58, True,8+7j               #定义四个变量a、b、c、d,为它们赋予不同类型的值
print(type(a),type(b), type(c), type(d))           #将这四个变量的类型打印出来
运行后会得到如下结果:
<class'float'> <class 'int'> <class 'bool'> <class'complex'> 
结果中的四个尖括号内的内容表示了对应变量的类型。即,a的类型是float,b的类型是int,c的类型是bool,d的类型是complex。
函数type对于所有的Python类型(包括后文中还会讲解的String、List等类型)都是有效的,其用法也是一样。

4.2.2  算术运算符

Numbers类型支持多种算术运算处理,具体的算术运算符见表4-1。
表4-1  算术运算符
[align=center]
运算符
描述
+

-

*

/
除,会生成浮点数结果
%
取模(余数)
** 或 pow(x, y)
幂。例如:3**4等价与pow(3,4)即 3的4次方
//
取整除
abs(x)
取绝对值
int(x,[base])
将x转换为整型。
x可以是字符串或其他数字;base是可选参数,默认为10,表示将字符串x以10进制转化为整数。
当base被赋值时,x必须是字符串;当x为浮点数时,转成的整数会将小数点后面全部舍掉。
如果想要更精确的转化,推荐用math库里面的floor和ceil函数来明确转换方式
float(x)
将x转换为浮点型
complex(re,im)
生成复数。re为实数部分,im为虚数部分。
例如:complex(8,7) ,则生成一个复数 8+7j
c.conjugate()
取c的共轭复数,假如c=8+7j,则 c.conjugate() = 8-7j
divmod(x, y)
返回商和余数。例如:divmod(13, 4) = (3, 1)
[/align]

4.2.3  赋值运算符

下面来介绍Numbers类型中所支持的赋值运算符,见表4-2。
表4-2  赋值运算符
[align=center]
运算符
描述
+=
加。例如:a+=b等价于 a=a+b
-=
减。例如:a-=b等价于 a=a-b
*=
乘。例如:a*=b等价于 a=a*b
/=
除。例如:a/=b等价于 a=a/b
%=
取模。例如:a%=b等价于 a=a%b
**=
幂。例如:a**=b等价于 a=a**b
//=
取整除。例如:a//=b等价于 a=a//b
[/align]

4.2.4  生成布尔型结果的运算符

下面来介绍Numbers类型中所支持的比较运算符,见表4-3。
表4-3  比较运算符
[align=center]
运算符
描述
==
等于
!= 或者 <>
不等于

大于

小于
>=
大于等于
<=
小于等于
is
指针等于
is not
指针不等于
[/align]上表中列出了八个比较运算符,它们的优先级是相同的。在使用x < y <= z这样的链式比较式时,它会从左到右的顺序来进行计算,相当于x< y and y <= z。
另外,复数类型是不能进行大小比较的,只能比较是否相等。
比较运算符的优先级会比算术运算符低,例如下面的情况:
print(1.1==1.1-0.1)                    #会输出False
上面的代码会先计算1.1-0.1,得到结果1.0;然后再与1.1进行比较,得到结果False。尽管Python内部有这样优先级的规则,还是建议大家在写类似这样得代码时为后面的算术运算加上括号。例如:
print(1.1==(1.1-0.1))                 #将后面的算术运算括起来,表示优先计算。
像上面这样的代码具有更好的可读性。令代码易读、最大化的消除代码歧义,是一个优秀工程师必备的开发习惯。
提示:
在使用相等的比较符“==”时,建议要将常量在左,变量在右。这么做的原因是,一旦在编码中发生疏忽,将“==”写成“=”时,系统便会自动报错。不会产生由于将赋值语句当成比较语句,使得比较结果永远返回“真”的逻辑错误。

4.2.5  慎用is函数

表4-3中,is函数的作用是比较指针是否相等。这里的指针代表存放对象的内存地址,即,is函数是比较两个对象所在的内存是否相等。
那么is函数与“==”的区别是什么呢? “==”只是判断两个对象的内容是否相等。而is函数不仅比较对内容是否相等,还比较指针是否相等。即,两个对象如果使用“==”得到的是true,使用is得到的不一定是true,因为它们的地址有可能会不同;但是如果使用is得到的是true,使用“==”就会一定是true,因为他们本身就是一块内存的内容。例如:
a=-256
b=-256
print(a==b)         #判断a和b的内容是否相等
print(a is b)       #判断a和b的内存是否相等
print(id(a),id(b)) # id的意思是打印出对象的地址
执行代码,输出如下结果:
True
False
151744048151743312
可以看出虽然a和b都是-256,但是他们的地址并不相等。
当然上面的例子只是一个很小的点,由于Python在优化效率时做了好多工作,导致存储对象的内存分配有很多隐含的规则。当不了解这些规则时,使用is函数就会带来很多意想不到的麻烦。所以建议能使用“==”搞定的尽量不要使用is函数。
那么Python中关于对象内存的分配规则是什么样的呢?一起来往下学习。

1. 赋值的机制

在Python中等号赋值是指直接将对象的内存指针赋值,如下例子:
x=23
y =45
print("x,y:",id(x),id(y),x,y)
x= y
print("x,y:",id(x),id(y),x,y)
执行代码,输出结果如下:
x,y:1725105328 1725106032 23 45
x,y:1725106032 1725106032 45 45
可以看到x与y在赋值之前各自有自己的指针。当赋值之后,x与y不仅有相同的值,而且还有相同的指针。
在赋值之前的x=23中,1725105328所指的内存哪去了呢?其实这个23还在内存中。当系统判断没有变量引用该内存地址时,会使用内存回收机制,按设定好的规则来回收该部分内存。

2. 缓存的重用机制

在优化整数对象时,Python会根据对象的频繁使用程度及内存占用的角度考虑,按照一定规则把某些对象缓存起来。当程序的其他部分代码使用该值时,系统会先去缓存中查找,并且直接引用找到的缓存,不需要额外创建。具体规则如下:
l 在范围[-5, 256]之间的小整数,如之前在程序中创建过,就直接缓存。后续不再创建,全局生效;
l 对于字符串对象,如之前在程序中创建,过就直接缓存。后续不再创建,全局生效;
l 在大于256的整数中,只要在该代码块内创建过就直接缓存。后续不再创建,本代码块生效;
l 对于大于0的浮点型对象,只要在该代码块内创建过就直接缓存。后续不再创建,本代码块生效;
l 对于小于0的浮点型对象,不进行缓存,每次都需要额外创建;
l 在小于-5的整数中,不进行缓存,每次都需要额外创建。
 

4.2.6  实例:演示Python的缓存机制

下面通过一段程序来演示Python具体的缓存规则
实例描述
定义两个变量,为它们赋予相同的值。将这两个变量的指针打印出来。分别按照缓存的具体规则为它们赋值,观察赋值后的两个变量的指针是否相等。如果相等,则表明Python内部对其使用了缓存机制。
示例代码中使用了6种情况进行对缓存机制的演示,分别对应于4.2.5节中“2.缓存的重用机制”中列出的具体规则。
代码4-1:缓存机制演示
01   #在范围[-5, 256]之间的小整数
02   int1=-5
03   int2=-5
04   print("[-5, 256]情况下的两个变量指针",id(int1),id(int2))
05    
06   #对于字符串
07   s1="3344"
08   s2= "3344"
09   print("字符串情况下的两个变量指针",id(s1),id(s2))
10    
11   #大于256的整数
12   int3=257
13   int4=257
14   print("大于256的整数情况下的两个变量指针",id(int3),id(int4))
15    
16   #大于0的浮点数
17   f1=256.4
18   f2=256.4
19   print("大于0的浮点数情况下的两个变量指针",id(f1),id(f2))
20    
21   #小于0的浮点数
22   f1=-2.45
23   f2=-2.45
24   print("小于0的浮点数情况下的两个变量指针",id(f1),id(f2))
25    
26   #小于-5的整数
27   n1=-6
28   n2=-6
29   print("小于-5的整数情况下的两个变量指针",id(n1),id(n2))
上面的代码运行后会得到如下的输出:
[-5,256]情况下的两个变量指针 1882718512 1882718512
字符串情况下的两个变量指针 153383856 153383856
大于256的整数情况下的两个变量指针 153472560 153472560
大于0的浮点数情况下的两个变量指针 153023472 153023472
小于0的浮点数情况下的两个变量指针 153023616 153023640
小于-5的整数情况下的两个变量指针 153471792 153471824
读者可以对照着具体的代码情况和打印的指针结果来理解4.2.5节中“2.缓存的重用机制”介绍的规则,来加深对该部分知识的体会。
该缓存规则在不同的代码块中也会有不同的表现。具体演示如下:
在下面的代码中,添加一个函数fun。这个函数fun的函数体就是一个新的代码块。在函数fun之外的代码也是一个代码块。在fun的函数体内定义一个与fun之外代码块相同名字的变量(如int1),来测试下同名变量在不同代码下的缓存规则。
接着又调用了变量int2。int2在改代码块中没有定义,系统会去外层代码块中找是否有该变量。如果有就直接访问(访问的就是第一个代码块中的int2),如果没有就会报错。具体代码如下:
代码4-1:缓存机制演示(续)
30   def fun():
31       #[-5,256]
32       int1=-5
33       print("fun中-5的指针",id(int1),id(int2))
34       
35       #字符串类型
36       s1="3344"
37       print("fun中3344字符串的指针",id(s1),id(s2))
38       
39       #>256
40       int3=257
41       print("fun中257的指针",id(int3),id(int4))
42       
43       #浮点类型
44       f1=256.4
45       print("fun中256.4的指针",id(f1),id(f2)) 
46       
47       #<-5
48       n1=-6
49       print("fun中-6的指针",id(n1),id(n2))
50       
51   fun()
运行程序,输出如下:
fun中-5的指针 1882718512 1882718512
fun中3344字符串的指针 153383856 153383856
fun中257的指针 153472368 153472560
fun中256.4的指针 153023736 153023640
fun中-6的指针 153471760 153471824
根据打印结果可以看出:
l  对于-5与字符串“3344”的输出,无论是在同一个代码块,还是不同的代码块,它们都是有相同的缓存内容。
l  对于257与256.4的输出,在同一个代码块中的指针是一样的,在不同的代码块中的指针不同。
l  对于-6的输出,每次都不一样。表明没有对其缓存操作。

4.2.7  布尔型关系的运算符

布尔型数值之间需要逻辑关系运算。在计算机底层的逻辑电路中,布尔型运算应用的更为广泛。Python同样支持布尔型的运算,在需要使用多个布尔变量联合判断的结果作为条件的场景下,常常会用到布尔型关系运算。具体的运算符见表4-4。
表4-4  布尔型关系运算符
[align=center]
运算符
描述
And
取与。左右都为True,结果才为True
Or
取或。左右有一个为True,结果就为True
not或 !
取反。如果是True结果就为False
[/align]基于上表的内容,还需要额外补充几点:
l and也是个短路运算符,它只有在第一个运算数为True时,才会计算第二个运算数的值;
l or是个短路运算符,它只有在第一个运算数为False时,才会计算第二个运算数的值;
l not的优先级比其他类型的运算符低, not a == b相当于not (a == b);而 a == not b是错误的。

4.2.8  位运算符

位运算符属于更细微的操作,它是针对每个字节中的每个位(bit)进行操作的。支持的运算符见表4-5。
表4-5  位运算符
[align=center]
运算符
描述
&
按位与
|
按位或
^
按位异或
~
按位取反
<< 
按位左移
>> 
按位右移
[/align]位运算符在网络通信、硬件驱动领域应用比较广泛。在编写应用层程序时,用位来存储取值范围小的整型变量,可以一定程度的节省内存空间。
 

4.3  Strings(字符串)类型

字符串类型属于Python中序列类型(sequence)的一种。序列类型可以理解成由一个容器和容器中的元素组成的结构体,其中的元素是有一定序列顺序的。
字符串类型更像是一个数组,按照一定顺序放置着一个个的字符,如图4-1所示。

图4-1 字符串

4.3.1  字符串描述

字符串可以使用多种表达方式来表示,大体可以分为两类:
l 单行字符串:使用单引号(’’)、 双引号(””)来表示;
l 多行字符块:三个(双或单)引号来表示。如: """xxx """ 。

1. 单行字符串

单行字符串表明引号内的字符串必须是单行。如果隔行了,需要用反斜杠“\”符号连接。例如:
a='line1line2'                   # a为单行字符串,里面的内容必须在一行里
a='line1\                           #a为单行字符串的另一种写法。如果隔行了,需要用\来连接下一行
line2'
单行字符串的输出也是单行的。即,print(a)得到的字符串为:line1line2。

2. 多行字符串

在多行字符串的表述中,每行之间可以直接用回车符号分开。输出也是按照代码中的回车符号来换行的。例如:
#b为多行字符串
b='''line1    
line2
line3'''
多行字符串的输出也是多行,即,print(b)输出的结果为:
line1
line2
line3
需要注意的一点是:定义多行字符串时,千万不要把注释写在字符串定义符中间。这样会把注释也当成字符串了。例如:
b='''line1     #b为多行字符串
line2
line3'''
print(b)输出的结果就变成:
line1     #b为多行字符串
line2
line3
 
提示:
单行字符串在逻辑处理方面用的比较多。多行字符串常用与描述大段的函数说明,或是对文件或类的帮助说明,有时也会用来以块的方式注释代码。
除了直接定义单行字符串和多行字符串之外,也可以使用str函数对其他类型转换,得到字符串。例如:str(5)就是可以得到一个字符串‘5’。

4.3.2  转义符

在4.3.1节中提到了一个反斜杠“\”符号,它可以将多个单行连成一个单行。这个符号叫做续行符,表示下一行是上一行的延续。还可以使用"""..."""或者'''...'''跨越多行。

1.转义符介绍

转义字符是一种特殊的字符。它在代码中看得见,但在输出时要被转换成特殊意义。Python中有很多转义字符,具体见表4-6。
表4-6 转义字符
[align=center]
转义字符
描述
\(在行尾时)
续行符
\\
反斜杠符号
\'
单引号
\"
双引号
\a
响铃
\b
退格(Backspace)
\e
转义
\000

\n
换行
\v
纵向制表符
\t
横向制表符
\r
回车
\f
换页
\oyy
八进制数yy代表的字符。例如:\o12代表换行
\xyy
十进制数yy代表的字符。例如:\x0a代表换行
\other
其他的字符以普通格式输出
[/align]

2.原字符串(rawstring)

转义字符并不是所有情况下都是有用的。有时还需要输出含表4-5中的字符,即,不想让表4-5中的字符发生转义。这时需要在字符串前面加个“r”或者“R”,变为原始字符串(raw string)。这种字符串的内容会与代码中的内容完全一致。例:
aa='line\tline'                     #aa里面含有转义字符\t
print(aa)                         #将aa输出:line    line
bb=R'line\tline'                   #bb里面含有转义字符\t,同时前面加个R,关闭转义功能
print(bb)                         #将bb输出:line\tline
也可以使用函数repr(str)将字符串aa(含有转义字符的字符串)转成关闭转义功能之后的原始字符串(raw string)。例如:
aa='line\tline'                    #aa里面含有转义字符\t
print(repr(aa))                    #将使用repr函数将aa的原始字符串输出:line\tline
变量aa里面含有转义字符“\t”。在直接使用print函数输出时,将里面的“\t”变成了制表符;当通过repr函数转化之后再使用print函数输出时,转义字符“\t”停止了转义,直接被输出出来了。

3.原字符串(rawstring)原理

函数repr(str)与在字符串前面加上“r”或者是“R”的原理一样,都是在字符串str中查找反斜杠“\”字符。如果找到,就在该字符前面再加一个反斜杠“\”,组成两个反斜杠字符“\\”。根据表4-6中所示,两个反斜杠字符“\\”生成的字符串会转义成一个反斜杠字符“\”,这样就会把原来str中那个不需要转义的反斜杠“\”输出来了。
在字符串前面加上“r”或者是“R”的方式是直接作用在字符串常量上,相对不容易出错。而函数repr(str)是直接作用在字符串变量上。在使用repr(str)函数时就要多加注意,输入的字符串参数一定要是一个正常的字符串。如果对一个原字符串(raw string)进行repr转化时,便会使反斜杠变得更多。例如:
bb=R'line\tline'                    #定义个raw string
print(repr(bb))                     #对raw string进行repr,输出:'line\\tline'
上例中将原字符串(rawstring)传入repr函数中,生成的结果中出现了两个反斜杠字符,这是我们不想看到的。所以在使用repr(str)函数时一定要明确传入参数的字符串类型,不能是原字符串。
关于函数repr的更多内容可以参考9.6.1的注意部分。

4.原字符串转成转义字符串

前面介绍了转义字符串变量可以通过repr(str)函数变为非转义字符串变量,这里再介绍一个非转义字符串变量转变成转义字符串变量的方法,见下面的例子:
bb=R'line\tline'                    #定义个raw string
print(eval("'"+bb+"'"))              #将raw string变量前后加上引号字符,通过eval函数即可生成转义
                                 #字符,输出:line    line

5.规避转义字符带来的问题

转义字符的存在一定程度的简化的编码的复杂度,但在某些情况下也容易带来问题。例如当使用一个字符串来表示某个磁盘上的文件路径时,这么写就会产生错误:
path= "c:\temp.txt"
这时,如果使用打开文件函数open,来打开指定的文件时,代码如下:
open(path)
就会报错。系统会将“\t”进行转义,认为要打开的文件路径为:“c:    emp.txt”,于是会提示找不到该文件。
面对这种情况的解决办法是,将路径写成如下的方式来避免错误:
path= r"c:\temp.txt"

path= "c:\\temp.txt"

path= "c:/temp.txt"

6.字符串转化扩展

前面介绍了通过在字符串前面加上“r”或者是“R”,将字符串转成原字符串(raw string),实际上这种用法还有很多:
l 可以在字符串前面加上“b”,将字符串转成二进制字符串;
l 可以在字符串前面加上“u”,将字符串转成Unicode编码的字符串。
提示:
Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。想要了解更多可以参考百度百科:https://baike.baidu.com/item/Unicode/750500?fr=aladdin

4.3.3  屏幕I/O及格式化

print函数是用来将指定的内容以字符串的形式输出到屏幕上,属于屏幕I/O的一种。另外,还有与之对应的输入函数input,它的作用是将键盘的按键信息输入到系统。关于屏幕I/O的使用不仅仅涉及到函数本身的参数,还有字符串格式化方面的知识。下面就来具体的学习一下。

1.屏幕输出函数print

print语句的参数如下:
print([object,...][, sep=' '][, end='endline_character_here'][, file=redirect_to_here]) 
方括号内是可选的,具体参数的意义:
l Object:要输出的内容;
l sep:分割符;
l end:结束符;
l file:重定向文件(默认值为屏幕输出)。
通过调整print函数中字符串的格式,来控制屏幕上输出字符串的样子。这个调整的过程就叫做字符串的格式化。
字符串格式化应用最多的场景之一,就是在print函数中使用。在编程过程中,还会有很多场景都需要用到字符串格式化,例如从屏幕输入、将字符串写入文件等。
字符串格式化的方法有手动拼接和使用占位符。
l  手动拼接就是简单的用加号将不同的字符串连接起来;
l  使用占位符的方法相对比较高级,应用也比较广泛。主要是先制定一个模板(这个模版就是规定好的格式),在这个模板中某个或者某几个地方留出空位来(用占位符来代替),然后在那些空位(占位符对应的位置)填上具体内容。

2.占位符

占位符在一个字符串中仅仅是占据着那个位置,输出时替换成具体的内容。然而占位符并不是可以随意的替任何内容占位,它也会有严格的规则。即,每一个占位符只能替一种指定的类型占位。在字符串中需要选择相应的占位符来替具体的内容进行占位。
例如代码:"Hello%s" % "world"将会得到一个“Hello world”的字符串。这其中的“%s”就是一个占位符,代表“%s”的位置要用后面的一个字符串来代替。更多的占位符见表4-7。
表4-7  占位符
[align=center]
占位符
说明
%s
字符串
%r
非转义功能的字符串
%c
单个字符
%b
二进制整数
%d
十进制整数,一般会写成%nd,n代表输出的总长度。
%i
十进制整数
%o
八进制整数
%x
十六进制整数
%e
指数 (基底写为 e)
%E
指数 (基底写为 E)
%f
浮点数,一般会写成%m.nf,m代表总长度,n代表小数点后几位
%F
浮点数,与上相同
%g
指数(e) 或浮点数 (根据显示长度)
%G
指数(E)或浮点数 (根据显示长度)
[/align]上表中的浮点型占位符相对复杂,有必要详细说明一下。
对于%m.nf这种形式的占位符,m代表设定的总位数,n代表设定的小数点后的位数。当然,具体的输出结果还要根据生成的浮点型数字本身的位数来定。具体有如下几种情况:
l 输出的浮点型长度不足总长度m时,则会在前面补空格;
l 输出的浮点型小数点后的位数不足n时,则会在小数点后面补0;
l 当总长度m小于实际整数长度时,则会保存数据完整性,令总长度m失效,输出结果右对齐;
l 当总长度m小于实际整数加上要输出的小数长度之和时,则会保存数据完整性,令总长度m失效,输出结果右对齐。
将这几种情况分别用代码演示如下:
print("总长为8,小数点后为2,实际长度不足,需要前补空格\n输出:%8.2f"%23.45)
print("总长为8,小数点后为4,小数点后位数不足,会在小数点后面补0\n输出:%8.4f"%23.45)
print("总长为2,小数点后为0,总长度比实际整数长度还小,总长度失效\n输出:%2.0f"%23.45)
print("总长为6,小数点后为6,总长度小于实际整数长度与输出小数长度之和,总长度失效\n输出:%6.6f"%23.45)
上面的代码中,在字符串后面加一个“%”,表示将“%”后的内容填入前面的占位符里。运行之后输出如下结果:(其中□表示一个空格)
总长为8,小数点后为2,实际长度不足,需要前补空格
输出:□□□□ 23.45
总长为8,小数点后为4,小数点后位数不足,会在小数点后面补0
输出:23.4500
总长为2,小数点后为0,总长度比实际整数长度还小,总长度失效
输出:23
总长为6,小数点后为6,总长度小于实际整数长度与输出小数长度之和,总长度失效
输出:23.450000
读者可以根据代码中的m和n的值来对照着具体的输出,理解上面的规则。

3.演示手动拼接的格式化方法

前文说到格式化可以手动拼接,也可以使用占位符的方法。这里就来演示一下每个方法的具体用法。
先看一个手动拼接的例子:
x=5                                                                                     #定义个整型数5
print(“:”,str(x).rjust(2),str(x*x).rjust(3), end=', ')       #占2位右对齐的方式输出x本身,
  #占3位右对齐的方式输出x*x,结尾用逗号
print(str(x*x*x*10).rjust(4))                         #占4位右对齐的方式输出x*x*x,结尾用默认的回车
在代码print函数中,先是使用了字符串转化函数str(x)将整型变量x转成字符串,接着使用了字符串对象的str.rjust方法将字符串格式化输出。
str.rjust(n)的作用是将字符串靠右对齐,中间的参数n代表输出的长度。如果字符串不足这个长度,会默认在左边填充空格;如果字符串的长度大于n,则会令n失效,并不会截断字符串,而是把字符串全部显示。类似的方法还有字符串左对齐的方法str.ljust,和字符串居中对齐的方法 str.center 。
这段代码运行后,会输出如下结果:
:  □5 □25, □125
“:”之后是一个空格分割符;接下来的5前面有一个空格补位;然后有一个空格分隔符;再下来的25前面也会有一个空格补位;然后是一个逗号;最后的125前面同样也出现了一个补位的空格。

4.演示使用占位符的格式化方法

下面再演示一下格式化的方式,输出与手动拼接例子一样的字符串:
x=5                                                                                     #定义个整型数5
print(":",'%2d %3d,%4d'%(x, x*x, x*x*x))             #在模版中放置3个占位符,并指定输出长度
使用占位符的方法就是在模版字符串后面加个“%”符号,在“%”符号后面跟上要替换占位符的内容。
上面的代码执行后,得到如下输出:
: □5□25, □125
可以看到,输出结果与手动拼接例子中的结果是一样的。

5.演示使用str.format的格式化方法

使用“%”符号来进行字符串的格式化时,要求占位符出场的先后顺序必须与后面的具体内容相匹配。而使用str.format方法对字符串格式化时,含有占位符的模版与后面内容间的映射关系可以更加灵活。
(1)基本用法
先来看看str.format的基本用法,举例如下代码:
x=5                                                                                                #定义个整型数5
print(":",'{0:2d}{1:3d}, {2:4d} {0:4d}'.format(x, x*x, x*x*x)) #用4个占位符引用3个具体内容
在print函数里,字符串模版中的占位符都被加上了一个大括号。每个大括号里的第一项用于维护与后面具体内容的对应关系,它代表要引用format函数中的第几个具体内容。这样就不需要让模版里的占位符与后面的具体内容顺序一一对应了。
上面的代码执行后,得到如下输出:
: □5□25, □125 □□□5
可以看到,第一个数与最后一个数都是5。这表明format中的第0个具体内容x被引用了两次。使用str.format方法可以使模版与后面的具体内容间的映射关系可以更加灵活。
(2)简洁用法
如果不追求具体的显示格式,str.format方法还有更简单的使用方法,如下:
x=5                                                                                                       #定义个整型数5
print(":",'{0}{1}, {2} {0}'.format(x, x*x, x*x*x))                    #在模版中直接指定后面具体内容的顺序即可
如代码中的注释所说,在模版中直接指定后面具体内容的顺序即可,系统会自动根据变量类型匹配对应的占位符。该代码运行后,输出如下:
:5 25, 125 5
由于没有指定格式,只是原样将模版中的序号翻译成对应变量的值输出出来。
(3)扩展用法1——结合列表或元组类型
前面的简洁用法中在str.format里面需要放置待输出的具体内容,其实这部分的参数还可以使用一个列表或是元组类型的变量来替换(后面在4.4节会介绍列表类型、4.5节会介绍元组类型),具体示例如下:
mylist= [5,25,125]                                                        #定义一个列表变量
print(":",'{0}{1}, {2} {0}'.format(*mylist))           #模版中直接指定后面列表变量里具体元素的顺序即可
mytuple= (5,25,125)                                                    #定义一个元组变量
print(":",'{0}{1}, {2} {0}'.format(*mytuple))        #模版中直接指定后面元组变量里具体元素的顺序即可
这段代码将format函数里面的参数分别替换成了列表和元组两个变量。该代码运行后,输出了与前面简洁用法例子中一样格式的字符串,如下:
:5 25, 125 5
初学者可以先跳过这部分。待看完后面4.4节和4.5节的内容后,再来复习该部分内容。
(4)扩展用法2——结合字典类型
其实str.format还有更高级的用法,需要配合字典类型(在后面章节会讲到),具体示例如下:
d= {'x':5, 'xx':25, 'xxx':125}                                                 #定义一个字典
print('xis {x}, xx is {xx}, xxx is {xxx}.'.format(**d))   #模版中只需要将字典里对应的名称填入即可
这次模版中的大括号里不再是整数的序号,而是具体的字符串。这个字符串要与format中的字典变量里的具体条目名称相对应。系统会根据字典里的条目名称找到对应的值替换到字符串中去。
该代码运行后,输出如下:
xis 5, xx is 25, xxx is 125.
初学者可以先跳过这部分。待看完后面4.7节的字典类型后,再来复习该部分内容。

6.键盘输入

下面介绍另一个屏幕I/O有关的内置函数:input([prompt])。该函数用于从标准输入读取一个行,并返回一个字符串(去掉结尾的换行符),用法举例:
s= input("请输入字符,并按回车键结束:")
print("您输入了字符:%s"% s.strip())    #strip是一个函数,意思是去掉字符串变量s中的首尾空格
运行该代码,屏幕会输出如下信息:
  请输入字符,并按回车键 结束:
这时候系统开始等待我们输入字符,比如输入“hello”并按回车键,屏幕显示:
您输入了字符:hello
input函数默认的输入都是字符串类型。在实际情况中,可能会需要用户输入不同类型的数据,该函数并不能直接满足要求。这种情况下只能手动将输入的字符转成需要的类型数据,然后再使用。
注意:
要养成使用strip配合输入使用的习惯。这里的strip函数常用来从外界输入内容到系统内的场景下。对于从键盘输入、从文件读取等操作,一般都要对字符串进行一次strip操作,以便获得非空格开始的有效字符。
另外,还有两个与strip类似的函数也需要了解:lstrip 去掉字符串的左边空格,rstrip 去掉字符串的右边空格。

4.3.4  实例:以字符串为例演示序列类型的运算及操作

前文介绍过,字符串属于序列类型(sequence)的一种。序列类型即有序排列,里面的每个元素通过某个符号分开,按照顺序排成一列。本节就以字符串为例,介绍序列类型的相关特征即用法。

1.序列类型的基本操作

序列类型中有个重要的概念就是索引。索引是指在序列中,排名第几的具体数值。例如:“hello”中h的索引是0,e的索引是1,o的索引是4。
在了解完索引的概念之后,就可以学习一下关于序列类型的几种基本操作了,具体如下:
l 连接:使用“+”运算符,意义是将两个序列连接起来。在这里就是将两个字符串的连接,例如:“5”+“hello”就会得到一个“5hello”的字符串;
l 重复:使用“*”运算符,意义是重复该序列内容。如:"a" *3 就会得到一个"aaa"的字符串;
l 检索:使用中括号加下标的表示方法([下标]),下标指的是序列中的索引位置。
n 当下标为正数时是从左往右的索引顺序,从0开始计算。如:s="hello",s[0]为“h”;
n 当下标为负数时是从右向左的顺序,从1开始计算,如:s="hello",s[-1]为“o”。
l 反检索:使用index函数来完成与检索相反的功能。即,通过字符返回该字符在字符串中的索引,该索引是按照从左向右排列的。如s.index('e'),会得到一个1的数字,表明e在s中的索引为1。
l 切片 :使用“[起始:结束:步长]”的表示形式。意思是在原序列数据上面,在开始位置切一下,在结束位置切一下,剩的中间片段就是切片。按照步长指定的间隔来取字符串(一般默认为1)。切片数据是由开始位置的索引与结束位置的前一个索引所组成的(顾头不顾尾),如: s="hello",s[1:4]为“ell”;
注意:
在切片例子中要想取出“ello”字符串,一般会将结束部分的索引设为默认值,即s[1:]。不建议使用s[1:5],因为“hello”字符串中最大索引为4,在某种循环的处理方式中这么写还要考虑数组越界的问题,很容易引起错误。

2.代码举例

对上述的基本操作一一举例,使用代码演示具体用法及效果。
实例描述
通过定义一个hello字符串,来代表一个序列类型。对其进行连接、重复、检索、切片操作的处理,并通过print函数将处理的结果输出。
在这里面使用了一个变量s,并对s赋值,令其等于字符串“hello”。在后续的代码中,s就代表指定的字符串了。当然将代码中的变量s都换成“hello”,是一样不影响任何结果的。
代码4-2:序列类型的基本操作
s='hello'
s2=' daimayisheng'
#连接
print(s+s2)                 #输出:hello daimayisheng
#重复
print(s*3)                  #输出:hellohellohello
#检索
print(s[0],s[1],s[2],s[4])  #当索引为正数,方向从左到右,索引从0开始。输出:h e l o
print(s[-1],s[-2],s[-4])    #当索引为负数,方向从右到左,索引从1开始。输出:o l e
print(s[-5] )               #因为是从1开始,所有最后一个是5。输出:h
#print(s[5])                #因为是从0开始,所有最后一个是4。索引5已经超过了字符串的范围,于是报错。
                            #输出:IndexError: string index out of range
 
#反检索
print(s.index('e'))          #返回e在s中的索引,该索引是从左向右的顺序。输出:1
print(s.index('l'))          #当有两个l时,返回第一个。输出:2
print(s.index('w'))          #因为s中没有w字符。所以报错:ValueError: substring not found
 
#切片
print(s[1:3])               #从第一个还是到第三个,步长不指定默认为1。输出:   el  
print(s[:3])                  #开始位置不指定,默认从第一个字符开始截取,取到第3个。输出:hel
print(s[0:])                   #结束位置不指定,默认到最后,即从第一个字符开始截取,一直截取到最后。
#输出: hello
print(s[:])                    #开始与结束都不指定,即从第一个字符开始截取,一直截取到最后。输出: hello
print(s[::2])                  #步长为2,即每取一个之后,光标往后移动2个。输出: hlo
print(s[::-2])                 #步长为-2,即反方向读取,每取一个之后,光标往前移动2个。输出:olh
print(s[::-1])                 #实现字符串的逆序。输出olleh
print(s[-2::-1])              #实现字符串的逆序,然后再切片。输出:lleh
print(s[:0:-1])               #实现字符串的逆序,然后再切片。输出:olle
 
#字符串不能被修改
s[3]='3'                       #报错,输出:TypeError: 'str' object does not support item assignment
该实例代码的注释中包括了运行后的结果。读者可以根据代码的含义,配合注释来理解序列类型基本操作的规则。
注意:
上面代码的最后一行,字符串不能被改变。向一个索引位置赋值会导致错误。

4.3.5  关于切片的特殊说明

切片的操作本质上是从新定一个字符串常量,只不过该常量是依赖于原有字符串通过一定规则产生的。所以它也符合前面4.2.5节所说的缓存重用机制,如下列代码:
s='hello'
print(id(s),id(s[::2]),id(s[:]))      #第一个id为s的内存指针,第二个和第三个为切片的指针
该代码运行后,输出的结果为 :
70068184  147319248   70068184
可以看出,同样都是切片,但是第二个id是重新生成的,而第三个id与原字符串s是一样的。s[:]的指针与原字符串“hello”的指针一样。这是因为系统默认的使用了缓存的重用机制,没有再为其分配新的内存。
 

4.3.6  字符串的相关函数

Python中内置了很多字符串操作的相关函数,表4-8中列举了一些较为常见的函数。
表4-8  常见字符串操作函数
[align=center]
函数
说明
len()
求序列长度
in :
判断元素是否存在于序列中。例如:"ab" in "abcd",返回True
max() :
返回字符编码的最大值。例如: max("abcd") ,返回“d”
min() :
返回字符编码的最小值。例如: min("abcd") ,返回“a”
cmp(str1,str2) :
比较 2 个序列值是否相同。若相等,则返回0
ord(str)
返回单个字符的字符编码。例如:ord('我'),则返回25105(默认在UTF-8下)
chr(number)
根据某个字符编码返回对应的字符。例如:chr(25105),则返回'我'(默认在UTF-8下)
str.split()
这个函数的作用是将字符串根据某个分割符进行分割。例如:
 a = "I LOVE Python"
print( a.split(" "))#用空格作为分割
输出:
['I', 'LOVE', 'Python']
输出的返回值是一个列表(list)类型,关于列表的内容,后续会介绍
chr.join(list)
split的逆操作,即将list中的每个元素,用chr字符连接起来。例如:
b=['I', 'LOVE', 'Python']
   print(" ".join(b)) #将b按照空格连起来
输出:
I LOVE Python
str.title()
将每个单词首字母转为大写。例如:
a = "I love Python"
print( a.title())
输出:
I Love Python
[/align]字符串的方法还有很多。可以通过dir 命令来查看,在IPPython console中输入:
dir(str)
则会显示如下内容:
['__add__', '__class__', '__contains__', '__delattr__','__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__','__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__','__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__','__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__','__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split','_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode','endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha','isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust','lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust','rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip','swapcase', 'title', 'translate', 'upper', 'zfill']
这些内容就是字符串操作相关的全部函数,这里不在一一介绍,要了解某个具体的含义和使用方法,可以使用 help 命令查看。
例:想了解str的isalpha函数,可以在IPPython console中输入如下命令:
        help(str.isalpha)
输出结果如下:
Helpon method_descriptor:
 
isalpha(...)
    S.isalpha() -> bool
   
    Return True if all characters in S arealphabetic
    and there is at least one character in S,False otherwise.
 

4.4  list(列表)类型

list(列表)是Python 中使用最频繁的数据类型。它是一个有序集合,与字符串一样,都属于序列类型。
list的描述方法是将其内部元素使用中括号括起来,元素之间使用逗号来分隔。
需要注意的地方是,列表中每个元素的类型可以互不相同,并且可以嵌套使用。
例如,下面的代码是合法的:
tt='hello'                                 #定义一个变量tt
li= [1,3,4,tt,3.4,"yes",[1,2]]                 #列表中放置了整型、变量、浮点型、字符串、嵌套列表
print(li)                                 #将其打印出来,屏幕输出[1,3, 4, 'hello', 3.4, 'yes', [1, 2]]
上面的代码中定义一个列表变量li,并将其内容输出到屏幕上。这段代码示范了在列表里可以放置多种类型。其中所放置的变量也是没有任何类型约束的,可以是任意类型。
注意:
list的定义时是允许定义空列表的,可以使用li=[]代码来实现一个空列表。

4.4.1  list的运算及操作

list也是序列类型,它与字符串一样都具有4.3.4节实例中所介绍的连接、重复、检索、反检索、切片这几个基本功能。与字符串不同的是,列表中的元素是可以改变的。

4.4.2  list的内置方法

list的内置方法见表4-9。
表4-9 list的内置方法
[align=center]
列表的内置函数
描述
list.append(x)
在尾部增加一个元素,等价于 a[len(a):] = [x]
list.extend(L)
 
将给定的列表B中的元素接到当前列表a后面,等价于 a[len(a):] = B
list.insert(i, x)
 
在给定的索引位置 i 前插入项,例如:a.insert(len(a), x) 等价于 a.append(x);a.insert(0, x)将x插入列表头部。
list.index(x)
 
返回列表中第一个值为 x 的项的索引。如果没有匹配的项, 则产生一个错误
list.remove(x)
 
删除列表中第一个值为 x 的元素。当list中没有x时会报错
list.pop(i)
是将指定元素弹出的意思。弹出是指返回列表中指定索引i 的元素,并在列表中删除它。
也可以不指定索引,例如:a.pop(),表示弹出最后一个元素
list.clear()
删除列表中的所有项,相当于 del a[:]
del list[i或切片]
删除列表中指定索引i 的元素或指定的切片,等价于a.remove(a[i]),效率会比a.remove(a[i])慢。
还可以使用a.__delitem__(i)可以获得一样的效率。如del lst[:]为清空所有元素,等同于list.clear()。同时del还可以删除变量。
list.count(x)
返回列表中 x 出现的次数
list.sort()
列表排序操作
list.reverse()
逆序操作,等价于a[::-1]
[/align] 

4.4.3  实例:演示list使用中的技巧及注意事项

表4-9里介绍了很多关于list的操作函数。细心的读者会发现有很多操作函数非常相似。例如增加元素功能的函数有append与extend;删除元素功能的函数有clear、del命令、remove、pop。下面就通过实例加讲解的方式来明确各个函数之间的区别,及使用时的注意事项。
 
实例描述
定义一个多类型的list与一般的list,同时进行如下实验:
(1)使用多种方法将他们连接起来,并通过print函数将处理的结果输出,来比较内部变化及外部结果。
(2)使用del对list进行切片删除、索引删除、全部删除,观察操作后的结果。

1.比较添加元素的方法及区别

首先定义好list1、list3。然后分别使用加号(+)、extend函数、append函数对两个list进行操作,并将结果赋给变量l2。代码如下:
代码4-3:list操作示例
01   tt='hello'
02   list1 = [1,4,tt,3.4,"yes",[1,2]]    #定义一个涵盖各种类型的list
03   print(list1,id(list1))              #将其内容及地址打印出来[1, 4, 'hello', 3.4, 'yes', [1, 2]]
04       # 151672328
05    
06   #比较list中添加元素的几种方法的区别
07   list3 = [6,7]                     #定义list3作为后面拼接实验使用
08   l2 = list1+list3                  #使用+将list1与list3连接一起,得到l2
09   print(l2,id(l2))                  #输出l2的内容及地址[1, 4, 'hello', 3.4, 'yes', [1, 2], 6, 7]
10   # 151650568
11    
12   l2=list1.extend(list3)    #使用extend将list1与list3连接一起,给到l2
13   print(l2,id(l2))           #输出l2的内容及地址None 1722913824
14   print(list1,id(list1))    #将list1内容及地址打印出来[1, 4, 'hello', 3.4, 'yes', [1,2], 6, 7] 151672328
15    
16   l2=list1.append(list3)           #使用append将list3加入到list1中,给到l2
17   print(l2,id(l2))                 #输出l2的内容及地址None 1722913824
18   print(list1,id(list1))          #将list1内容及地址打印出来[1, 4, 'hello', 3.4, 'yes', [1,2],
19   # 6, 7, [6, 7]] 151672328
 
上面的代码运行后会有如下输出:
[1,4, 'hello', 3.4, 'yes', [1, 2]] 151650952
[1,4, 'hello', 3.4, 'yes', [1, 2], 6, 7] 151621832
None1722913824
[1,4, 'hello', 3.4, 'yes', [1, 2], 6, 7] 151650952
None1722913824
[1,4, 'hello', 3.4, 'yes', [1, 2], 6, 7, [6, 7]] 151650952
从结果可以看出以下结论:
l 使用加号连接的列表,是将list3中的元素放在list1的后面得到的l2。并且l2的内存地址值与list1并不是一样,这表明l2是一个从新生成的列表;
l 使用extend处理后得到的l2是none。表明extend没有返回值,并不能使用链式表达式。即extend千万不能放在等式的右侧。这是编程时常犯的错误,一定要引起注意;
l extend处理之后, list1的内容与使用加号生成的l2是一样的。但list1的地址在操作前后并没有变化。这表明extend的处理仅仅是改变了list1,而没有从新创建一个list。这个角度来看extend的效率要高于加号;
l 从append的结果可以看出,append的作用是将list3整体当成一个元素追加到list1后面。与extend和加号的功能完全不同。这一点也是需要注意的地方;

2.删除操作演示

接下来演示关于del的基本用法。代码如下:
代码4-3:list操作示例(续)
20   #删除操作
21   print(list1)         #输出list1的内容及地址[1, 4, 'hello', 3.4, 'yes', [1, 2], 6, 7, [6, 7]]
22   del list1[2:5]       #删除list1的切片
23   print(list1)         #再次输出list1的内容及地址 [1, 4, [1, 2], 6, 7, [6, 7]]
24   del list1[2]         #删除list1的索引
25   print(list1)         #再次输出list1的内容及地址 [1, 4, 6, 7, [6, 7]]
运行后得到如下输出:
[1,4, 'hello', 3.4, 'yes', [1, 2], 6, 7, [6, 7]]
[1,4, [1, 2], 6, 7, [6, 7]]
[1,4, 6, 7, [6, 7]]
这3行输出分别是list1的原始内容、删除一部分切片内容、删除指定索引内容。可以看到del函数按照指定的位置删掉了指定的内容。

3.删除变量演示

使用del还需要弄清楚,删除的到底是变量还是数据。下面通过代码来演示并证明一下删除变量的方法。
代码4-3:list操作示例(续)
26   l2=list1
27   print(id(l2),id(list1))     #打印地址
28   del list1                     #删除list1变量
29   print(l2)                      #l2还能访问,表明地址指向的数据并没有删
30   print(list1)                  #再次输出list1的内容及地址 NameError: name 'list1' is notdefined
运行后得到如下输出:
149263880149263880
[1,4, 6, 7, [6, 7]]
NameError:name 'list1' is not defined
第一行输出的内容是l2与list1的地址。该输出验证了前面讲述赋值的操作,因为只是传递内存地址,所以它们是相等的。
接下来将list1删掉,并且打印l2,得到了第二行的输出。从输出可以证明l2所指向的内存数据还是在的,这表明del删除list1时仅仅是销毁了变量list1并没有删除指向的数据。

4.删除数据

其实除了删除变量这个例子,其他的删除都是删除数据。接下来在举一个极端的例子将数据情况,如下代码:
代码4-3:list操作示例(续)
31   l3=l2
32   del l2[:]                        #删除l2全部内容
33   print(l2)                        #输出l2的内容及地址 []
34   print(l3)                        #输出l3的内容及地址也为 [],表明数据已经被删            
运行后得到如下输出:
 []
[]
从输出内容可以看到,先让l3与l2指向同样的内存。当l2清空后,l3的内容也清空了。这表明内存中的数据真正改变了。

5.总结

在实际过程中,这些内存只是被标为无效,并不是真正的被系统回收进行二次使用。如果想让系统回收这些可用的内存需要引入gc库,并配合如下代码形式:
importgc               #引入gc库
dellist1                  #删除list1
gc.collect()                          #回收内存
在4.2.5节中的“2. 缓存的重用机制”里介绍过:系统为了提升性能,会将一部分变量驻留在内存中。这个机制对于多线程并发时,程序如果产生大量占用内存的变量无法得到释放,或者某些不再需要使用的全局变量占用着大量的内存,会出现导致程序在后续运行中出现内存不足处理缓慢的情况。这时候记得使用del函数来回收内存,使系统的性能提升起来。它可以为团队省去大量扩充内存的成本。

4.4.4  列表嵌套

Python中并没有二维数组的概念,但可以通过列表嵌套达到同样的目的。实例如下:
mat= [                 #定义一个list变量mat,mat中的每个元素分别是一个list。
        [1, 2, 3],        #mat中的每个元素中又嵌套着一个list,嵌套的list中包含有3个整型。
        [4, 5, 6], 
        [7, 8, 9] 
      ] 
print(mat)              #将mat输出。
上面的代码运行之后,生成如下结果:
[[1,4, 7], [2, 5, 8], [3, 6, 9]]
结果显示出这是一个3×3的二维数组。这个例子仅仅只是演示了一下list能做二维数组而已。在实际编程过程中通常会使用numpy库里面的有关操作来处理二维或多维数组问题,不推荐使用list来处理二维数组。numpy库是一个支持Python使用的第三方库,提供了更多关于数值变换、矩阵处理相关的函数封装,是Python语言应用在人工智能领域使用最广的第三方库之一。
 

4.4.5  实例:使用list类型实现队列和栈

队列和栈是两种数据结构,其内部都是按照固定顺序来存放变量的。二者的区别在于对数据的存取顺序:
l 队列是先存入的数据,最先取出。即,先进先出;
l 栈是最后存入的数据,最先取出。即,后进先出。
list类型本身的数据存放就是有顺序的,而且内部元素又可以是各不相同的类型,非常适合用于队列和栈的实现。本例将用代码来演示使用list类型变量进行队列和栈的实现。
实例描述
定义一个list变量,让它充当队列或栈:
(1)使用list的insert的方法来存3个数据,使用pop方法来取三次数据。观察输出数据的顺序,并与队列的顺序进行比较。
(2)使用list的append的方法来存3个数据,使用pop方法来取三次数据。观察输出数据的顺序,并与栈的顺序进行比较。

1.使用list实现队列

定义一个list变量queue,使用insert方法存入数据。
insert方法的第一个参数设为0,代表每次都在最前面插入数据。
取数据时使用的是pop方法,即将queue的最后一个元素弹出。这样就形成了先进先出的顺序。
代码如下:
代码4-4:list实现对列和栈
01   queue = []                            #定义一个空list,当作队列
02   queue.insert(0,1)                    #向队列里放入一个整型元素 1
03   queue.insert(0,2)                    #向队列里放入一个整型元素 2
04   queue.insert(0,"hello")              #向队列里放入一个字符型元素 hello
05   print("取第一个元素",queue.pop())     #从队列里取一个元素,根据先进先出原则,输出 1
06   print("取第二个元素",queue.pop())     #从队列里取一个元素,根据先进先出原则,输出 2
07   print("取第三个元素",queue.pop())     #从队列里取一个元素,根据先进先出原则,输出 hello
这段代码的运行结果在代码中每条print语句后面的注释中。建议读者看明白书中内容后,可以在本机独立实现并运行一次,加深印象。

2.使用list实现栈

定义一个list变量stack,使用append方法存入数据。
append代表每次都在最后面添加数据。取数据时使用的是pop方法,即,将stack的最后一个元素弹出。这样就形成了后进先出的顺序。代码如下:
代码4-4:list实现对列和栈(续)
08   stack = []                                #定义一个空list,当作栈
09   stack.append(1)                      #向栈里放入一个整型元素 1
10   stack.append(2)                      #向栈里放入一个整型元素 2
11   stack.append("hello")                #向栈里放入一个字符型元素 hello
12   print("取第一个元素",stack.pop())     #从栈里取一个元素,根据后进先出原则,输出 hello
13   print("取第二个元素",stack.pop())     #从栈里取一个元素,根据后进先出原则,输出 2
14   print("取第三个元素",stack.pop())     #从栈里取一个元素,根据后进先出原则,输出 1
这段代码的运行结果在代码中每条print语句后面的注释中。读者在本机同步运行之后,可以试试将每次存入和取出操作后的stack内容打印出来,观察其变化。

3.扩展:使用deque 结构体实现更高效的队列

前面使用list实现队列的例子中,插入数据部分是通过insert函数实现的,这种方法效率并不高。因为每次从列表的开头插入一个数据,列表中所有元素都得向后移动一个位置。
还有一个高效的方法,使用标准库的collections.deque结构体, 它被设计成在两端添加和弹出都很快的特殊list。同时collections.deque结构体也可以实现栈的功能,代码演示如下:
代码4-4:list实现对列和栈(续)
15   from collections import deque      #导入deque结构体
16   queueandstack = deque()              #创建空结构体,既可以当队列又可以当栈
17    
18   queueandstack.append(1)              #向结构体里放入一个整型元素 1
19   queueandstack.append(2)              #向结构体里放入一个整型元素 2
20   queueandstack.append("hello")       #向结构体里放入一个字符型元素 hello
21   print(list(queueandstack))          #将结构体内容打印出来,输出:[1, 2, 'hello']
22     
23   print(queueandstack.popleft())      #实现队列功能,从队列里取一个元素,根据先进先出原则,输出 1
24   print(queueandstack.pop())           #实现栈功能,从栈里取一个元素,根据后进先出原则,输出 hello
25   print(list(queueandstack))      #将结构体内容打印出来,输出:[2]
代码中演示了:
l 使用deque函数,创建一个deque结构体的方法。
l 使用append函数,向结构体(deque)里存入数据的统一用法;
l 使用popleft函数,以队列的方式取出数据的用法;
l 使用pop函数,以z栈的方式取出数据的用法;
l 使用list类型转换,将deque转成list的方法。

4.总结

本实例的重点是对队列和栈两个结构体进行介绍,并巩固了对list内置方法的使用。但只是个示例,它实现了队列与栈的最基本功能,用于加深对list类型的理解。在实际应用场景,栈和队列的实现要比这个复杂一些,所有的操作都需要用函数封装起来,并加入一定的安全性检查。在多线程环境下,还需要要为具体的某些操作加入锁的同步机制。
 

4.5  tuple(元组) 类型

tuple(元组)可以理解为list(列表)的只读版。它与list(列表)非常类似,内部元素也是按照一定顺序存储的,每个元素的类型也可以各不相同。不同之处在于,元组的元素不能修改。

4.5.1  tuple的描述

元组的描述方法是将其内部元素使用小括号括起来,元素之间使用逗号来分隔。例如:
a= (1991, 2014, 'physics', 'math')                    #定义一个元组变量a。
print(a,type(a), len(a))                #将a的内容、类型、长度打印出来。
输出结果如下:
(1991,2014, 'physics', 'math') <class 'tuple'> 4
元组的序列类型相关操作与list也是基本一样,下标索引从0开始,支持索引、切片等读取方式,但不支持修改。例如:
tup= (1, 2, 3, 4, 5, 6)                        #定义一个元组变量tup 。
print(tup[0],tup[1:5])                        #通过切片访问内部数据,输出:1 (2, 3, 4, 5) 
tup[0]= 11                                                  #但不支持修改,该句是非法的,会报错误。
    上面代码中的最后一行,企图修改tup中第0个元素的值为11,但由于tup是元组,元素不可更改,所以会报错误。
注意:
虽然tuple的元素不可改变,但它可以包含可变的对象,比如list列表,并且被包含的可变对象内部的内容也是可以修改的。(见4.5.3节实例演示)
关于tuple的定义还有一些特殊的规则:
l 在定义包含0个元素的tuple时,直接使用小括号;
l 在定义包含1个元素的tuple时,需要在元素后面加个逗号。
例:
tup1= ()               #定义空元组。 
tup2= (20,)               # tup2中只有一个元素,需要在元素后添加逗号。
另外,因为字符串常量与元组都有不可修改的特性,所以字符串常量也可以理解成为一种特殊的元组。

4.5.2  运算及操作

元组与列表内部的元素都是可以任何形式,这表明元组与列表都可以互相嵌套,也可以嵌套自己。
元组与字符串操作一致,这里不再表述。

4.5.3  实例:tuple演示

本例中统一演示一下tuple的用法,并与list的使用做出比较。
实例描述
通过3个实验的代码来演示tuple的使用方法以及与list的区别:
(1)定义一个空的tuple与list,观察二者写法上的区别。
(2)定义只有一个元素的tuple,观察二者写法上的区别。
(3)将list嵌入到tuple中,并分别对tuple及list的内容修改,看看能发生什么?

1.定义一个空的tuple与list

对于tuple与list的空元素类型定义,tuple直接使用小括号,list直接使用中括号即可。代码如下:
代码4-5:元组与list 对比
01   #空元素
02   t=()                                 #空tuple
03   li=[]                                #空list
因为tuple不能修改,所以定义一个空的tuple几乎是没什么用处。但是定义一个空的list确实很常用,空的list一般会定义在循环体外面,这样即使循环体内不对该list添加元素,也不会因为找不到list变量而报错。

2.定义一个只含有一个元素的list与tuple

对于只有一个元素的list定义,直接使用中括号包含起来就可以。而对于只有一个元素的tuple定义,除了用小括号包含,必须要在元素后面加个逗号。
下面的代码中分别演示了只有一个元素的list与tuple定义时,在元素后面加逗号和不加逗号的情况。
代码4-5:元组与list对比(续)
04   #一个元素的注意
05   t=(3)                                #一个元素的注意
06   t1=(3,)                              #必须要加逗号
07   li=[3]                               #对于list加不加都可以
08   l1=[3,]                               #对于list加不加都可以
09   print(t,t1,li,l1)
代码运行后结果如下显示:
3(3,) [3] [3]
可以看到,第一个变量t输出的值是整型3,并不是tuple。这说明定义只有一个元素的tuple时,如果元素后面不加逗号,将得不到tuple类型。而对于list,在元素后面加不加逗号,则没有任何影响。

3.将list嵌入到tuple中

下面代码定义了一个变量tt,然后将变量与各种类型的常量放到tuple类型的t1中,来演示tuple内部可以包含各种类型。接着修改t1中的list,然后再修改该t1中第0个元素。代码如下:
代码4-5:元组与list对比(续)
10   #元素修改处理
11   tt='hello'                                 #定义一个变量tt
12   t1 = (1,3,4,tt,3.4,"yes",[1,2],(4,3))   #列表中放置了整型、变量、浮点型、字符串、嵌套列表
13   print(t1)                                  #将其打印出来
14   t1[6][0]="3445"                            #为元组中的list里面的元素赋值
15   print(t1)                                  #打印内容
16   t1[0]=3                                    #改变元组中的元素,返回错误
运行后,输出结果:
(1,3, 4, 'hello', 3.4, 'yes', [1, 2], (4, 3))
(1,3, 4, 'hello', 3.4, 'yes', ['3445', 2], (4, 3))
Traceback(most recent call last):
 
  File "<iPython-input-9-d7fba5548f53>",line 1, in <module>
    runfile('D:/Python2/4-5 元组与list.py', wdir='D:/Python2')
……
TypeError:'tuple' object does not support item assignment
结果中第一行显示的是原始tuple内容。第二行显示的是tuple中的list被修改后的内容。没有任何报错,表明tuple中的list内容是可以修改的。在执行代码16行时,报错了,显示的错误信息意思就是tuple对象不支持内部元素的赋值。

4.总结

通过该实例再次证明了tuple是list的只读版本。从功能的角度来说list可以完全的替代tuple的功能,但是在某种场合下tuple还会有它特殊的作用。例如,作为某个函数的返回值时,它可以保证返回的内容不被修改,从而增加了代码的健壮性。
回顾前面4.3.3节中的“5演示使用str.format的格式化方法”里面的“(3)扩展用法1-结合列表或元组类型”的例子,应该明白其中变量“mylist”和“mytuple”的意义了。
提示:
format函数的参数中,变量“mylist”和“mytuple”的前面加了个符号“*”。这个“*”的意思是解包参数列表。即,将tuple或list中的内容解包出来,作为参数传入到函数format中。
其实在这种应用场景下,直接使用tuple的代码就会比使用list的代码有更强的健壮性。
 

4.6  set(集合) 类型

Python还有个数据类型叫做set(集合)。它的基本功能是进行成员关系测试和消除重复元素,在数据清洗领域运用相对比较广泛。

4.6.1  set的描述

set是一个无序不重复元素的集。它的描述方法是使用大括号或者set函数来生成,里面的元素同样可以是任何类型。例如:
myset= {'hello', 'hello', 'Python', 'tensorflow',2,1,2}        #使用大括号方法定义一个set(集合)。
print(myset)                                                                                      #生成的set会自动将重复的元素去掉。 
                                                  #输出:{'hello',1, 2, 'Python', 'tensorflow'}
还可以使用set函数,将其他类型的变量转成set(集合)。例:
mylist= ['hello', 'hello', 'Python', 'tensorflow',2,1,2] #定义一个list。
mytuple= ('hello', 'hello', 'Python', 'tensorflow',2,1,2)        #定义一个tuple。
myset= set(mylist)                                                                 #使用set函数将list转成set(集合)。
print(myset)                                                                           #生成的set会自动将重复的元素去掉。
                                             #输出:{'hello',2, 'Python', 'tensorflow', 1}
myset= set(mytuple)                                                              #使用set函数将tuple转成set(集合)。
print(myset)                                                                           #生成的set会自动将重复的元素去掉。
                                                                                                     #输出:{'hello', 2, 'Python', 'tensorflow', 1}
正常情况下,要定义set类型变量,使用大括号与set函数都是可以的。但要想定义一个空的set变量,就必须要使用set函数的方法。当使用大括号的方法时,如果里面内容为空,系统会认为这是个字典类型(字典类型会在4.7节介绍),而不会去创建集合类型。

4.6.2  set的运算

set的运算与数学中的集合运算相似,支持差、并、交、等操作。另外,还有一个in的语法来测试某个元素是否在某个集合里,返回一个布尔型的结果。下面通过例子来具体演示:
helloset= set('hello')                    #通过set函数,将字符串 “hello”转成一个集合。
tensorflowset= set('tensorflow')              #通过set函数,将字符串“'tensorflow'”转成一个集合。
print('w'intensorflowset,tensorflowset)   #判断“w”是否在集合tensorflowset中,并打印tensorflowset内容。
                               #输出:True {'t','s', 'l', 'r', 'e', 'f', 'w', 'o', 'n'}
print('w'inhelloset,helloset)                        #判断“w”是否在集合helloset中,并打印helloset内容。
                                      #输出:False {'e','l', 'o', 'h'}
print(helloset-tensorflowset )            #计算集合helloset与tensorflowset的差集,
                                                                         #helloset中有而tensorflowset中没有。输出:{'h'}
print(helloset|tensorflowset )                             #计算集合helloset与tensorflowset的并集,
                                                                         #既包括helloset又包括tensorflowset。
                                                                         #输出:{'t', 's', 'l', 'r', 'e', 'f', 'w', 'o', 'n', 'h'}
print(helloset&tensorflowset )                  #计算集合helloset与tensorflowset的交集,
                                                                         #helloset中有而tensorflowset中也有。输出:{'e', 'l', 'o'}
print(helloset^tensorflowset )                    #计算集合helloset与tensorflowset的对称差集,
                               #该集合中的元素在helloset或tensorflowset中,但不会同时出现在
                                                                  #helloset和tensorflowset中。输出:{'t', 's', 'r', 'f', 'w', 'n','h'}
 

4.6.3  set的内置方法

set还有一些自身的内置方法,表4-10中列出了set中的常见内置函数的意义及用法。
表4-10  set的内置方法
[align=center]
列表的内置函数
描述
set.add(x)
往集合里添加一个元素x
set.update(list)
输入参数是一个列表,将列表里的元素全部添加到集合里
set.remove(x)
 
删除集合里的元素x。当x不在集合里时,会报错误(输出KEYERROR)
set.discard(x)
如果结合里有元素x时,删除集合里的元素x
set.clear()
情况集合中的所有元素
set.pop()
随机选择集合中的一个元素,并删除
len(set)
返回集合中元素的个数
in
 
判断某元素是否在集合里,返回BOOL类型。例如:
s=set(“ab”)
print(‘a’in s)
运行该代码会输出:True
not in
判断某元素是否不在集合里,返回BOOL类型
set.issubset(set2)  或set <= set2
判断set是否是set2的子集。返回BOOL类型
set.issuperset(set2) 或set >= set2
判断set2是否是set的子集。返回BOOL类型
set.union(set2) 或set | set2
计算set与set2的并集
set.intersection(set2) 或 set & set2
计算set与set2的交集
set.difference(set2) 或 set – set2
计算set与set2的差集
set.symmetric_difference(set2)或set ^ set2
计算set与set2的对称差集
[/align] 

4.7  dictionary(字典) 类型

字典(dictionary)是Python中另一个非常有用的内置数据类型。字典是一种映射类型(mapping type),它是一个无序的(键 : 值)对集合。关键字必须使用不可变类型,也就是说list和包含可变类型的tuple不能做关键字。因为在同一个字典中,关键字是要做为唯一检索使用的,所以必须互不相同。

4.7.1  字典的描述

字典的描述与set的描述非常的相似,也是用大括号(“{}”)来扩起来的。唯一不同的是,字典的元素必须是键值对(key:value)类型。另外也可以使用dict函数将其他变量(list或tuple)转成字典。例如:
mylist= [('hello',1),('good', 2), ['ok', 3]]  #定义一个list里面嵌套元组和list,每个嵌套的元素都是键值对。
d= dict(mylist)                               #使用dict函数将mylist转换成字典。
d2= {'hello': 1, 'good': 2, 'ok': 3}                #使用大括号来创建字典。
print(d,d2)                                                      #输出{'hello': 1, 'ok': 3, 'good': 2} {'hello': 1, 'ok': 3, 'good': 2}
定义空字典变量的方法很简单,直接使用大括号就可以,如:mydic = {} 。

4.7.2  字典的运算

字典在使用中,主要是通过key来查询,它的用法是在后面加个中括号,里面输入key的字符串。例如:
d2= {'hello': 1, 'good': 2, 'ok': 3}                #使用大括号来创建字典。
print(d2['hello'])                    #取出字典中key为hello的值,输出:1
如果要在字典中更新某个key对应的值,直接将其取出来用等号赋值即可。例如:
d2= {'hello': 1, 'good': 2, 'ok': 3}                #使用大括号来创建字典。
d2['hello']= 'e'                                                #将字典中key为hello的值赋值为“e”。
print(d2['hello'])                    #取出字典中key为hello的值,输出:e
上面字典中key为“hello”的值本来为整型1,却被改成了字符型的“e”。这表明在键值对中,值的类型也可以任意修改的。
如果要在已有的字典中增加一个(key:value)的键值对,可以直接在后面加个中括号,里面输入key的字符串,并用等号赋值。例如:
d2= {'hello': 1, 'good': 2, 'ok': 3}                #使用大括号来创建字典。
d2['new']= 100                                              #在字典中加入key为new,值为100的键值对。
print(d2)                               #输出:{'ok': 3, 'hello': 1, 'new': 100,'good': 2}
从输出看出,因为字典中的元素是无序的,所以新加的键值对并不一定是在最后的位置。
如果要删掉某个键值对,可以使用内置函数del。例如:
d2= {'hello': 1, 'good': 2, 'ok': 3}                #使用大括号来创建字典。
deld2['hello']                                                 #在字典中删除key为hello的键值对。
print(d2)                               #输出:{'ok': 3, 'good': 2}
字典中还有内置方法.keys,可以返回dict_keys类,里面包含所有的key;
同时还有内置方法.values,可以返回dict_values类,里面包含所有的值。
一般都会将其转成list来使用。例如:
d2= {'hello': 1, 'good': 2, 'ok': 3}                #使用大括号来创建字典。
print(type(d2.values()))                               #打印.values()返回值的类型,输出:<class'dict_values'>
print(d2.values())                                          #打印.values()返回值,输出:dict_values([3, 1, 2])
print(type(d2.keys()))                                   #打印.keys()返回值的类型,输出:<class 'dict_keys'>
print(d2.keys())                                             #打印.keys()返回值,输出:dict_keys(['ok', 'hello','good'])
list1= list(d2.keys())                                     #将.keys()返回值转成list类型。
print(list1)                                                       #打印转换后的list,输出:['ok', 'hello', 'good']
list2= sorted(d2.keys())                               #由于key的顺序不够定,常会用将其转成排序后的list。
print(list2)                                                       #打印排序后的list,输出:['good', 'hello', 'ok']
为了提高代码的健壮性,建议最好要使用sorted函数来将字典中的key转成list。
这里再来看看前面4.3.3节中的“5演示使用str.format的格式化方法”里面的“(4)扩展用法2-结合字典类型”的例子,其中变量“d”就是字典类型。与列表或元组不同的是,将字典类型的参数列表解包传入其他函数调用时,需要用两个星号“**”。所以在format函数里,变量“d”前面加了两个星号 “**”。
字典还有更多的内置方法见4.7.3节。

4.7.3  字典的内置方法

关于字典更多的内置方法,见表4-11。
表4-11  字典的内置方法
[align=center]
列表的内置函数
描述
dict.fromkeys(seq [,value])
创建一个新字典,序列seq中元素作为字典的键,value(可选)作为字典所有键对应的初始值
dict.get(key[, default=None])
返回指定键key的值。如果key不在字典中,则返回default值(默认为none)
dict.setdefault(key, default=None)
 
与get类似, 但如果键不存在于字典中,将会添加键并将键值设为default
del
删除字典中指定key的键值对
dict.clear()
情况字典中的所有元素
dict.items()
以列表返回可遍历的(键, 值) 元组数组
len(dict)
返回字典中元素的个数
in
判断某个key是否在字典里,返回BOOL类型
not in
判断某个key是否不在字典里,返回BOOL类型
dict.keys()
以列表返回一个字典所有的键
dict.values()
以列表返回字典中的所有值
dict.update(dict1)
把字典dict1的键/值对更新到dict里。无返回值
[/align] 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Python基础
相关文章推荐