Python入门
Python入门
本文基于董付国老师的《Python程序设计》
还可以参考:Python3 官方文档中文版
第1章 基础知识
1.1 Python安装与简单使用
Python是一门跨平台的开源、免费的解释型脚本语言,同时也支持伪编译以进行优化和提高运行速度,还支持使用py2exe工具将Python程序转换为exe可执行程序以使得可以在没有安装Python解释器和相关依赖包的平台上运行;Python同时支持命令式编程、函数式编程和面向对象的编程,语法简洁清晰,并且拥有大量的几乎支持所有领域应用开发的成熟扩展库;最后,Pyhton就像胶水一样,可以把多种不同语言编写的程序融合到一起实现无缝拼接,更好地发挥不同语言和工具的优势。
Python的安装与大多数软件的安装并没有什么明显的不同,打开Python的官方主页,然后选择适合自己的版本下载安装即可。安装好以后默认以IDLE为开发环境,使用交互编程模式。这意味着,你可以在一个 Python 提示符 >>>
后直接执行代码,输入相应命令并回车执行即可,执行正常的话,马上就可以看到执行结果。
例如:
>>> 16+51
67
一般来讲,我们可能更需要编写Python程序来实现特定的业务逻辑,同时也方便代码的不断完善和重复利用,毕竟直接使用交互编程模式不是很方便,此时可以在IDLE界面中执行File
-> New File
命令创建程序文件,输入程序并保存为文件(务必要保证扩展名为py)后,执行Run
->Run Module
命令运行,程序运行结果将直接显示在IDLE交互界面上。除此之外,也可以通过在资源管理器中双击扩展名为py的Python程序文件来运行。
在实际开发中,如果能够熟练使用集成开发环境IDLE提供的一些快捷键,将会大幅度提高编写速度和开发效率。在IDLE环境下,比较常用的快捷键如下表:
快捷键 | 功能说明 |
---|---|
Alt+P | 浏览历史命令(上一条) |
Alt+N | 浏览历史命令(下一条) |
Ctrl+F6 | 重启Shell,之前定义的对象全部无效 |
F1 | 打开Python帮助文档 |
Alt+/ | 自动补全前面曾经出现过的单词,在多个单词中循环选择 |
Ctrl+] | 缩进代码块 |
Ctrl+[ | 取消代码块缩进 |
Alt+3 | 注释代码块 |
Alt+4 | 取消注释代码块 |
1.2 使用pip管理扩展库
目前,pip已经成为管理Python扩展库(或模块,一般不作区分)的主流方式,大多数扩展库都支持这种方式进行安装、升级、卸载等操作,使用这种方式管理Python扩展库只需要保证计算机联网情况下输入几个命令即可完成,极大地方便了用户。
Python 3.4.0之后的安装包中已经集成了pip命令。pip常用命令的使用方法如下表:
pip命令示例 | 说明 |
---|---|
pip install SomePackage | 安装SomePackage模块 |
pip list | 列出当前已安装的所有模块 |
pip install --upgrade SomePackage | 升级SomePackage模块 |
pip uninstall SomePackage | 卸载SomePackage模块 |
1.3 Python语言基础知识
1.3.1 Python对象模型
在Python中的一切都是对象,这一点可能与其它面向对象程序设计语言略有区别。Python中有许多内置对象可供编程者直接使用,例如数字、字符串、列表、元组、字典、集合、del命令、以及cmp()、len()、id()、type()等大量内置函数;有些对象则需要导入模块(有些模块还需要单独进行安装)后才能使用,如math模块中的正弦函数sin(),random模块中的随机数产生函数random()等。
在Python中内置了大量的常用对象,这些内置对象或类型无须导入模块即可直接使用。Python常用内置对象如下表:
对象类型 | 示例 | 对象类型 | 示例 |
---|---|---|---|
数字 | 1234,3.14,3+4j | 文件 | f=open('data.dat','r') |
字符串 | 'swfu',"I‘m student",'''Python''' | 集合 | set('abc'), |
列表 | [1,2,3],['a','b',['c',2]] | 布尔型 | True,False |
字典 | 空类型 | None | |
元组 | (2,-5,6) | 编程单元类型 | 函数(使用def定义) 类(使用class定义) |
1.3.2 Python变量
在Python中,不需要事先声明变量名及其类型,直接赋值即可创建各种类型的对象变量。
例如语句:
>>> x=3
创建了整型变量x,并赋值为3.
需要说明的是,Python属于强类型编程语言,虽然不需要在使用之前显式地声明变量及其类型,但是Python解释器会根据赋值或运算来自动推断变量类型。每种类型支持的运算也不完全一样,因此在使用变量时需要程序员自己确定所进行的运算是否合适,以免出现异常或者是意料之外的结果。另外,Python还是一种动态类型语言,也就是说,变量的类型是可以变化的。
例如:
>>> x=3
>>> print(type(x))
<class 'int'>
>>> x='Hello,World.'#创建字符串类型变量x,之前的整型变量x自动失效
>>> print(type(x))
<class 'str'>
在大多数情况下,如果变量出现在赋值运算符或复合赋值运算符(例如+=、*=等)的左边,则表示创建变量或修改变量的值,否则表示引用该变量的值。
另外,Python采用的是基于值的内存管理方式,如果为不同变量赋值为相同值,则在内存中只有一份该值,多个变量指向同一块内存地址。
Python具有自动内存管理功能,对于没有任何变量指向的值。Python自动将其删除。Python会跟踪所有的值,并自动删除不再有变量指向的值。因此Python程序员一般不需要太多考虑内存管理的问题。尽管如此,显式释放不需要的值或显式关闭不再需要访问的资源,仍是一个好习惯,同时也是一个优秀的程序员的基本素养之一。
最后,在定义变量名的时候,需要注意以下问题:
(1)变量名必须以字母或下划线开头;
(2)变量名中不能有空格和标点符号(括号,引号,逗号,斜线,反斜线,冒号,句号和问号等);
(3)不能使用关键字作为变量名,可以导入keyword模块后使用print查看所有Python关键字。
>>> import keyword
>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
(4)不建议使用系统内置的模块名、类型名或函数名作为变量名,这样会改变其类型和含义,可以通过dir(__builtins__)
查看所有内置模块类型和函数。
(5)变量名对英文字母的大小写敏感,例如student和Student是不同的变量。
1.3.3 数字
数字属于Python不可变对象,即修改整型变量值的时候,并不是真的修改变量的值,而是先把值存到内存中,然后修改变量使其指向新的内存地址,浮点数、复数等数字类型以及其他类型的变量,具有同样的特点。
Python数值类型主要有整数、浮点数和复数。
整数类型主要有4种:
(1)十进制整数。
(2)十六进制整数。必须以0X开头。
(3)八进制整数。必须以0o开头。
(4)二进制整数。必须以0b开头。
浮点数也称为小数,例如.3、11.0、0.37、-12.1、1.2e2、314.15e-2。
Python中的复数与数学上的复数形式一致,都是由实部和虚部构成,并且使用j或J来表示虚部。
>>> a = 3 + 4j
>>> b = 5 + 6j
>>> c = a + b
>>> c
(8 + 10j)
>>> c.real#查看复数实部
8.0
>>> c.image#查看复数虚部
10.0
>>> a.conjugate#返回共轭复数
(3 - 4j)
1.3.4 字符串
在Python中字符串属于不可变序列,一般使用单引号,双引号或三引号进行界定,并且单引号,双引号,三单引号,三双引号还可以互相嵌套,原来表示复杂字符串。例如:
'abc'、"python"、''' Tom said, ''Let's go'' '''都是合法字符。空字符表示为''或""或'''''',即一对不包含任何内容的任意字符串界定符。
Python支持转义字符,常用的转义字符如下表:
转义字符 | 含义 | 转义字符 | 含义 |
---|---|---|---|
\n | 换行符 | \" | 双引号 |
\t | 制表符 | \\ | 一个\ |
\r | 回车 | \ddd | 3位八进制数对应的字符 |
\' | 单引号 | \xhh | 2位十六进制数对应的字符 |
需要特别说明的是,字符串界定符前面加字母r或R表示原始字符串,其中的特殊字符不需要进行转移,但字符串最后一个字符不能是\符号。
1.3.5 运算符与表达式
Python常用运算符如下表:
运算符示例 | 功能说明 |
---|---|
x+y | 算术加法,列表,元组,字符串合并 |
x-y | 算术减法,集合差集 |
x*y | 乘法,序列重复 |
x/y | 除法 |
x//y | 求整商 |
-x | 负数 |
x%y | 余数/格式化(对实数可以进行余数运算) |
x**y | 幂运算 |
x<y;x<=y;x>y;x>=y | 大小比较,集合的包含关系比较 |
x==y;x!=y | 相等比较(值),不等比较 |
x or y | 逻辑或(只有x为假才会计算y) |
x and y | 逻辑与(只有x为真才会计算y) |
not x | 逻辑非 |
x in y;x not in y | 成员测试运算符 |
x is y;x is not y | 对象实体同一性测试(地址) |
|、^、&、<<、>>、~ | 位运算符 |
&、| | 集合交集、并集 |
1.3.6 常用内置函数
内置函数是指不需要导入任何模块即可直接使用的函数。
执行下面的命令可以列出所有内置函数:
>>> dir(__builtins__)
建议编写程序时应优先使用内置函数,因为内置函数不仅成熟、稳定而且速度相对较快。
1.3.7 对象的删除
在Python中可以使用del命令来删除对象并解除与值之间的指向关系。删除对象时如果其指向的值还有别的变量指向,则不删除该值,如果删除对象后该值不再有其他变量指向,则删除该值。del可以用来删除列表或其他可变序列中的指定元素,也可以删除整个列表或其他类型序列对象,无法删除元组或字符串中的指定元素,而只可以删除整个元组和字符串,因为这两者均属于不可变序列。
1.3.8 基本输入输出
键盘输入与输出
>>> x = input('提示:')
>>> print("你输入的内容是:")
1.3.9 模块
可以使用sys.modules.items()显示所有预加载模块的相关信息。对于很多模块而言,需要首先导入,然后才能使用其中的对象。Python中主要有以下两种导入模块的方式。
- import 模块名 [as 别名]
使用这种方式导入模块以后,需要在使用的对象之前加上前缀,即以“模块名.对象名”的方式进行访问。也可以为导入的模块名设置一个别名,然后使用“别名.对象名”的方法来访问其中的对象。
>>> import math
>>> math.sin(0.5)
0.479425538604203
>>> import numpy as np
>>> a = np.array((1,2,3,4))
>>> print(a)
[1,2,3,4]
- from 模块名 import 对象名 [as 别名]
使用这种方式仅导入明确指定的对象,并且可以为导入的对象起一个别名,这种导入方式可以减少查询次数,提高访问速度,同时减少了程序员要输入的代码量,而不需要使用模块名作为前缀。
>>> from math import sin
>>> sin(3)
0.1411200080598672
1.4 Python代码编写规范
(1)缩进
Python程序依靠代码块的缩进来体现代码之间的逻辑关系,对于类定义,函数定义,选择结构,循环结构,以及异常处理结构来说,行尾的冒号以及下一行的缩进表示一个代码块的开始,而缩进结束就表示一个代码块结束了。在编写程序时,同一个级别的代码块的缩进量必须相同,在IDLE开发环境中,一般以4个空格为基本缩进单位或者在设置中修改基本缩进量。
还可以使用Ctrl+]进行缩进,使用快捷键Ctrl+[进行反缩进。
(2)注释
①以#开始,表示本行#之后的内容为注释。
②包含在一对三引号'''...'''或"""..."""之间且不属于任何语句的内容将被解释器认为是注释。
可以使用快捷键Alt+3和Alt+4进行代码块的批量注释和解除注释。
(3)每个import语句只导入一个模块,尽量避免一次导入多个模块。
(4)如果一行语句太长,可以在行为使用续行符来表示下面紧接的一行仍属于当前语句,但是一般建议使用括号来包含多行内容。
(5)使用必要的空格与空行增强代码的可读性,运算符两侧,函数参数之间,逗号两侧建议使用空格进行分隔,而不同功能的代码块之间,不同的函数定义以及不同的类定义之间,则建议增加一个空行,以增加可读性。
(6)适当使用异常处理结构提高程序容错性,但不能过多依赖异常处理结构。
(7)软件应具有较强的可测试性,测试与开发齐头并进
1.5 Python文件名
在Python中,不同扩展名的文件有不同的含义和用途,常见的扩展名主要有以下5种,
(1)py:Python源文件,由Python解释器负责解释执行。
(2)pyw:Python源文件,常用于图形界面程序文件。
(3)pyc: Python字节码文件,无法使用文本编辑器正常查看文件的内容。对于Python模块,第一次被导入时将被编译成字节码的形式,并在以后再次导入时优先使用pyc文件,以提高模块的加载和运行速度。对于非模块文件,直接执行时并不生成pyc文件,但可以使用py_compile模块的compile()函数进行编译以提高加载和运行速度。
(4)pyo:优化的Python字节码文件,同样无法使用文本编辑器正常查看其内容。
(5)pyd:一般是由其他语言编写并编译的二进制文件,常用于实现某些软件工具的Python编程接口和Python动态链接库。
1.6 Python之禅
努力让自己编写的代码更加优雅、更加Pythonic。
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
第2章 Python数据结构
2.1 列表
序列是程序设计中经常用到的数据存储方式,几乎每一种程序设计语言都提供了类似的数据结构,如C++中的一维、多维数组等。 Python中常用的序列结构有列表、元组、字典、字符串、集合以及range对象等。除了字典和集合属于无序序列之外,列表、元组、字符串等序列,均支持双向索引,第1个元素下标为0,第2个元素下标为1,依次类推,最后一个元素下标为-1,倒数第2个元素下标为-2,以此类推。以负数作为序列索引是Python语言的一大特色,熟练掌握和运用可以大幅提高程序开发效率。
列表是Python的内置可变序列,是包含若干元素的有序连续内存空间,列表中的每一个数据称为元素,列表所有元素放在一对中括号中,并使用逗号分隔开。当列表增加或删除元素时,列表对象自动进行内存的扩展或收缩,从而保证元素之间没有缝隙,但这涉及列表元素的移动,效率较低,应尽量从列表尾部进行元素的增加与删除操作以提高处理速度。
在Python中,同一个列表中元素的数据类型可以各不相同,可以同时分别为整数、实数、字符串等基本类型,甚至是列表、元组、字典、集合以及其他自定义类型的对象。
例如:
[10,20,30,40]
['crunchy frog','ram bladder','lark vomit']
['spam',2.0,5,[10,20]]
[['file1',200,7],['file2',260,9]]
都是合法的列表对象。
对于Python序列而言,有很多方法是通用的,而不同类型的序列也有一些特有的方法,列表对象常用的方法如下表所示:
方法 | 说明 |
---|---|
list.append(x) | 将元素X添加至列表尾部 |
list.extend(L) | 将列表L中的所有元素添加至列表尾部 |
list.insert(index,x) | 在列表指定位置index处添加元素x |
list.remove(x) | 在列表中删除首次出现的指定元素 |
list.pop([index]) | 删除并返回列表对象指定位置的元素,默认为最后一个元素 |
list.clear() | 删除列表中所有元素,但保留列表对象 |
list.index(x) | 返回值为x的首个元素的下标,若元素不存在则抛出异常 |
list.count(x) | 返回指定元素x在列表中的出现次数 |
list.reverse() | 对列表元素进行原地翻转 |
list.sort() | 对列表元素进行原地排序 |
list.copy() | 返回列表对象的浅复制 |
2.1.1 列表创建与删除
如同其他类型的Python对象变量一样,使用=直接将一个列表复制给变量即可创建列表对象,例如:
>>> a_list = ['a','b','example']
或者也可以使用list()函数将元组、range对象、字符串或其他类型的可迭代对象类型的数据转换为列表,例如:
>>> list1 = list((3,5,7,9,11))
>>> list1
[3,5,7,9,11]
>>> list(range(1,10,2))
[1,3,5,7,9]
range()函数语法为range([start,] stop[,step])
。该函数接受三个参数:第一个参数表示起始值(默认为0);第二个参数表示终止值(结果中不包括这个值);第三个参数表示步长(默认为1),函数返回一个range对象。
当不再使用列表时,使用del命令删除整个列表,如果列表对象所指向的值不再有其他对象指向,Python将同时删除该值。
2.1.2 列表元素的增加与删除
(1)可以使用+运算符来实现将元素添加到列表中的功能。虽然这种用法在形式上比较简单并且容易理解,但严格意义上来讲,这并不是真的为列表增加元素,而是创建一个新列表,并将原列表中的元素和新元素依次复制到新列表的内存空间。由于涉及大量元素的复制,该操作速度较慢,在涉及大量元素添加时不建议使用该方法。
>>> alist = [3,4,5]
>>> alist = alist + [7]
>>> alist
[3,4,5,7]
(2)使用列表对象的append()方法,原地修改列表,是真正意义上的在列表尾部添加元素,速度较快,也是推荐使用的方法。
>>> alist.append(9)
>>> alist
[3,4,5,7,9]
(3)使用列表对象的extend()方法可以将另一个迭代对象的所有元素添加至该列表对象尾部。通过extend()方法来增加列表元素不改变其内存首地址,属于原地操作。
>>> alist.extend([11,13])
>>> alist
[3,4,5,7,9,11,13]
(4)使用列表对象的insert()方法将元素添加至列表的指定位置。
>>> alist.insert(3,6)
>>> alist
[3,4,5,6,7,9,11,13]
尽量从列表尾部进行元素的增加与删除操作。insert()方法会涉及插入位置之后所有元素的移动,这会影响处理速度,类似的还有后面介绍的remove()方法。除非有必要,尽量避免在列表中间位置插入和删除元素的操作,而是优先考虑使用append()方法。
(5)使用乘法来扩展列表对象,将列表与整数相乘,生成一个新列表,新列表是原有列表中元素的重复。
>>> alist = [3,5,7]
>>> alist = alist * 3
>>> alist
[3,5,7,3,5,7,3,5,7]
该操作实际上是创建了一个新列表,而不是扩展原列表。该操作同样适用于字符串和元组。
(6)使用del命令删除列表中指定位置上的元素。del命令也可以直接删除整个列表。
>>> del alsit[1]
>>> alist
[3,7,3,5,7,3,5,7]
(7)使用列表的pop()方法删除并返回指定(默认为最后一个)位置上的元素,如果给定的索引超过了列表的范围则抛出异常。
(8)使用列表的remove()方法删除首次出现的指定元素,如果列表中不存在要删除的元素,则抛出异常。
有时可能需要删除列表中指定元素的所有重复,大家会自然地想到用“循环+remove()”的方法,但具体操作时很可能会出现意料之外的错误,代码运行没有出现错误,但结果是错的。例如,下面的代码试图删除列表中所有的1,使用下面的代码从前向后遍历列表元素,遇到1则删除,然而当循环结束后却发现只删除了一半,还有一半并没有删除。
>>> alist = [1,1,1,1,1,1,1,1,1,1,1,1]
>>> len(alist)
12
>>> for i in alist
if i==1:
alist,remove(i)
>>> alist
[1,1,1,1,1,1]
>>> len(alist)
6
出现这个问题的原因是列表的自动内存管理功能,在删除列表元素时,Python会自动对列表内存进行收缩以保证所有元素之间没有空隙,这样的话,每当删除一个元素后,该元素后面所有元素的索引就都改变了。为了避免这样的问题,可以使用下面的方法。
>>> alist = [1,1,1,1,1,1,1,1,1,1,1,1]
>>> for i in alist[::-1]:
if i == 1:
alist.remove(i)
>>> alsit
[]
>>> len(alist)
0
2.1.3 列表元素访问与计数
可以直接使用下标直接访问列表中的元素,如果指定下标不存在,则抛出异常提示下标越界。
使用列表对象的index()方法可以获取指定元素首次出现的下标,语法为index(value,[start,[stop]]),其中start和stop用来指定搜素范围,start默认为0,stop默认为列表长度。若列表对象中不存在指定元素,则抛出异常提示列表中不存在该值。
如果需要知道指定元素在列表中出现的次数,可以使用列表对象的count()方法进行统计。
2.1.4 成员资格判断
如果需要判断列表中是否存在指定的值,可以使用count()方法,如果存在则返回值大于0,如果返回0则表示不存在。或者,使用更加简洁的in关键字来判断一个值是否存在于列表中,返回结果为True或False。
>>> alist
[3,4,5,7,9]
>>> 3 in alist
True
关键字in和not in也可以用于其他可迭代对象,包括元组、字典、range对象、字符串和集合等,常用在循环语句中对序列或其他可迭代对象中的元素进行遍历。推荐熟练掌握运用。
2.1.5 切片操作
切片是Python序列的一个重要操作,适用于列表、元组、字符串、range对象等类型。切片使用2个冒号分隔的3个数字来完成,第一个数字表示切片的开始位置(默认为0),第二个数字表示切片截止(但不包括)位置(默认为列表长度),第三个数字表示切片的步长(默认为1),当步长省略时可以顺便省略最后一个冒号。可以使用切片来截取列表中的任何部分,得到一个新列表,也可以通过切片来修改和删除列表中的部分元素,甚至可以通过切片操作为列表对象添加元素。
与使用下标访问元素的方法不同,切片操作不会因为下标越界而抛出异常,而是简单地返回一个空列表或在列表尾部截断。
可以使用切片操作来快速实现很多目的,如原地修改列表内容,列表元素的增、改、删、查以及元素替换等操作都可以通过切片来实现,并且不影响列表对象内存地址。
>>> alist = [3,5,7]
>>> alist[len(alist):]
[]
>>> alist[len(alist):] = [9]
>>> alist
[3,5,7,9]
>>> alist[:3] = [1,2,3]
>>> alist
[1,2,3,9]
>>> alist = [3,5,7,9,11]
>>> del alist[:3]
>>> alist
[9,11]
需要注意的是,切片返回的是列表元素的浅复制,与列表对象的赋值并不一样
>>> alist = [3,5,7]
>>> blist = alist
>>> alist == blist
True
>>> alist is blist
True
>>> id(alist)#输出结果很可能不一样,这是正常的
19061816
>>> id(blist)
19061816
>>> blist = alist[::]#浅复制
>>> alist == blist
True
>>> alist is blist
False
>>> id(alist) == id(blist)
False
2.1.6 列表排序
一个很自然的想法是使用列表对象自身提供的sort()方法进行原地排序,该方法支持多种不同的排序方法。
>>> alist = [3,4,5,6,7,9,11,13,15,17]
>>> import random
>>> random.shuffle(alist)#打乱顺序
>>> alist
[3,4,15,11,9,17,13,6,7,5]
>>> alist.sort()#默认为升序
>>> alist
[3,4,5,6,7,9,11,13,15,17]
>>> alist.sort(reverse = True)#也可以降序排列
>>> alist
[17,15,13,11,9,7,6,5,4,3]
>>> alist.sort(key = lambda x:len(str(x)))#自定义排序
>>> alist
[9,7,6,5,4,3,17,15,13,11]
也可以使用内置函数sorted()对列表进行排序,与列表对象的sort()方法不同,该函数返回新列表,并不对原列表进行任何修改。
>>> alist
[9,7,6,5,4,3,17,15,13,11]
>>> sorted(alist)
[3,4,5,6,7,9,11,13,15,17]
>>> sorted(alist,reverse = True)
[17,15,13,11,9,7,6,5,4,3]
将列表元素进行逆序排列,也就是所有元素位置反转,第一个变成最后一个,第二个变成倒数第二个,以此类推。可以使用列表对象的reverse()方法将元素原地逆序。
>>> import random
>>> alist = [random.randint(50,100) for i in range(10)]
>>> alist
[87,79,52,96,56,59,74,80,53,79]
>>> alist.reverse()
>>> alist
[79,53,80,74,59,56,96,52,79,87]
Python提供了内置函数reversed()支持对列表元素进行逆序排列,与列表对象的reverse()方法不同,该函数不对原列表做任何修改,而是返回一个逆序排列后的迭代对象。
>>> alist = [3,4,5,6,7,9,11,13,15,17]
>>> newlist = reversed(alist)
>>> newlist
<listreverseiterator object at 0x0000000003624198>
>>> list(newlist)
[17,15,13,11,9,7,6,5,4,3]
>>> for i in newlist:
print (i)
在上面的代码中,最后的for循环没有输出任何内容,因为在之前的list()函数执行时,迭代对象已遍历结束,需要重新创建迭代对象才能再次访问其内容
>>> newlist = reverrsed(alist)
>>> for i in newlist
print (i)
17 15 13 11 9 7 6 5 4 3
2.1.7 用于序列操作的常用内置函数
(1)cmp(列表1,列表2):对两个列表进行比较,若第一个列表大于第二个,则结果为1,否则为-1,元素完全相同则结果为0,类似于==运算符,但和 is、is not不一样。
>>> (1,2,3) < (1,2,4)
True
>>> ((1,2,3),(1,2,4))
-1
但在python3.x中不再支持cmp()函数,可以使用关系运算符比较序列大小。
(2)len(列表):返回列表中的元素个数,同样适用于元组、字典、集合、字符串和range对象等各种可迭代对象。
(3)max(列表)、min(列表):返回列表中的最大或最小元素,同样适用于元组、字符串、集合、range对象和字典等。但对字典进行操作时,默认是对字典的“键”进行计算,如果需要对字典的“值”进行计算,则需要使用字典对象的values()方法明确说明。
>>> a = {1:1,2:5,3:8}
>>> max(a)
3
>>> max(a.values())
8
(4)sum(列表):对数值型列表的元素进行求和运算,对非数值型列表运算则出错,同样适用于元组、集合、range对象、字典等。但对字典进行操作时,默认是对字典“键”进行计算,如果需要对字典“值”进行计算,则需要使用字典对象的values()方法明确说明。
>>> a = {1:1,2:5,3:8}
>>> sum(a)
6
>>> sum(a.values())
14
(5)zip(列表1,列表2,...): 将多个列表或元组对应位置的元素组合为元组,并返回包含这些元组的zip对象。
>>> alist = [1,2,3]
>>> blist = [4,5,6]
>>> clist = zip(a,b)
>>> clist
<zip object at 0x0000000003728908>
>>> list(clist)
[(1,4),(2,5),(3,6)]
(6)enumerate(列表):枚举列表、元组或其他可迭代对象的元素,返回枚举对象,枚举对象中每个元素是包含下标和元素值的元组。该函数对字符串、字典同样有效。但对字典进行操作时,默认是对字典“键”进行计算,如果需要对字典“值”进行计算,则需要使用字典对象的values()方法明确说明。
>>> for item in enumerate(clist):
print (item)
(0,(1,4))
(1,(2,5))
(2,(3,6))
>>> a = {1:1,2:5,3:8}
>>> for i,v in enumerate(a.values()):
print (i,v)
0 1
1 5
2 8
2.1.8 列表推导式
列表推导式可以说是python程序开发时应用最多的技术之一。列表推导式使用非常简洁的方式来快速生成满足特定需求的列表,代码具有非常强的可读性。
>>> alist = [x*x for x in range(10)]
相当于:
>>> alist = []
>>> for x in range(10)
alist.append(x*x)
而
>>> freshfruit = ['banana','loganberry','passion fruit']
>>> alist = [w.strip() for w in freshfruit]
则等价于下面的代码:
>>> freshfruit = ['banana','loganberry','passion fruit']
>>> for i,v in enumerate(freshfruit):
freshfruit[i] = v.strip()
同时也等价于
>>> freshfruit = ['banana','loganberry','passion fruit']
>>> freshfruit = list(map(str.strip,freshfruit))
但是不等价于下面的代码:
>>> freshfruit = ['banana','loganberry','passion fruit']
>>> for i in freshfruit
i = i.strip()
提供几个示例
(1)使用列表推导式实现嵌套列表的平铺
>>> vec = [[1,2,3],[4,5,6],[7,8,9]]
>>> [num for elem in vec for num in elem]
[1,2,3,4,5,6,7,8,9]
(2)过滤不符合条件的元素
下面的代码用于从列表中选择符合条件的元素组成新的列表
>>> alist = [-1,-4,6,7.5,-2.3,9,-11]
>>> [i for i in alist if i>0]
[6,7.5,9]
(3)在列表推导式中使用多个循环,实现多序列元素的任意组合,并且可以结合条件语句过滤特定元素:
>>> [(x,y) for x in range(3) for y in range(3)]
[(0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)]
>>> [(x,y) for x in [1,2,3] for y in [3,1,4] if x!=y]
[(1,3),(1,4),(2,3),(2,1),(2,4),(3,1),(3,4)]
(4)使用列表推导式实现矩阵转置:
>>> matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
>>> [[row[i] for row in matrix] for i in range(4)]
[[1,5,9],[2,6,10],[3,7,11],[4,8,12]]
也可以使用内置函数zip()和list()来实现矩阵转置:
>>> list(zip(*matrix))
[(1,5,9),(2,6,10),(3,7,11),(4,8,12)]
2.2 元组
与列表类似,元组也是Python的一个重要序列结构,但与列表不同的是,元组属于不可变序列。元组一旦建立,用任何方法都不可以修改其元素的值,也无法为元组增加或删除元素,如果确实需要修改的话,只能再创建一个新的元组。
元组的定义形式和列表相似,区别在于定义元组时所有元素放在一对圆括号中,即()中,而不是方括号中。
2.2.1 元组的创建与删除
使用=将一个元组赋值给变量,就可以创建一个元组变量。
>>> atuple = ('a',)
>>> atuple
('a')
如果要创建只包含一个元素的元组,只把元素放在圆括号里是不行的,还需要在元素后面加一个逗号“ , ”,而创建包含多个元素的元组则不存在这个问题。
>>> a = 3
>>> a
3
>>> a = (3)
>>> a
3
>>> a = 3,
>>> a
(3,)
>>> a = 1,2
>>> a
(1,2)
如同使用list()函数将序列转换为列表一样,也可以使用tuple()函数将其他类型序列转换为元组。
>>> alist
[1,4,6,7,9]
>>> tuple(alist)
(1,4,6,7,9)
对于元组而言,只能使用del命令删除整个元组对象,而不能只删除元组中的部分元素,因为元组属于不可变序列。
2.2.2 元组与列表的区别
列表属于可变序列,可以随意地修改列表中的元素值以及增加和删除列表元素,而元组属于不可变序列,元组中的数据一旦定义就不允许通过任何方式进行更改。元组也支持切片操作,但是只能通过切片来访问元组中的元素,而不支持使用切片来修改元组中元素的值,也不支持使用切片操作来为元组增加或删除元素。
Python内置函数tuple()可以接受一个列表、字符串或其他序列类型和迭代器作为参数,并返回一个包含同样元素的元组,而list()函数可以接受一个元组、字符串或其他序列和迭代器作为参数并返回一个列表。从效果上看,tuple()函数可以看作是在冻结列表并使其不可变,而list()是在融化元组使其可变。
元组的访问和处理速度比列表更快。如果定义了一系列常量值,主要用途仅是对它进行遍历或其他1类似用途,而不需要对其元素进行任何修改,那么一般建议使用元组而不用列表。可以认为元组对不需要修改的数据进行了“写保护”,从内在实现上不允许修改其元素值,从而使得代码更加安全。
最后,作为不可变序列,与整数、字符串一样,元组可用作字典的键,而列表则永远都不能当作字典键使用,因为列表是可变的。
2.2.3 序列解包
使用序列解包功能可以对多个变量同时进行赋值:
>>> x,y,z = 1,2,3
>>> x,y,z
(1,2,3)
>>> print(x,y,z)
1 2 3
序列解包也可以用于列表和字典,但对字典使用时,默认是对字典“键”进行操作,如果需要对键-值对进行操作,则需要使用字典的items()方法;如果需要对字典“值”进行操作则需要使用字典的values()方法明确指定。对字典操作时,不需要对元素的顺序考虑过多。
下面的代码演示了列表与字典的序列解包操作:
>>> a = [1,2,3]
>>> b,c,d = a
>>> s = {'a':1,'b':2,'c':3}
>>> b,c,d = s.items()
>>> b
('c',3)
>>> b,c,d = s
>>> b
'c'
>>> b,c,d = s.values()
>>> print(b,c,d)
1 3 2
使用序列解包可以很方便的地同时遍历多个序列。
>>> keys = ['a','b','c','d']
>>> values = [1,2,3,4]
>>> for k,v in zip(keys,values):
print(k,v)
a 1
b 2
c 3
d 4
在调用函数时,在实参前面加上一个星号(*)也可以进行序列解包,从而实现将序列中的元素值依次传递给相同数量的形参。
2.2.4 生成器推导式
从形式上看,生成器推导式与列表推导式非常相近,只是生成器推导式使用圆括号而不是列表推导式所使用的方括号。与列表推导式不同的是,生成器推导式的结果是一个生成器对象,而不是列表,也不是元组。使用生成器对象的元素时,需要将其转换为列表或元组,也可以使用生成器对象的__next()__
方法进行遍历,或者直接将其作为迭代器来使用。但是不管用哪种方法访问其元素,当所有元素访问结束以后,如果需要重新访问,必须重新创建该生成器对象。
2.3 字典
字典是键-值对的无序可变序列,字典中的每个元素包含两部分:键和值。定义字典时,每个元素的键和值用冒号分隔,相邻元素之间用逗号分隔,所有的元素放在一对大括号{}中。
字典中的键可以为任意不可变数据,比如整数、实数、复数、字符串和元组等,但不能使用列表、集合、字典作为字典的键,因为这些类型的对象是可变的。另外,字典中的键不允许重复,值是可以重复的。
可以使用内置函数globals()返回和查看包含当前作用域内所有全局变量和值的字典,使用内置函数locals()返回包含当前作用域所有局部变量和值的字典。
2.3.1 字典的创建与删除
与列表和元组的创建一样,使用=将一个字典赋值给一个变量即可创建一个字典变量。
可以使用内置函数dict()通过已有数据快速创建字典:
>>> keys = ['a','b','c','d']
>>> values = [1,2,3,4]
>>> dictionary = dict(zip(keys,values))
>>> print(dictionary)
{'a':1,'c':3,'b':2,'d':4}
或者使用内置函数dict()根据给定的键-值对来创建字典:
>>> d = dict(name = 'Dong',age = 37)
>>> d
{'age':37,'name':Dong}
还可以以给定内容为键,创建值为空的字典:
>>> adict = dict.fromkeys(['name','age','sex'])
>>> adict
{'age':None,'name':None,'sex':None}
不再需要某个字典时,可以使用del命令删除整个字典,也可以使用del命令删除字典中指定的元素。
2.3.2 字典元素的读取
与列表和元组类似,可以使用下标的方式来访问字典中的元素,但不同的是字典的下标是字典的“键”,而列表和元组访问时下标必须为整数值。使用下标的方式访问字典时,若指定的“键”不存在则抛出异常。
目前推荐使用的且更加安全的字典元素访问方式是字典对象的get()
的方法,使用字典对象的get()的方法可以获取指定键对应的值,并且可以在指定键不存在的时候返回指定值,如果不指定则默认返回None。
另外使用字典对象的items()
方法可以返回字典的键-值对列表,使用字典对象的keys()方法,可以返回字典的键列表,使用字典对象的values()
方法可以返回字典的值列表。
2.3.3 字典元素的操作
当以指定键为下标为字典元素赋值时,若该键存在,则表示修改该键的值;若该键不存在,则表示添加一个新的键值对,也就是添加一个新元素。
使用字典对象的update()方法将另一个字典的键-值对一次性全部添加到当前字典对象。
>>> aDict
{'age':37,'name':'Dong'}
>>> aDict.update({'a':'a','b':'b'})
>>> aDict
{ 'a':'a','b':'b','age':37,'name':'Dong'}
需要删除字典元素时,可以根据具体要求使用del命令删除字典中指定键对应的元素,或者也可以使用字典对象的clear()方法来删除字典中的所有元素,还可以使用字典对象的pop()方法删除并返回指定键的元素,或者使用字典对象的 popitem()方法删除并返回字典中的一个元素。
2.4 集合
集合是无序可变序列,与字典一样使用一对大括号作为界定符,同一个集合的元素之间不允许重复,集合中每个元素都是唯一的。
2.4.1 集合的创建与删除
直接将集合赋值给变量即可创建一个集合对象。
可以使用set()函数将列表、元组等其他可迭代对象转化为集合,如果原来的数据中存在重复元素,则在转换为集合时只保留一个。
>>> aset = set(range(8,14))
>>> aset
set ([8,9,10,11,12,13])
>>> bset = set([0,1,2,3,0,1,2,3,7,8])
>>> bset
set([0,1,2,3,7,8])
不再使用某个集合时可以使用del命令删除整个集合。另外,也可以使用集合对象的pop()方法弹出并删除其中一个元素,或者使用集合对象的remove()方法直接删除指定的元素,以及使用集合对象的clear()方法清空集合并删除所有元素。
2.4.2 集合操作
aset.union(bset)#并集
aset & bset#交集
aset.intersection(bset)#交集
aset.difference(bset)#差集
aset.symmetric_difference(bset)#对称差
aset^bset#对称差
提取出序列中所有不重复的元素:
>>> newset = set(listrandom)
2.5 其他数据结构
2.5.1 堆
排序时使用较多,在heapq模块中提供了对堆的支持。
2.5.2 队列
队列的特点是先进先出(FIFO)和后进后出(LILO),在某些应用中有着重要的作用,例如多线程编程,作业处理等。Python还提供了queue模块和collections.deque模块支持队列的操作,当然也可以使用Python列表进行二次开发来实现自定义的队列结构。
2.5.3 栈
这栈是一种后进先出(LIFO)或先进后出(FILO)的数据结构,Python列表本身就可以实现栈结构的基本操作。列表对象的append()方法是在列表尾部追加元素,类似于入栈操作;pop()方法默认是弹出并返回列表的最后一个元素,类似于出栈操作。但是直接使用Python列表对象模拟栈操作并不太方便,例如当列表为空时再执行pop()则会抛出一个不很友好的异常;另外,也无法限制列表对象的大小。
2.5.4 链表
可以直接使用Python列表及其基本操作来实现链表的功能,但也可以对列表进行封装来实现自定义的链表结构。
2.5.5 二叉树
2.5.6 有向图
第3章 选择与循环
在传统的面向过程程序设计中,有三种经典的控制结构,即顺序结构、选择结构循环结构,即使在面向对象程序设计语言中以及事件驱动或消息驱动应用开发中,也无法脱离这三种基本的程序结构。可以说,不管使用哪种程序设计语言,在实际开发中,为了实现特定的业务逻辑和算法,都不可避免地要用到大量的选择结构和循环结构,并且经常需要将选择结构和循环结构嵌套使用。
3.1 选择结构
单分支选择结构
if 表达式:
语句块
双分支
if 表达式:
语句块1
else:
语句块2
多分支
if 表达式1:
语句块1
elif 表达式2: #elif 是 else if的缩写
语句块2
elif 表达式3:
语句块3
嵌套
if 表达式:
语句块1
if 表达式2:
语句块2
else:
语句块3
else:
if 表达式4:
语句块4
3.2 循环结构
Python提供了两种基本的循环结构:while循环和for循环。其中,while循环一般用于循环次数难以提前确定的情况,当然也可以用于循环次数确定的情况;for循环一般用于循环次数可以提前确定的情况,尤其适用于枚举或遍历序列或迭代对象中的元素;编程时一般建议优先考虑使用for循环,相同或不同的循环结构之间可以互相嵌套,也可以与选择结构嵌套使用,用来实现更为复杂的逻辑。
while循环和for循环常见的用法为:
while 表达式:
循环体
for 变量 in 序列或其他迭代对象:
循环体
另外while循环和for循环都可以带else子句,如果循环因为条件表达式不成立而自然结束(不是因为执行了break而结束循环)时则执行else结构中的语句,如果循环是因为执行了break语句而导致循环提前结束则不执行else中的语句。
while 表达式:
循环体
else:
else 子句
for 变量 in 序列或其他迭代对象:
循环体
else:
else 子句
3.3 break和continue语句
break语句在while循环和for循环中都可以使用,一般常与选择结构结合使用,以达到在特定条件得到满足时跳出循环的目的。一旦break执行语句,将使得整个循环提前结束。continue语句的作用是终止本次循环并忽略continue之后的所有语句,然后回到循环的顶端,提前进入下一次循环。需要注意的是过多的break和continue语句会严重降低程序的可读性,除非break或continue语句可以让代码更简单或更清晰,否则不要轻易使用。
第4章 字符串
在Python中字符串属于不可变序列类型,使用单引号、双引号、三单引号或三双引号作为界定符,并且不同的界定符之间可以相互嵌套。除了支持序列通用方法(包括比较、计算长度、元素访问和分片等操作)以外,字符串类型还支持一些特有的操作方法,例如格式化操作,字符串查找,字符串替换等。但由于字符串属于不可变序列,不能对字符串对象进行元素增加、修改与删除等操作。字符串对象提供的replace()和translate()方法并不是对原字符串直接进行修改替换,而是返回一个修改替换后的结果,字符串并不对原字符串做任何改动。
4.1 字符串格式化
'% [-] [+] [0] [m] [.n] 格式字符' % x
'''%符号之前的部分为格式字符串,%之后的部分为需要进行格式化的内容。
从左到右各字符的含义:
格式标志,表示格式开始 指定左对齐输出 对正数加正号 指定空位填0 指定最小宽度 指定精度 指定类型 格式运算符 待转换的表达式'''
格式字符 | 说明 |
---|---|
%s | 字符串(采用str()的显示) |
%c | 单个字符 |
%r | 字符串(采用repr()的显示) |
%d | 十进制整数 |
>>> '%d,%c'%(65,65) #使用元组对字符串进行格式化,按位置对应
'65,A'
目前Python社区更推荐使用format()方法进行格式化,该方法更加灵活,不仅可以使用位置进行格式化,还支持使用与位置无关的名字来进行格式化,并且支持序列解包格式化字符串。
例如:
>>> print("The number {0:,} in hex is:{0:#x}, the number {1} in oct is {1:#o}".format(5555,55))
The number 5,555 in hex is: 0x15b3, the number 55 in oct is 0o67
4.2 字符串常用方法
(1)find()
该函数用来查找一个字符串在另一个字符串指定范围(默认是整个字符串)中首次出现的位置,如果不存在则返回-1
>>> s = "apple,peach,banana"
>>> s.find("peach")
6
(2)split()
该函数用来以指定字符为分隔符,将字符串分割成多个字符串,并返回包含分割结果的列表。
>>> s = "apple,peach,banana,pear"
>>> li = s.split(",")
>>> li
["apple","peach","banana","pear"]
如果不指定分隔符,则字符串中的任何空白符号(包括空格、换行符和制表符等)都将被认为是分隔符,返回包含最终分割结果的列表。
(3)join()
与split()方法相反,该函数用来将列表中多个字符串进行连接,并在相邻两个字符之间插入指定字符。
使用运算符+也可以连接字符串,但效率较低,应优先使用join()方法。
>>> li = "apple,peach,banana,pear"
>>> sep = ","
>>> s = sep.join(li)
>>> s
"apple,peach,banana,pear"]
(4)lower()、upper()、capitalize()、title和swapcase()
这几个函数分别用来将字符串转换为小写字符串、将字符串转换为大写字符串、将字符串首字母变为大写、将每个单词的首字母变为大写以及大小写转换。
(5)replace()
该函数用来替换字符串中指定字符或子字符串的所有重复出现,每次只能替换一个字符或一个子字符串的重复出现。
>>> s = "中国,中国"
>>> print(s)
中国,中国
>>> s2 = s.replace("中国","中华人民共和国")
>>> print(s2)
中华人民共和国,中华人民共和国
(6)maketrans()和translate()
maketrans()函数用来生成字符映射表,而translate()则按映射表关系转换字符串并替换其中的字符,使用这两个函数的组合可以同时处理多个不同的字符,replace()则无法满足这一要求。
(7)strip()和rstrip()
该函数用来删除两端(或右端)的空白字符或连续的指定字符。
(8)eval()
内置函数eval()尝试把任意字符串转换为Python表达式并进行求值,它可以计算任意合法表达式的值。
(9)关键字in
与列表、元组、字典、集合一样,也可以使用关键字in 和 not in来判断一个字符串是否出现在另一个字符串中。
(10)startswith()和endswith()
这两个函数用来判断字符串是否以指定字符串开始或结束。
(11)isalnum()、isalpha()和isdigit()
用来测试字符串是否为数字或字母、是否为字母、是否为数字字符。
第5章 函数设计与使用
5.1 函数定义
Python中定义函数的语法如下,使用def
关键字来定义函数,然后是一个空格和函数名称,接下来是一对圆括号,在圆括号内是形式参数列表,如果有多个参数则使用逗号分隔开,圆括号之后是一个冒号,最后以换行开始的函数体代码。定义函数时需要注意的问题是:①函数形参不需要声明其类型;②即使该函数不需要接收任何参数,也必须保留一对空的圆括号;③函数体相对于def关键字必须保持一定的空格缩进。
def 函数名([参数列表]):
函数名
例如,下面的函数用来计算斐波那契数中小于参数n的所有值:
def fib(n):
a,b = 1,1
while a<n:
print(a,end = '')
a,b = b,a+b
print()
该函数的调用方式为
fib(100)
5.2 参数类型
5.2.1 可变长度参数
可变长度参数有两种形式:*parameter和**parameter,前者用来接受任意多个实参并将其放在一个元组中,后者接受类似于关键参数一样显式赋值形式的多个实参并将其放入字典中。
5.2.2 参数传递的序列解包
要保证实参中元素个数与形参个数相等。
>>> def demo (a,b,c)
print(a+b+c)
>>> seq = [1,2,3]
>>> demo(*seq)
6
5.2.3 lambda表达式
lambda表达式可以用来声明匿名函数,即没有函数名字的临时使用的小函数,lambda表达式只可以包含一个表达式,不允许包含其他复杂的语句,但在表达式中可以调用其他函数,该表达式的计算结果是函数的返回值。
>>> f = lambda x,y,z:x+y+z
>>> print f(1,2,3)
6
>>> L = [(lambda x:x**2),(lambda x:x**3),(lambda x:x**4)]
>>> print (L[0](2),L[1](2),L[2](2))
4 8 16
5.2.4 几个函数
(1)map()函数
子函数map可以将一个单参数函数依次作用到一个序列或迭代器对象的每一个元素上,并返回一个列表,作为结果,该列表中的每个元素是原序列中的元素经过函数处理后的结果,该函数不对原序列或迭代器对象做任何修改。
>>> map(str,range(5))
['0','1','2','3','4']
>>> def add5(v)
return v+5
>>> map(add5,range(10))
[5,6,7,8,9,10,11,12,13,14]
(2)reduce()函数
函数reduce()可以将一个接受2个参数的函数以累积的方式从左到右依次作用到一个序列或迭代器对象的所有元素上。从functools模块导入。
>>> seq = [1,2,3,4,5,6,7,8,9]
>>> reduce(lambda x,y:x+y, seq)
45
>>> def add(x,y)
return x+y
>>> reduce(add, range(10))
45
(3)filter()函数
内置函数filter()将一个单参数函数作用到一个序列上,返回该序列中使得该函数返回值为True的那些元素组成的列表、元组或字符串。
>>> seq = ['foo', 'x41', '?!', '***']
>>> def func(x)
return x.isalnum()
>>> filterr(func,seq)
['foo', 'x41']
第6章 面向对象程序设计
6.1 类的定义与使用
Python使用class关键字来定义类,class保留字之后是一个空格,然后是类的名字,再然后是一个冒号,最后换行并定义类的内部实现。类名的首字母一般要大写。
class Car: #新式类必须有至少一个基类
def infor(self):
print("This is a car")
类的所有实例方法都必须至少有一个名为self的参数,并且必须是方法的第一个形参(如果有多个形参的话),self参数代表将来要创建的对象本身。在类的实例方法中访问实例属性时需要self为前缀,但调用对象方法时不需要传递这个参数。
注:在定义类的方法时,一般习惯将第一个参数定义为self,但实际上类的实例方法中第一个参数的名字是可以变化的,而不必须使用self这个名字。
属性有两种:一种是实例属性;另一种是类属性。实例属性一般是指在构造函数__init__()
中定义的,定义时以self作为前缀;类属性是在类中所有方法之外定义的数据成员。在主程序中(或类的外部),实例属性属于实例(对象),只能通过对象名访问,而类属性属于类,可以通过类名或对象名访问。
在类的方法中可以调用类本身的其他方法,也可以访问类属性以及对象属性。在Python中比较特殊的是,可以动态地为类和对象增加成员,这一点是和很多面向对象程序设计语言不同的,也是Python动态类型特点的一种重要体现。
class Car:
price = 100000
def __init__(self,c)
self.color = c
car1 = Car("Red")
car2 = Car("Blue")
print(car1.color,Car.price)
Car.price = 110000
Car.name = 'QQ'
car1.color = "Yellow"
print(car2.color,Car.price,Car.name)
print(car1.color,Car.price,Car.name)
在Python中,以下划线开头的变量名有特殊的含义,尤其是在类和模板的定义中。用下划线作为变量前缀和后缀来表示类的特殊成员。
(1)_xxx
:这样的对象称为保护变量,不能用'from module import *'导入,只有类对象和子类对象能访问这些变量。
(2)__xxx__
:系统定义的特殊成员。
(3)__xxx
:类中的私有成员,只有类对象自己能访问,子类对象也不能访问这个成员,但在对象外部可以通过“对象名._类名__xxx
”这样的特殊方式来访问。Python中不存在真正意义上的私有成员。
另外,在IDLE交互模式下,一个下划线“_”表示解释器中最后一次显示的内容或最后一次语句正确执行的输出结果。
>>> 3 + 5
8
>>> _ + 2
10
6.2 类的特殊方法
类的方法可以粗略分为四大类:公有方法、私有方法、静态方法和类方法。其中,公有方法和私有方法都属于对象,每个对象都有自己的公有方法和私有方法,在这两类方法中,可以访问属于类和对象的成员;公有方法通过对象名直接调用,私有方法不能通过对象名直接调用,只能在属于对象的方法中通过self调用或在外部通过Python支持的特殊方式来调用;静态方法和类方法都可以通过类名和对象名调用,但不能访问属于对象的成员,只能访问属于类的成员。
Python类有大量的特殊方法,比较常见的是构造函数和析构函数。Python中类的构造函数是__init__()
,一般用来为数据成员设置初值或进行其他必要的初始化工作,在创建对象时自动执行。如果用户没有设计构造函数,Python将提供一个默认的构造函数用来进行必要的初始化工作。Python中类的析构函数是__del__()
,一般用来释放对象占用的资源,在Python删除对象和收回对象空间时自动执行。如果用户没有编写析构函数,Python将提供一个默认的析构函数进行必要的清理工作。
6.3 继承机制
在继承关系中,已有的、设计好的类称为父类或基类,新设计的类称为子类或派生类。派生类可以继承父类的公有成员,但是不能继承其私有成员。
Python支持多继承,如果父类中有相同的方法名,而在子类中使用时没有指定父类名,则Python解释器将从左向右按顺序进行搜索。
第7章 文件操作
7.1 文件基本操作
无论是文本文件还是二进制文件,其操作流程基本都是一致的,即:首先打开文件并创建文件对象,然后通过该文件对象对文件内容进行读取、写入、删除、修改等操作,最后关闭并保存文件内容。Python内置了文件对象,通过open()
函数可以按指定模式打开指定文件并创建文件对象,例如:
文件对象名 = open(文件名[,打开方式[,缓冲区]])
其中,文件名指定了被打开的文件名称,如果要打开的文件不在当前目录,还需要指定完整路径,为了减少完整路径中\符号的输入,可以使用原始字符串;打开模式指定了打开文件后的处理方式,例如“只读”、“读写”、“追加”等;缓冲区指定了读写文件的缓存模式,数值0表示不缓存,数值1表示缓存,如大于1则表示缓冲区的大小,默认值是缓存模式。如果执行正常,open()
函数返回一个文件对象,通过该文件对象可以对文件进行各种操作,如果指定文件不存在、访问权限不够、磁盘空间不够或其他原因导致创建文件对象失败则抛出异常。
例如,下面的代码分别以读、写方式打开了两个文件并创建了与之对应的文件对象:
f1 = open('file1.txt','r')
f2 = open('file2.txt','r')
当对文件内容操作完以后,一定要关闭文件,以保证所做的任何修改都得到保存。
f1.close()
打开模式
模式 | 说明 |
---|---|
r | 读模式 |
w | 写模式 |
a | 追加模式 |
b | 二进制模式(可与其他模式组合使用) |
+ | 读、写模式(可与其他模式组合使用) |
文件对象常用常用属性
属性 | 说明 |
---|---|
Closed | 判断文件是否关闭,若文件被关闭,则返回True |
Mode | 返回文件的打开模式 |
Name | 返回文件的名称 |
7.2 文本文件基本操作
文本文件存储的是常规字符串,由若干文本行组成,通常每行以换行符\n结尾。常规字符串是指记事本或其他文本编辑器能正常显示、编辑并且人类能够直接阅读和理解的字符串,如英文字母、汉字、数字字符串。文本文件可以使用字处理软件(gedit、记事本等)进行编辑。
主要熟悉read()、write()以及其他读写方法,当读写操作完成之后,都会自动移动文件指针,如果需要对文件指针进行定位,可以使用seek()方法,如果需要获知文件指针当前位置可以使用tell()方法。
使用上下文管理关键字with可以自动管理资源,不论何种原因跳出with块,总能保证文件被正确关闭,并且可以在代码快执行完毕后自动还原进入该代码块。
7.3 二进制文件
二进制文件把对象内容以字节串(bytes)进行存储,无法用记事本或其他普通字处理软件直接进行编辑,也无法通过Python的文件对象直接读取和理解,通常也无法被人类直接阅读和理解,需要专门的软件进行解码后读取、显示、修改、或执行。常见的如图形图像文件、音视频文件、可执行文件、资源文件、各种数据库文件、各类Office文档等都属于二进制文件。
必须正确理解二进制文件结构和序列化规则,才能准确地理解二进制文件内容并且设计正确的反序列化规则。序列化是指把内存中的数据在不丢失其类型信息的情况下转成对象的二进制形式表示的过程,对象序列化后的形式经过正确的反序列化过程应该能够准确地恢复为原对象。
Python中常用的序列化模块有struct、pickle、json、marshal和shelve,其中pickle有C语言实现的cPickle,速度约提高1000倍,应优先考虑使用。
7.4 文件和目录操作
Python提供了os、os.path和shutil等大量模块支持文件级的操作。
os模块的常用文件操作方法:
方法 | 功能说明 |
---|---|
access(path mode) | 按照mode指定的权限访问文件 |
open(filename,flag[mode=0777]) | 按照mode指定的权限打开文件,默认权限为可读、可写、可执行 |
chmode() | 改变文件的访问权限 |
remove(path) | 删除指定的文件 |
rename(src,dst) | 重命名文件或目录 |
stat(path) | 返回文件的所有属性 |
fstat(path) | 返回打开的文件的所有属性 |
listdir(path) | 返回path目录下的文件和目录列表 |
getced() | 返回当前工作目录 |
os.path模块的常用文件操作方法:
方法 | 功能说明 |
---|---|
exist(path) | 判断文件是否存在 |
getatime(filename) | 返回文件的最后访问时间 |
getctime(filename) | 返回文件的创建时间 |
getmtile(filename) | 返回文件的最后修改时间 |
getsize(filename) | 返回文件的大小 |
isabs()、isdir(path)、isfile(path) | 判断path是否为绝对路径、目录、文件 |
split(path) | 对路径进行分割,以列表形式返回 |
splitext(path) | 从路径中分割文件的扩展名 |
splitdrive(path) | 从路径中分割驱动器的名称 |
walk(top,func,arg) | 遍历目录 |
除了支持文件操作,os和os.path模块还提供了大量的目录操作方法,可以通过dir(os.path)查看os.path模块关于目录操作的方法。
os模块常用的目录操作方法:
方法 | 功能说明 |
---|---|
mkdir(path[,mode=0777]) | 创建目录 |
makedirs(path1/path2…,mode=511) | 创建多级目录 |
rmdir(path) | 删除目录 |
removedirs(path1/path2...) | 删除多级目录 |
listdir(path) | 返回指定目录下的文件和目录信息 |
getcwd() | 返回当前工作目录 |
chdir(path) | 把path设为当前工作目录 |
walk(top,topdown=True,onerror=None) | 遍历目录树,该方法返回一个元组,包括三个元素:所有路径名、所有目录列表与文件列表 |
第8章 异常处理结构与程序调试
8.1 Python中的异常处理结构
几户每种高级编程语言都提供了异常处理结构。简单地说,异常是指程序运行时引发的错误,引发错误的原因有很多,如除零、下标越界、文件不存在和网络异常等。如果这些错误得不到正确的处理会导致程序终止运行,而合理地使用异常处理结果可以使程序更加健壮,具有更高的容错性,不会因为用户不小心的错误输入而造成程序终止。或者,也可以使用异常处理结构给用户更加友好的提示。
异常处理结构中最常见也是最基本的结构应该是try...except...
结构。其中try子句中的代码块包含可能出现异常的语句,而except子句用来捕捉相应的异常,except子句中的代码块则用来处理异常。如果try中的代码块没有出现异常则继续往下执行异常处理结构后面的代码;如果出现异常并且被except子句捕获则执行except子句中的异常处理代码;如果出现异常但没有被except捕获则继续往外层抛出;如果所有层都没有捕获并处理该异常,则程序终止并将该异常抛给最终用户。
try:
try块 #被监控的语句,可能会引发异常
except Exception[, reason]:
except块 #处理异常的代码
在使用时,except子句可以在异常类名字后面指定一个变量,用来捕获异常的参数或更详细的信息。
try:
raise Exception('spam','eggs')
except Exception as inst:
print(type(inst)) #the exception instance
print(inst.args) #arguments stored in .args
另一种常用的异常处理结构是try...except..else...
语句。如果try中的代码抛出了异常,并且被某个except捕捉过则执行相应的异常处理代码,这种情况下不会执行else中的代码;如果try中的代码没有抛出异常,则执行else块。例如下面的代码:
alist = ['China','America','England','France']
print('请输入字符串的序号')
while True:
n = input()
try:
print(alist[n])
except IndexError:
print('列表元素的下标越界或格式不正确,请重新输入字符串的序号')
else:
break
在实际开发中,同一段代码可能会抛出多个异常,需要针对不同的异常类型进行相应的处理。Python提供带有多个except的异常处理结构,这类似于多分支选择结构。一旦某个except捕获了异常,则后面剩余的except子句将不会再执行。
在try...except...finally...
结构中,finally子句中的内容不论是否发生异常都会被执行,常用来做一些清理工作以释放try子句中申请的资源。语法如下:
try:
...
finally:
... #无论如何都会执行的代码
例如下面的代码,无论读取文件是否发生异常,总是能够保证正常关闭该文件。
try:
f = open('test.txt','r')
line = f.readline()
print(line)
finally:
f.close()
需要注意的一个问题是,如果try子句中的异常没有被捕捉和处理,或者except子句或else子句中的代码出现了异常,那么这些异常将会在finally子句执行完后再次抛出。例如上面的代码,使用异常处理结构的本意是为了防止文件读取操作出现异常而导致文件不能正常关闭,但是如果因为文件不存在而导致文件对象创建失败,那么finally子句中关闭文件对象的代码将会抛出异常从而导致程序终止运行。
使用带有finally子句的异常处理结构时,应尽量避免在finally子句中使用return语句,否则可能会出现出乎意料的错误。
8.2 断言与上下文管理
断言的语法如下
assert expression[,reason]
当判断表达式expression的值为真时,什么都不做;如果表达式的值为假,则抛出异常。
assert语句一般用于对程序某个时刻必须满足的条件进行验证,仅当__debug__
为True时有效。当python脚本以-O选项编译为字节码文件时,assert语句将被移除以提高运行速度。
使用上下文管理语句with可以自动管理资源,在代码块执行完毕后自动还原进入该代码块之前的现场或上下文。无论何种原因跳出with块,也不论是否发生异常,总能保证资源被正确释放,这大大简化了程序员的工作,常用于文件操作、网络通信之类的场合。
with语句的语法如下:
with context_expr [as var]:
with块
例如下面的代码演示了文件操作时with语句的用法,使用这样的写法程序员丝毫不用担心忘记关闭文件,当文件处理完以后,将会自动关闭。
with open('D:\\test.txt') as f:
for line in f:
print(line)
8.3 用sys模块回溯最后的异常
当发生异常时,Python会回溯异常,给出大量的提示,可能会给程序员的定位和纠错带来一定的困难,这时可以使用sys模块来回溯最后的异常。
语法为
import sys
try:
block
except:
t = sys.exc_info()
print(t)
sys.exc_info()
的返回值是一个三元组(type,value/message,traceback)。其中,type表示异常的类型,value/message表示异常的信息或者参数,而traceback则包含调用栈信息的对象。