这份笔记适合需要快速入门 Python 的零基础选手。 因为我学过 C,而且 Python 是用 C 做的,所以笔记中会经常对比 C 的用法。 由于我个人的爱好和需求,本笔记会补充很多书上没有的知识。 为了兼顾要复习的读者和初学的读者,我已经尽量调整这本书的讲授顺序了,但 Python 里很多概念交织在一起,感觉先介绍哪个都不合适,还请初学编程的读者多多谅解啦!

一、Python 的简介

1.1 Python 的特点

  • 简单易学。它语法简单,优秀的 Python 代码如英文伪代码,让程序员专注于解决问题而非语言本身。
  • 自由开放。它是FLOSS(自由/开放源代码软件)的成员之一,可以由使用者自由学习、改进。
  • 跨平台性。它已被移植到诸多平台,只要避开系统依赖性的特性即可不作修改地移植。
  • 解释性。从源代码直接通过解释程序运行,保证了良好的跨平台性。
  • 面向对象。它是强面向对象的,其中的一切都称为对象,包括函数、常量、变量等。
  • 可扩展性。可以用 C/C++ 写一部分代码,在 Python 程序中使用。
  • 可嵌入性。可以把 Python 程序嵌入 C/C++ 中使用。
  • 丰富的库。有庞大的标准库和各种各样由万能的网友编的库,可以方便地实现个性化的需求。

    1.2 Python2 与 Python3

    《A Byte of Python》是为 Python 3 编写的。可根据实际需要选择用 Python 2 还是 Python 3 ,学会了 Python 3 之后可以很快学会 Python 2 。

    二、预备工作

    2.1 Python 的安装

    https://www.python.org/downloads/
    这里以 Windows 操作系统的 Python 3.5.1 为准,虽然现在已经有更新版本的了。

    2.2 交互式解释器提示符

    通过 Python 来运行程序有两种方法——使用交互式解释器提示符,或直接运行一个源代 码文件。接下来会分别讲这两种方法。

启动 Python 后,在能输入内容的地方会出现>>>,被称作 Python 解释器提示符 ,在它后面输入Python代码后按 Enter 键就可以运行代码,输入参数或表达式会显示它的值。Python 的细节会因为不同的操作系统而有所不同,但是从提示符开始部分应该是相同的。
image.png
若使用的是 Windows 命令提示符,可以按 Ctrl + Z + Enter 键来退出解释器提示符。
按照学编程语言的惯例,在解释器提示符后面输入如下代码(注意 Python 和 C 一样对大小写敏感):

  1. print("Hello World")

然后按 Enter 键,屏幕上就会打印出“Hello World”。这就是交互式解释器提示符的基本用法。

2.3 编辑器

编辑器可以让用户输入并保存 Python 源代码,以便重复运行使用。它们还有语法高亮和自动缩进的功能,方便编程。初学者建议用 PyCharm 教育版,老手建议用 Vim 或 Emacs。不建议用记事本,虽然它真的可以用。

2.4 help( ) 函数

用 Python 内置的 help( ) 函数可以了解关键字的相关知识。其原理是打印关键字的文档字符串。
例如想知道 len( ) 函数和 return 运算符的用法,就这样写:

  1. help('len')
  1. help('return')

然后运行,就会显示帮助。按 Q 键(quit)可以退出帮助。

三、基础知识

3.1 注释

#右侧的内容会被解释程序忽略,不执行,作为注释。
例:

  1. #喵喵喵,喵喵喵喵喵喵喵喵。
  2. print('喵喵喵') #喵喵,喵喵喵“print('喵喵喵')”喵喵喵喵喵。
  3. #喵喵(Meow Meow)喵喵喵喵喵喵喵。

3.2 字面常量

叫这个名字是因为这类常量的值就是它字面的意义。包括数字、字符串。

3.2.1 数字

整数(Integer)5浮点数(float)3.1452.3E-4(科学计数法,表示52.3×10-4)。
跟 C 类似,但不区分 int 和 long ,不区分 float 和 double。
因为计算机内存中是用二进制来计浮点数的,所以浮点数一定会有少量误差,Python 也不例外。

3.2.2 字符串

3.2.2.1 字符串基本知识

可以是用单引号/双引号括起来的一行内的字符序列,也可以是用三引号括起来的多行字符串。
Python 没有 C 中的 char 类型,且 Python 中的字符串是常量而非字符数组,是不可修改的。
例:

  1. '用单引号括起来的字符串中可以用"字符,解释器不会解释错误。'
  2. "同理,在这里写'也不会解释错误。除此之外,这两种引号括起来的字符串是一样的。"
  3. '''三引号里可以用"和'。
  4. 三引号可以是三个',也可以是三个"。'''
  5. """这是多行字符串的第一行。
  6. And this is the second line.
  7. "What's your name?" I asked.
  8. He said "Bond, James Bond." #用在三引号中的"和'都不会解释错误,这一点在写英文时最有用
  9. """#另外,看解释器给代码标的颜色可以发现,三引号中不好加注释,会被当成字符串的一部分。如上↑

Python 和 C 一样有转义符,用来代表那些不方便在字符串中表示出来的符号。解释程序会把字符串中的转义符当作对应的字符来对待。

转义符 \‘ \“ \\ \n \t
意义 单引号 双引号 一个反斜杠 换行 水平制表

不想解释程序把字符串中的转义符换成对应的字符,可以在字符串前面放rR来指定原始字符串。

  1. r"Newlines are indicated by \n"
  2. R"Newlines are indicated by \n"

image.png

3.2.2.2 .format( ) 方法

.format( )方法,即格式化方法,是一种字符串方法,可以结合其他信息构建字符串,把代表着输出字符串的格式的格式化字符串中的{n}(n表示从0开始的整数序列)依次替换成括号中的参数。在用{n}表示了一般格式的字符串后写.format(参数表)即可。
代码:

  1. age = 20 #Python中创建变量不需要专门的声明语句,直接用赋值语句就行了。像这里创建了一个叫age的变量
  2. name = 'Swaroop' #也不需要专门说明变量的数据类型,Python解释器会自动判断
  3. #print()函数既能打印数字,也能打印字符串,不用担心类型不对
  4. print('{0} was {1} years old when he wrote this book'.format(name, age))
  5. print('Why is {0} playing with that python?'.format(name))
  6. #其实{}中的数字可有可无。因为它们肯定是从0开始的自然数序列,不用提示Python也知道哪个{}换成哪个参数

输出:

  1. Swaroop was 20 years old when he wrote this book
  2. Why is Swaroop playing with that python?

.format( ) 方法中还可以用更详细的格式,如下。
代码:

  1. #:右边写具体格式,:d表示整数,:f表示浮点数
  2. #这里的:.nf和C中的意义相同,即对于浮点数保留小数点后n位
  3. print('{0:.3f}'.format(1/3))
  4. #使用下划线填充文本,保持文字处于中间位置,并用^定义字符串长度为11
  5. print('{0:_^11}'.format('hello'))
  6. #还可以基于关键词输出。这样往()中放参数的时候就不用考虑顺序了。而且一个参数可以用多次
  7. print('{name} wrote {book}'.format(name='Swaroop', book='A Byte of Python'))

输出:

  1. 0.333
  2. ___hello___
  3. Swaroop wrote A Byte of Python

注意到,print( ) 函数将参数表中的信息被打印到屏幕上时,参数之间自动补充空格,且打印完后会自动换行。这会对我们的格式产生影响。可以在其他参数后面增加一个 end 参数来改变这种默认的格式。
代码:

  1. print('a', end='') #end参数设为空字符串,表示不用任何符号结尾
  2. print('b') #没有end参数,默认打印完后用\n结尾,即打印完后换行
  3. print('a', end=' ') #表示用一个空格而不是默认的\n结尾
  4. print('b', end=' ')
  5. print('c')

输出:

  1. ab
  2. a b c

3.2.2.3 .join( ) 方法

一般形式:字符串.join(序列)
可以返回一个在序列元素之间插入了指定字符串的大字符串。
代码:

  1. print('>'.join('54321'))

输出:

  1. 5>4>3>2>1

3.2.2.4 其他字符串方法

方法 .title( ) .upper( ) .lower( ) .strip( ) .rstrip( ) .lstrip( )
效果 单词首字母大写,其余小写 全大写 全小写 删除首尾空格(含制表符、换行符) 删除末尾空格(含制表符、换行符) 删除开头空格(含制表符、换行符)

可以打印被这些方法操作过的字符串,但原字符串的值并没有改变,因为字符串是不可修改的。要想让这些方法对字符串的操作永久生效,只能把操作后的字符串存储在其他变量中,作为新字符串保存。

方法 .startswith() .find() .endswith()
参数 一个字符串 一个字符串 一个字符串
效果 返回布尔型,表示原字符串是否以作为参数的字符串开头 返回-1表示原字符串不包含作为参数的字符串,返回其他数则说明包含 返回布尔型,表示原字符串是否以作为参数的字符串结尾

这三个方法可以查找字符串内容。

3.3 变量

用来储存各种信息,可以被访问、操控。和 C 不同,使用前不需要声明其类型,但是必须赋值。变量赋值以后才会被创建,它的数据类型才确定下来。给变量起的名字称为标识符

3.3.1 命名规则

跟 C 一样,标识符由字母、数字、下划线构成,大小写敏感,第一个字符不能是数字。
为了区分变量和其他东西,虽然语法上没这样要求,但 Python 用户之间约定俗成:变量名中不使用大写字母。
合法的标识符:i``name_2_3``_2等。
不合法的标识符:2things``this is spaced out等。

3.3.2 变量与内存空间

和 C 不同的是,Python 中内存空间不是给变量名分配的,而是给常量分配的,Python 中的变量名都只代表指针。修改了一个变量的内容,原来它指向的内存空间上还放着原来的那个值,新的值则是被存储在新的内存空间里,变量名代表的指针指向新的内存地址。可以用 id( ) 函数返回变量指向的内存地址来验证这种说法。
代码:

  1. a = 1
  2. print(id(a))
  3. a += 1
  4. print(id(a)) #这两次打印出来的内存地址不一样

3.4 数据类型

内存中对象(含变量、常量)的类型。
除了使用 Python 自带的基本数据类型,还可以通过类来创建构造自己的数据类型。
《A Byte of Python》笔记 - 图3
Python中有强制类型转换函数,可以把数据类型不合适的数据改成需要的类型。例如:

  1. int(x) #把x的数据类型转换成整数
  2. float(y) #把y的数据类型转换成浮点数
  3. str(z) #把z的数据类型转换成字符串

3.5 物理行&逻辑行

物理行是用户编写程序时所看到的一行代码。逻辑行是解释程序所看到的单个语句。
Python 会假定每一物理行对应一个逻辑行,且鼓励每一逻辑行使用一物理行,从而使得代码更加可读。
如果一定要在一个物理行中表示多个逻辑行,可以用;把多个语句连起来。

不建议用这个功能,会导致代码可读性下降。

例:

  1. i = 5
  2. print(i) #两个物理行,两个逻辑行,一个定义变量,一个打印。这个写法好。
  3. i = 5; print(i) #一个物理行,两个逻辑行。
  4. i = 5; print(i); #一个物理行,两个逻辑行。3、4两个物理行都跟前两行等价。

反之,也可以把一个冗长的逻辑行拆成多个物理行。用\把一个物理行拆成多个,称为显式行连接
例:

  1. s = 'This is a string. \
  2. This continues the string.'
  3. print(s) #输出仍在一行内,为This is a string. This continues the string.
  1. i = \
  2. 5 #等价于一行i = 5

有时逻辑行以括号开始,默认了多个物理行对应一个逻辑行,允许不用反斜杠,称为隐式行连接

3.6 缩进

Python 中,各行开头的空白区(空格/制表符)称为缩进,用来确定各逻辑行的缩进级别,从而确定语句的分组。逻辑上放置在同一层级的语句必须有相同的缩进,这样的一组语句称为。逻辑上在同一层级的语句不能被分在不同的语句块中,否则运行会出错,如下:

  1. i = 5 #这三行顺序运行,逻辑上处于同一层级
  2. print('Value is', i) #注意本行行首有一个空格,被缩进至另一层级。这样运行会出错
  3. print('I repeat, the value is', i)

建议每两个相邻层级之间用四个空格或一个制表符作为缩进。好的编辑器会帮忙缩进。

四、运算符与表达式

用运算符连接操作数构成表达式。如2 + 3是表达式,其中+是运算符,23是操作数。

表达式中的运算符两边的空格其实可有可无,但是一般都会空格,方便看清表达式,提高可读性。

4.1 运算符

基本的运算符如下:

符号 名称 注释
+ 一数不变/两数相加/两字符串连接。4 + 5输出9'a'+'b'输出'ab'
- 一数取相反数/两数相减/两集合取差集。
* 数字相乘/字符串重复。2 * 7输出14'la' * 3输出'lalala'
** 乘方 x ** y就是取 x 的 y 次方。
/ 除以 python3 中两整数相除也默认输出浮点数。Python2 就跟 C 一样了。
// 整除 向下取整。5 // 2输出2(-5) // 2输出-3
% 取余数 跟 C 不同,浮点数也允许取余数。如-25.5 % 2.25输出 1.5
<< 左移 跟 C 一样可以按位操作。把数字化成二进制表示,然后按位运算。
集合的操作和大家数学课上学到的没有区别。> 按位操作还是交给汇编语言和 C 去干罢,这里就不具体讲了。

| | >> | 右移 | | | & | 集合与/按位与 | | | | | 集合或/按位或 | | | ^ | 集合亦或/按位异或 | | | ~ | 按位取反 | | | < | 小于 | 跟 C 不同,Python 有布尔型,所有比较运算符输出的结果为TrueFalse。若出现在表达式中,True的值是1,False的值是0,但它们并不是整型的1和0,而是布尔型。
跟 C 不同,Python 中多个比较运算符连接的表达式含义与数学相同。例如5>3>2在 Python 中就是字面意思,输出True;而 C 会先算最左边的5>3,输出1,化成1>2,最后整体的返回值为0
比大小时,两操作数都是数字,会被转化成同一类型再比,否则一直输出False
==!=可以比较字符串是否相同。比较运算符==不是赋值运算符=。 | | > | 大于 | | | <= | 小于等于 | | | >= | 大于等于 | | | == | 等于 | | | != | 不等于 | | | not | 布尔非 | x = True; not x,返回值为False。 | | and | 布尔与 | 跟 C 一样有短路计算的规则,当前面的操作数可以确定整个表达式的返回值时,后面的就不算了。例如x and y中,若xFalse,后面y的值就不计算了。 | | or | 布尔或 | | | in | 资格检测符 | i in s,当s中有i时返回True,没有时返回False
i not in s,当s中有i时返回False,没有时返回True。 | | is | 身份检测符 | i is jij指向同一存储空间时返回True,否则返回False
i is not jij指向同一存储空间时返回False,否则True。 |

Python 中无自增/自减运算符,因为 Python 是给常量而非变量分配内存空间的,变量自增/自减没意义。
跟 C 一样,运算符与=合并可产生新的赋值运算符:+=``-=``*=``/=……
这两种写法等价:a = a + b``a += b。其他形同+=的赋值运算符同理。

4.2 表达式

表达式就是用运算符把操作数连起来,得到用户想要的结果。
代码:

  1. length = 5
  2. breadth = 2
  3. area = length * breadth #三个声明语句,定义了三个变量,把长、宽、面积赋值给它们。
  4. #长×宽的表达式输出的是面积,赋值给储存面积用的变量area
  5. print('Area is', area) #print()这个参数间自动加空格的功能主要是为了打印英文方便
  6. print('Perimeter is', 2 * (length + breadth))

输出:

  1. Area is 10
  2. Perimeter is 14

4.3 优先级

跟 C 一样,Python运算有优先级。在这张表中,越往上,优先级越高,在表达式中越优先与操作数结合并运算。

运算符 描述
(expressions…), [expressions…], {key: value…}, {expressions…} 显示绑定或数组、显示列表、显示字典、显示设置
x[index], x[index:index], x(arguments…), x.attribute 下标、切片、调用、属性引用
** 乘方
+x, -x, ~x 正、负、按位取反
*, @, /, //, % 乘法,矩阵乘法,除法,整除,取模
+, - 加减
<<,>> 移位
& 按位与
^ 按位异或
| 按位或
in, not in, is, is not, <, <=, >, >=, !=, == 比较,包括成员资格测试和身份测试
not 布尔非
and 布尔和
or 布尔或
if – else 条件表达式
lambda Lambda 表达式
= 赋值表达式

建议在表达式中合理使用( ),因为经常有人记不住这张表,用( )可以明确计算优先级。例如(a and b) or (c and d)的写法就比a and b or c and d好,虽然它们等效。
此外,( )也可以改变计算顺序。例如a and b or c and da and (b or c) and d不一样。

4.4 结合性

和 C 一样,运算符一般从左往右结合,即相同优先级的情况下表达式从左往右计算。例如2 + 3 - 6中,+-优先级相同,从左往右先算2 + 3输出5,再算5 - 6输出-1

五、控制流

之前的程序都是顺序结构,如果要实现分支结构、循环结构,就要用Python中的三种控制流语句——if、for 和 while。

5.1 if 语句

实现分支结构。
一般形式:

  1. if 条件1: #跟C类似,不过这里条件返回布尔型,条件后用英文冒号表示逻辑行结束,且条件不必放括号里
  2. 代码块1 #叫做if块,注意前面有缩进
  3. elif 条件2: #elif可有可无,可一个可多个
  4. 代码块2
  5. else: #else放最后,可有可无,不能有多个
  6. 代码块3 #叫做else块

if 语句内部也可以设置 if 语句,称为嵌套的 if 语句,有时可以用 elif 代替,简化程序并减少缩进量。
Python 中没有 C 的 switch 语句,因为完全可以用 if … elif … else 语句代替。

5.2 while 语句

实现循环结构。
一般形式:

  1. while 条件: #条件为True时不断执行代码块1,直到条件变为False
  2. 代码块1
  3. else: #条件变False后执行,可有可无。只有使用break语句强行结束while循环时才不会进入else块
  4. 代码块2 #C中while语句没有else

5.3 for … in 语句

也实现循环结构。其特点是会在一系列对象上进行迭代(原文iterate,也许应取“重复做”的意思?),意即它会遍历序列中的每一个项目。
一般形式:

  1. for 变量 in 序列: #循环时按顺序遍历序列中的元素
  2. 代码块1
  3. else: #for...in语句也可以有else,只有使用break语句结束循环时才不会进入else块
  4. 代码块2

5.4 break 语句

一个 break 自成一语句。可用在上述两种循环语句中,让循环强行结束,即使条件没有变 False,序列中的元素也没遍历完。用了 break 语句之后 else 块不会被执行。

5.5 continue 语句

一个 continue 自成一语句。用在上述两种循环语句中,跳过当前循环块中的剩余语句,并继续该循环的下一次迭代。和 break 语句的区别在于它只结束本次迭代,不是让整个循环直接结束。

六、函数

函数是指可重复使用的程序片段。Python 允许用户为某个代码块赋予名字,通过这一特殊的名字在程序任何地方来运行代码块,并可重复使用,即所谓调用函数。 Python 内置了一些包括 print( ) 函数在内的常用函数,用户也可以自己定义函数。

6.1 自定义函数的基本用法

一般形式:

  1. def 函数名(参数1,参数2): #用关键字def定义函数,写出函数名、参数表和函数的代码块
  2. 代码块
  3. 函数名(参数3,参数4) #写函数名,括号里按顺序放入类型正确的变量,即可在程序的其他位置调用函数

定义函数时给定的函数运算要用的参数的名称,如上述参数1、参数2,称为形参;调用函数时提供给函数的参数,如上述参数3、参数4,称为实参。调用函数时,形参被定义并被赋值为对应的实参的值,在自定义的函数中运算。函数要用的参数可以有任意多个,也可以没有,不过放参数用的括号不可省略。
虽然 Python 不像 C 一样有主函数的概念,但 Python 程序也是忽略掉函数定义的部分再按顺序运行的。自定义函数的代码块只有在函数被调用的时候才运行。

6.2 作用域

作用域就是一个名字可用的代码范围,函数中声明的变量的作用域就是这个函数。也就是说,函数中声明的变量是局部变量,在定义了它的代码块之外不能使用它。如果在其他地方定义了与它同名的变量也不会和它互相影响,在定义它的函数中用这个共同的名字只能使用它,在其他地方用这个共同的名字就只能使用和它同名的另一个变量。
与局部变量相对的是全局变量,它是定义在程序顶层的变量。在自定义函数中,如果没通过函数参数表定义同名变量,就可以用 global 语句声明要使用叫这个名字的全局变量,从而在函数内使用函数外定义的变量,且可以对函数外的变量的值产生影响,打破了局部变量的作用域限制。

其实不用 global 语句也可以直接用函数外的变量,但这样写对代码的读者不友好,不建议这么写。

代码1:

  1. x = 50 #定义全局变量x
  2. def func(x): #自定义函数func(),定义了形参x #推荐把函数定义和整个程序的主体分开写
  3. print('x is', x) #这里的x不是全局变量x,而是函数中另外定义的形参x
  4. x = 2 #给形参x赋值。形参是函数内部定义的,也是局部变量,所以这次赋值不影响全局变量x
  5. print('Changed local x to', x)
  6. func(x) #调用函数。在函数定义的代码块之外,输入的参数是全局变量x
  7. print('x is still', x) #操作一个函数中的局部变量,不会影响到其他地方同名的其他变量

输出1:

  1. x is 50
  2. Changed local x to 2
  3. x is still 50

代码2:

  1. x = 50
  2. def func():
  3. global x #声明要在函数中使用函数外的全局变量x。想声明多个全局变量,可以写global x,y,z
  4. print('x is', x)
  5. x = 2
  6. print('Changed global x to', x) #在函数中操作全局变量x,当然会对全局变量x产生影响
  7. func()
  8. print('Value of x is', x) #显然这里用的x是全局变量x

输出2:

  1. x is 50
  2. Changed global x to 2
  3. Value of x is 2

6.3 默认参数值

自定义函数时,可以给形参设定默认参数值,这样运行程序时如果没有给出对应的实参,这个形参的值就会被定为默认参数值。函数的参数列表中,有默认参数值的形参应写在其他形参后面。
代码:

  1. def say(message, times=1):
  2. print(message * times)
  3. say('Hello') #没输入形参times对应的实参,times的值默认为1
  4. say('World', 5) #容易看出,要求设定了默认值的形参放后面,是为了方便把实参一一对应到形参

输出:

  1. Hello
  2. WorldWorldWorldWorldWorld

6.4 关键字参数

调用函数时,可以通过关键字而非实参的顺序来给出各个形参的值,称为关键字参数。这样一来调用函数的时候不用考虑参数的顺序,二来可以只对部分参数赋值而让其他参数保持默认参数值,更方便。
代码:

  1. def func(a, b=5, c=10): #b和c有默认参数值
  2. print('a is', a, 'and b is', b, 'and c is', c)
  3. func(3, 7) #以前都是这样通过位置来给出参数的值
  4. func(25, c=24) #先通过位置给a赋值,然后通过关键字跳过b给c赋值,b用的就是默认参数值
  5. func(c=50, a=100) #可以不管函数定义时形参的位置顺序。因为不是按顺序来的,所以“a=”不能省

输出:

  1. a is 3 and b is 7 and c is 10
  2. a is 25 and b is 5 and c is 24
  3. a is 100 and b is 5 and c is 50

6.5 可变参数

在定义函数并给出形参时,我们可以通过星号来让函数拥有可变数量的参数。
当我们在参数表中声明一个形如*param星号参数时,从此处开始直到结束的所有位置参数都将被收集并汇集成一个称为“param”的元组。 类似地,当我们声明一个形如**param双星号参数时,从此处开始直至结束的所有关键字参数都将被收集并汇集成一个名为“param”的字典。
代码:

  1. def total(a=5, *numbers, **phonebook):
  2. print('a', a)
  3. for single_item in numbers: #遍历元组中的所有项目
  4. print('single_item', single_item)
  5. for first_part, second_part in phonebook.items(): #遍历字典中的所有项目
  6. print(first_part,second_part)
  7. print(total(10,1,2,3,Jack=1123,John=2231,Inge=1560)) #函数是可以嵌套函数的
  8. #10传递给a,1、2、3构成元组储存在numbers中,Jack、John、Inge是键,1123、2231、1560是对应的值

输出:

  1. a 10
  2. single_item 1
  3. single_item 2
  4. single_item 3
  5. Inge 1560
  6. John 2231
  7. Jack 1123
  8. None #不明白为什么,但是run了一下程序,就是会有这个None

6.6 return 语句

用于结束函数调用并返回一个值。
return x表示返回变量x的值。一个不带其他表达式的return返回值是None,是一个特殊的类型,用来表示一个变量没有值。没有 return 语句的函数的返回值也是None
代码:

  1. def maximum(x, y):
  2. if x > y:
  3. return x
  4. elif x == y:
  5. return '两数相等'
  6. else:
  7. return y
  8. print(maximum(2, 3)) #其实Python自带max()函数,不用自己定义

输出:

  1. 3

6.7 函数的文档字符串

Python 中有文档字符串(DocStrings)功能,适用于函数、模块、类。给函数加上文档字符串即给函数加注释。看函数的文档字符串,可以用help( ) 命令,也可以用 print( ) 函数打印函数的__doc__属性。
代码:

  1. def print_max(x, y):
  2. '''打印两个数值中的最大数。
  3. 这两个数都应该是整数。'''
  4. #约定俗成,文档字符串写在函数最前面,第一行概述功能,第二行空,第三行注明细节
  5. x = int(x)
  6. y = int(y)
  7. if x > y:
  8. print(x, 'is maximum')
  9. else:
  10. print(y, 'is maximum')
  11. print_max(3, 5) #正常调用函数的时候不会出现DocStrings
  12. print(print_max.__doc__) #Python中函数也是对象,可以用.来访问它名叫__doc__的属性
  13. help(print_max) #跟上面那行几乎等效,但help()有更好看的固定格式,且可以按Q退出帮助

输出:

  1. 5 is maximum
  2. 打印两个数值中的最大数。
  3. 这两个数都应该是整数。
  4. Help on function print_max in module __main__:
  5. print_max(x, y)
  6. 打印两个数值中的最大数。
  7. 这两个数都应该是整数。

七、模块

上一章的函数只能在重用在本程序中定义的函数,而模块可以把定义的函数打包,在其他程序中重用。事实上,每一个以 .py 为扩展名的 python 程序都同时是一个模块。

7.1 标准库模块

一些常用函数被打包成模块内置在 Python 中,用户通过 import 语句导入这些模块,即可调用这些函数。
代码:

  1. import math #用import语句导入math库。math库就是一个内置的标准库模块
  2. a = 5; b = 12; c = 13
  3. costheta = (math.pow(a,2) + math.pow(b,2) - math.pow(c,2))/(2 * a * b) #余弦定理
  4. theta = math.acos(costheta) #调用模块中的函数,需要在.前面声明是哪个模块的函数
  5. print('The theta is',theta) #print()这种自带的函数就不用声明模块

输出:

  1. The theta is 1.5707963267948966

为了避免每次使用一个常用的库函数都要声明是哪个模块,可以用 from … import … 语句导入模块中的单个常用函数。但是不建议这么写,因为容易导致出现不同模块的同名函数冲突。
代码:

  1. import math
  2. from math import pow #用from...import...语句专门导入pow()函数,以后调用它就不用声明math库
  3. a = 5; b = 12; c = 13
  4. costheta = (pow(a,2) + pow(b,2) - pow(c,2))/(2 * a * b) #这样余弦定理就短了,更易读
  5. #当然,真想让余弦定理写起来简单,完全可以用python自带的**代替pow()这种C语言玩家的恶趣味
  6. theta = math.acos(costheta) #调用模块中的其他函数,还需要声明math库
  7. print('The theta is',theta)

输出:

  1. The theta is 1.5707963267948966

7.2 自定义模块

跟编写一般的程序一样。将要调用的函数定义在一个 python 程序(模块)中,保存为以 .py 为扩展名的文件,放在要导入此模块的程序所在的文件夹中或 sys.path 所列出的一个路径中。然后就能用 import 语句导入模块了。
以下代码保存为 mymodule.py:

  1. def say_hi(): #在模块中定义的函数可以调用
  2. print('Hi, this is mymodule speaking.')
  3. __version__ = '0.1' #在模块中定义的变量也可以使用

以下代码保存为 mymodule_demo.py 并运行:

  1. import mymodule
  2. mymodule.say_hi()
  3. print('Version', mymodule.__version__)

输出:

  1. Hi, this is mymodule speaking.
  2. Version 0.1

7.3 dir( ) 函数

Python 内置的一个函数,能返回由括号中的对象所定义的对象的名称列表。如果提供模块名称作为参数,它会返回这一指定模块的名称列表;如果不提供参数,就会返回当前模块的名称列表。
在交互式解释器中做如下操作:

  1. >>> import sys #给出sys模块中的属性名称
  2. >>> dir(sys)
  3. ['__displayhook__', '__doc__', 'argv', 'builtin_module_names', 'version', 'version_info']
  4. #不代表真的只有这些属性,只是列举几个,让读者感受一下dir()的用法
  5. >>> dir() #给出当前模块的属性名称
  6. ['__builtins__', '__doc__', '__name__', '__package__']
  7. >>> a = 5 #创建一个新的变量 'a'
  8. >>> dir()
  9. ['__builtins__', '__doc__', '__name__', '__package__', 'a']
  10. >>> del a #del语句可以删除一个名称
  11. >>> dir()
  12. ['__builtins__', '__doc__', '__name__', '__package__']

7.4 包

包是指一个包含模块与一个特殊的“init.py”文件的文件夹,后者向 python 表明这一文件夹包含了 Python 模块。包的作用是组织起模块的分层结构。

八、数据结构

Python 内置了四种数据结构——列表、元组、字典、集合。它们可以储存一系列相关数据的集合。

8.1 列表 List

8.1.1 列表简介

将元素列在[ ]中用逗号分隔的有序、可变序列。不要求元素的数据类型相同。
列表作为一个类,有 list 类特殊的方法。字符串是不可变的,列表是可变的,意思是对字符串用方法只是返回操作后的字符串,原来的字符串没变,需要把操作后的字符串存到变量里才能保存操作后的字符串,而对列表用了方法之后列表本身就改变了,不会返回一个修改后的列表。
先大致感受一下:

  1. shoplist = ['apple', 'mango', 'carrot', 'banana'] #这是一张列表
  2. print('I have', len(shoplist), 'items to purchase.') #len()函数返回列表元素个数/字符串长度
  3. print('These items are:', end=' ')
  4. for item in shoplist: #for...in语句遍历序列中的所有元素,列表也是序列
  5. print(item, end=' ')
  6. print('\nI also have to buy rice.')
  7. shoplist.append('rice') #无需赋值,用了.append()方法就把元素加到列表后面了
  8. print('My shopping list is now', shoplist)
  9. print('I will sort my list now')
  10. shoplist.sort() #同样无需赋值,用了.sort()方法就按首字母排好了
  11. print('Sorted shopping list is', shoplist)
  12. print('The first item I will buy is', shoplist[0]) #跟C一样,元素计数从第0个开始
  13. olditem = shoplist[0]
  14. del shoplist[0] #del语句可以指定删除列表中的某一项
  15. print('I bought the', olditem)
  16. print('My shopping list is now', shoplist)

输出:

  1. I have 4 items to purchase.
  2. These items are: apple mango carrot banana
  3. I also have to buy rice.
  4. My shopping list is now ['apple', 'mango', 'carrot', 'banana', 'rice']
  5. I will sort my list now
  6. Sorted shopping list is ['apple', 'banana', 'carrot', 'mango', 'rice']
  7. The first item I will buy is apple
  8. I bought the apple
  9. My shopping list is now ['banana', 'carrot', 'mango', 'rice']

8.1.2 访问列表元素

一般形式:列表名[索引]
代表取列表中的某一个元素。对它赋值即可修改列表中的这个元素。
这里的索引往往取从0开始的自然数,就很像 C 中的访问数组元素,表示从列表最左边那个元素开始第0、第1、第2……往后数到第索引个元素。索引也可以是负数,表示倒数第几个元素,如列表名[-2]表示倒数第二个元素。-len(列表名)≤索引≤len(列表名)-1,否则会报错。

8.1.3 追加元素

一般形式:列表名.append(元素)
把新元素追加到列表最后面。

8.1.4 插入元素

一般形式:列表名.insert(索引,元素)
把新元素插入到列表的第索引个位置。即把列表中第索引个元素和它后面的元素都往后挪一位,空出来的位置放新元素。

8.1.5 删除元素

8.1.5.1 del 语句

一般形式:del 列表名[索引1], 列表名[索引2]
可以一次删除一个或多个元素。被删除的元素无法再访问。

8.1.5.2 .pop( ) 方法

没有参数时,弹出(删除)列表中的最后一个元素,并以最后一个元素为返回值以便之后访问;以索引为参数时,弹出第索引个元素并以之为返回值。
代码:

  1. wcu = ['清华','北大','复旦','交大','浙大'] #世界一流大学列表
  2. print('中国的世界一流大学有',wcu) #打印列表时[]也会被打印出来
  3. #不想要[]就用for...in遍历列表元素并打印,见上面shoplist的代码
  4. downgrade_1 = wcu.pop() #没有参数,就把排名最后的世一大除名
  5. print('第一次被降级的大学是',downgrade_1,'。')
  6. #像这样用“,”分隔打印会被自动补充空格,不要空格的话可以用+连接字符串
  7. print('现在,中国的世界一流大学有',wcu)
  8. downgrade_2 = wcu.pop(2) #给定索引作为参数,把某原世一大下的旦也除名了
  9. print('第二次被降级的大学是',downgrade_2,'。')
  10. print('现在,中国的世界一流大学有',wcu)
  11. wcu.insert(2,'港大') #深入贯彻一国两制政策,香港经济大繁荣,高等教育也得到发展
  12. #.insert()等方法是没有返回值的。如果试图像前面.pop()一样保存返回值并打印,打印出的是None
  13. print('被升级为世一大的大学是',wcu[2],'。')
  14. print('现在,中国的世界一流大学有',wcu)

输出:

  1. 中国的世界一流大学有 ['清华', '北大', '复旦', '交大', '浙大']
  2. 第一次被降级的大学是 浙大
  3. 现在,中国的世界一流大学有 ['清华', '北大', '复旦', '交大']
  4. 第二次被降级的大学是 复旦
  5. 现在,中国的世界一流大学有 ['清华', '北大', '交大']
  6. 被升级为世一大的大学是 港大
  7. 现在,中国的世界一流大学有 ['清华', '北大', '港大', '交大']

8.1.5.3 .remove( ) 方法

在列表中找到第一个值与参数相同的元素,并将其移除。
要删掉列表中所有与参数的值相同的元素,就用循环语句遍历列表。

8.1.6 给元素排序

8.1.6.1 .sort( ) 方法

把列表元素调整成(按照 ASCII 码大小,从第一个字母开始)升序排列。可给参数reverse = True,改为降序排列;不给参数时默认reverse = False

8.1.6.2 sorted( ) 函数

可以让列表中的元素(按照 ASCII)临时升序排列,和 .sort( ) 方法的区别是本函数不会改变列表本身的排序。同样,要将列表临时按倒序排列,可以按关键字给函数传递参数reverse = True
代码:

  1. vocaloids = ['Hatsune Miku','Kagamine Rin','Kagamine Len']
  2. print('原来的列表:', vocaloids)
  3. print('用sorted( )函数后:', sorted(vocaloids))
  4. print('原来的列表还在:', vocaloids)
  5. print('用sorted( )函数倒序排列:', sorted(vocaloids, reverse = True))

输出:

  1. 原来的列表: ['Hatsune Miku', 'Kagamine Rin', 'Kagamine Len']
  2. sorted( )函数后: ['Hatsune Miku', 'Kagamine Len', 'Kagamine Rin']
  3. 原来的列表还在: ['Hatsune Miku', 'Kagamine Rin', 'Kagamine Len']
  4. sorted( )函数倒序排列: ['Kagamine Rin', 'Kagamine Len', 'Hatsune Miku']

8.1.6.3 .reverse( ) 方法

把列表元素的顺序调整成与原来相反。
再次使用 .reverse( ) 方法即可恢复原顺序,不像 .sort( ) 方法不可撤销。

8.1.7 数字列表

8.1.7.1 range( ) 函数与 list( ) 函数

一般形式:range(首项, 末项, 步长)
一般用在循环语句中。同一逻辑行中的 range( ) 函数被调用第 n 次(从n=1开始计数)时,返回值为首项+(n-1)*步长,直到首项+(n-1)*步长 ≥ 末项时不再返回值。只有两个参数时认为步长参数没有,默认步长为1;只有一个参数时认为参数是末项,默认首项为0,步长为1。
代码:

  1. for value in range(1,5): #末项参数为5,就刚好不会打印到5。步长参数没有就默认为1
  2. print(value,end=' ')

输出:

  1. 1 2 3 4

list( ) 函数是强制类型转换函数之一。可以用 list( ) 函数套住 range( ) 函数,生成数字列表。
代码:

  1. numlist = list(range(1,6,2)) #末项参数为6,所以6及6以上的数都不会进入列表
  2. print(numlist)

输出:

  1. [1, 3, 5]

8.1.7.2 用于简单统计计算的函数

这些函数对于有成千上万个数据的列表也适用。
一般形式:max(列表名) 可以返回数字列表中的最大数字。
一般形式:min(列表名) 可以返回数字列表中的最小数字。
一般形式:sum(列表名) 可以返回数字列表所有元素的和,配合 len( ) 函数可求算数平均值。

8.1.7.3 列表解析

一般形式:[表达式1 for 参数 in 序列 if 表达式2]
用 for … in 语句遍历序列,如果表达式2(不想筛选的话也可以没有这个 if 语句)返回值为True,就把序列中的元素作为参数带入表达式1,得到列表的数值元素。类似数学中用描述法表示一个有序集合。
代码:

  1. squares = [value**2 for value in range(1,11) if value % 2 == 0]
  2. print(squares) #输出的是包含了从1到10的所有偶数的平方的序列

8.1.8 列表的切片

一般形式:列表[索引1:索引2:步长]
表示原列表中,从索引1开始按步长往后取,满足索引1 ≤ 索引 < 索引2(注意索引2是小于号,不能取等,最多取到前一个元素!)的所有元素构成的子列表。索引1没有时表示从头开始取,索引2没有时表示一直取到原列表的末尾。步长和第二个:也可以同时没有,此时默认步长为1,从左往右一个一个元素地取。索引1、索引2可以同时取负数,表示原列表的倒数第几个。
代码:

  1. print([0,1,2,3,4,5,6,7,8,9][0:5:2])
  2. #左边的[]里是列表,右边的[]表示切片
  3. #从第0个元素开始取,最多取到第(5-1)个元素,步长为2。所以就是第0个、第2个、第4个。

输出:

  1. [0, 2, 4]

8.1.9 列表的复制

要复制列表,在新变量中存储的必须是一个包含整个原列表的切片,即新变量 = 原列表名[:]
直接把原列表赋值给新变量,即新变量 = 原列表名,是不对的。否则新变量只是给原列表起了个新名字,指向的仍然是内存中的原来那个列表(可以用is验证),对新老变量分别的操作会同时对新老变量两者生效。

复制除列表外的其他序列时没有这个问题,就列表事多。

8.2 元组

8.2.1 元组简介

几乎所有特性都和列表一样,也可以用在 for … in 语句中被遍历。但由( )套住,且像字符串一样不可修改,一定要修改就只能重新定义整个元组。
代码:

  1. wcu = ('清华','北大')
  2. print('原有世一大',len(wcu),'所,分别是',wcu) #len()也可以返回元组元素个数
  3. new_wcu = wcu,'复旦','交大','浙大' #不引发歧义时,元组可无括号,但不建议这样写,因为不明了
  4. print('现有世一大',len(new_wcu)-1+len(wcu),'所,而不是',len(new_wcu),'所')
  5. #打印时,()是不会被省略的 #注意↑元组套元组时元素的计数规则
  6. print('它们分别是',new_wcu) #此时wcu本身是new_wcu中的第一个元素,python不会把wcu拆成两个元素
  7. print('原有世一大中最差的是',new_wcu[0][1]) #元组也可以用索引访问
  8. #访问元组中的元组要两个索引,表示new_wcu的第0个元素(wcu)中的第1个元素
  9. print('补录世一大中最强的是',new_wcu[1])

输出:

  1. 原有世一大 2 所,分别是 ('清华', '北大')
  2. 现有世一大 5 所,而不是 4
  3. 它们分别是 (('清华', '北大'), '复旦', '交大', '浙大')
  4. 原有世一大中最差的是 北大
  5. 补录世一大中最强的是 复旦

要表示有0个元素的元组时,括号不能省,即()。要表示有1个元素的元组时,要加个逗号,否则 python 会不清楚这是元组还是带括号运算符的量,如(5,)

8.2.2 函数返回元组

需要函数返回多个值时,可以把多个值打包成元组并返回,形如return (a,b,c...)
代码:

  1. def some_func():
  2. return 0,1 #不引发歧义时,元组可以没有括号
  3. #这并不代表return返回了多个值。用print打印返回值就可以看见被省略的括号,证明返回的是一个元组
  4. a,b=some_func() #用元组给多个变量赋值,变量得到的值就是元组中的元素
  5. print('返回元组{},a={},b={}'.format(some_func(),a,b))

输出:

  1. 返回元组(0, 1),a=0b=1

8.2.3 用元组交换变量的值

Python 中交换变量的值不需要像 C 那样定义临时变量,直接把变量原来的值临时存储在元组中,再用元组给一组变量赋值即可。
代码:

  1. a = 0; b = 1; c = 2
  2. b,c,a = a,b,c #元组可以没括号。等号右边是元组(0,1,2),,三个元素的值依次赋给了b,c,a三个变量
  3. print('此时a={},b={},c={}'.format(a,b,c)) #这样三个变量的值就交换好了

输出:

  1. 此时a=2b=0c=1

8.3 序列

被排成一列的有序对象,以上提到的列表、元组、字符串都是序列。主要功能是资格检测(in、not in 表达式)和索引操作(下标、切片操作)。

虽然 python 没有 C 中的 char 类型,但是别忘了字符串也是由字符构成的序列。 字符串也可以用 in 运算符,也可以用索引。

8.4 字典

8.4.1 字典简介

罗列在{ }中的无序的键-值对集合(数学意义上的集合)。和数据库中一样,每个必须不同,以便用键访问。字典中,键在左,值在右,对应的键、值之间用:连接,键-值对之间用,分割。值可以是python中的任何对象,但键必须是常量。

8.4.2 字典的基本操作

8.4.2.1 访问字典元素

同样是用索引,但索引不是整数,而是键。

8.4.2.2 添加或修改元素

一般形式:字典名[键] = 值
字典中没有这个键时,创建这个键,并将值设为等号右边的值;已有这个键时,修改值为等号右边的值。

8.4.2.3 删除字典元素

一般形式:del 字典名[键]
依然是用 del 语句,删除完之后键和其关联的值都无法再访问。
代码:

  1. s_2021 = {'清华':695,'北大':696,'复旦':686,'交大':682} #2021年浙江高考投档线字典
  2. print('清华大学的投档线是'+str(s_2021['清华'])+'分。')
  3. #用“+”连接字符串可以防止print()自动加空格,但要把整数的类型转换成字符串才能用“+”连接
  4. s_2021['北大'] = 690 #发现忘了算上医学部,修改北大的投档线数据
  5. s_2021['浙大'] = 647 #添加浙大的投档线数据
  6. print('经完善,2021年浙江考生考世一大的投档线为:\n', s_2021)
  7. #用“,”分隔就不像用“+”连接字符串一样会有数据类型的问题,什么都能打印
  8. del s_2021['浙大'], s_2021['清华']
  9. print('去除一个最低分和一个最高分:\n', s_2021)

输出:

  1. 清华大学的投档线是695分。
  2. 经完善,2021年浙江考生考世一大的投档线为:
  3. {'清华': 695, '北大': 690, '复旦': 686, '交大': 682, '浙大': 647}
  4. 去除一个最低分和一个最高分:
  5. {'北大': 690, '复旦': 686, '交大': 682}

8.4.3 字典元素的基本组织方式

8.4.3.1 记录一个对象的多个不同属性

例:

  1. hatsune_miku = { #记录初音未来的多个属性
  2. 'name':'初音ミク',
  3. 'gender':0,
  4. 'age':16,
  5. 'height':158,
  6. 'weight':42,
  7. 'language':['Japanese','English','Chinese'], #值可以是任何python对象,包括列表
  8. } #约定俗成写成这种一行一个的格式。最后一个元素结尾的“,”在语法上可以没有,但约定俗成会写上

8.4.3.2 记录多个对象的同一属性

例:

  1. gaokao_declaration = {
  2. '小明':'清华北大在我脚下,二中学霸名扬天下!',
  3. '小丽':'学海行舟,将达彼岸;秣马厉兵,竞相折桂夺冠。',
  4. '小红':'复旦复旦旦复旦,浙罗交网无羁绊。',
  5. '小白':'只要上的不是省内985就行。',
  6. '小刚':'我好想做北京大学的狗啊。',
  7. }

8.4.4 遍历字典

8.4.4.1 用 .items( ) 方法遍历键-值对

一般形式:for 变量1,变量2 in 字典名.items()
.item( ) 方法返回一个键-值对列表,变量1、变量2分别存储每次循环的键、值,这样就可以在 for … in 语句的代码块中使用此次循环的键、值。
如果没有变量2,那么变量1存储的就是用( )套住的键-值对。
代码:

  1. gaokao_declaration = {
  2. '小明':'清华北大在我脚下,二中学霸名扬天下!',
  3. '小丽':'学海行舟,将达彼岸;秣马厉兵,竞相折桂夺冠。',
  4. '小红':'复旦复旦旦复旦,浙罗交网无羁绊。',
  5. '小白':'只要上的不是省内985就行。',
  6. '小刚':'我好想做北京大学的狗啊。',
  7. }
  8. for name,declaration in gaokao_declaration.items():
  9. print(name+'同学的高考宣言是:'+declaration)

输出:

  1. 小明同学的高考宣言是:清华北大在我脚下,二中学霸名扬天下!
  2. 小丽同学的高考宣言是:学海行舟,将达彼岸;秣马厉兵,竞相折桂夺冠。
  3. 小红同学的高考宣言是:复旦复旦旦复旦,浙罗交网无羁绊。
  4. 小白同学的高考宣言是:只要上的不是省内985就行。
  5. 小刚同学的高考宣言是:我好想做北京大学的狗啊。

8.4.4.2 用 .keys( ) 方法遍历键

一般形式:for 变量 in 字典名.keys()
其中 .keys( ) 方法返回一个键列表。

8.4.4.3 用 .values( ) 方法遍历值

一般形式:for 变量 in 字典名.values()
其中 .values( ) 方法返回一个值列表。值是可以重复的,所以这个列表中可能有重复项,可以用 set( ) 函数强制类型转换成集合解决这个问题。

8.5 字典与列表的嵌套

8.5.1 字典列表

把结构化信息存储在一个个字典中,再把字典组织成列表。
代码:

  1. user_1 = {'username':'Mary','password':'123456'}
  2. user_2 = {'username':'David','password':'123456789'}
  3. user_3 = {'username':'Chan','password':'Cxq1#996Wyh'}
  4. userlist = [user_1, user_2, user_3] #这就是包含了每个用户的用户名、密码的字典列表
  5. for user in userlist:
  6. print(user)
  7. user_4 = {'username':'Nakamura','password':'pasuwaado_wa_123456_desu'}
  8. userlist.append(user_4)
  9. print(userlist[3])

输出:

  1. {'username': 'Mary', 'password': '123456'}
  2. {'username': 'David', 'password': '123456789'}
  3. {'username': 'Chan', 'password': 'Cxq1#996Wyh'}
  4. {'username': 'Nakamura', 'password': 'pasuwaado_wa_123456_desu'}

没什么难的,但是这个功能很有用,所以还是放了个例子。

8.5.2 在字典中储存列表/字典

需要把一个键关联到多个相关信息,就把这些信息写成列表/字典,作为键对应的值。字典中存储列表的情况这里已经出现过了,字典中存储字典的情况也类似。
对于字典套列表的情况,可以用嵌套的 for … in 语句遍历每个列表元素。外层 for … in 遍历字典,内层 for … in 遍历列表。
而对于字典套字典的情况,可以用 for … in 语句遍历外层字典,再用索引访问内层字典的元素。
代码:

  1. users = {
  2. 'aeinstein': { 'first': 'albert',
  3. 'last': 'einstein',
  4. 'location': 'princeton',
  5. },
  6. 'mcurie': { 'first': 'marie',
  7. 'last': 'curie',
  8. 'location': 'paris', #最后一个元素后面的,可有可无,但是为了对称还是会写上
  9. },
  10. }
  11. for username, user_info in users.items():
  12. print("\nUsername: " + username)
  13. full_name = user_info['first'] + " " + user_info['last']
  14. location = user_info['location']
  15. print("\tFull name: " + full_name.title())
  16. print("\tLocation: " + location.title())

输出:

  1. Username: aeinstein
  2. Full name: Albert Einstein
  3. Location: Princeton
  4. Username: mcurie
  5. Full name: Marie Curie
  6. Location: Paris

8.6 集合

放在{ }中的简单元素集合,和数学上的集合一样,无序,没有重复元素。

8.6.1 创建集合

可以在{ }中用,分隔元素创建集合。若有重复元素,会自动消除多余元素,而不会报错。
可以用解析的方法创建集合。
也可以用 set( ) 函数创建集合。

8.6.2 set( ) 函数

是强制类型转换函数,可以把一个作为参数的序列的数据类型改成集合。把序列作为参数就可以消重,因为集合中不能有重复元素。同时,不带参数的 set( ) 函数也是唯一创建空集的方法,因为{}代表空字典,而不是空集。

8.6.3 集合的基本运算

运算符 & | - ^ in / not in
意义 数学上的∩ 数学上的∪ 数学上的差集 两个集合中不同时包含于两个集合的元素的集合 资格检测符

例:

  1. >>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
  2. >>> print(basket) #自动消重
  3. {'orange', 'banana', 'pear', 'apple'}
  4. >>> 'orange' in basket #判断元素是否在集合内
  5. True
  6. >>> 'crabgrass' in basket
  7. False
  8. >>> a = set('abracadabra')
  9. >>> b = set('alacazam')
  10. >>> a
  11. {'a', 'r', 'b', 'c', 'd'}
  12. >>> a - b #集合a中包含而集合b中不包含的元素
  13. {'r', 'd', 'b'}
  14. >>> a | b #集合a或b中包含的所有元素
  15. {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
  16. >>> a & b #集合a和b中都包含了的元素
  17. {'a', 'c'}
  18. >>> a ^ b #不同时包含于a和b的元素
  19. {'r', 'd', 'b', 'm', 'z', 'l'}
  20. >>> c = {x for x in 'abracadabra' if x not in 'abc'}
  21. >>> c #解析法创建集合
  22. {'r', 'd'}

8.6.4 集合的基本方法

方法 .add( ) .remove( ) .discard( ) .pop( ) .clear( )
参数 一个 一个 一个
效果 添加元素 删除元素,找不到该元素会报错,无返回值 删除元素,找不到该元素不报错,无返回值 随机删除一个元素并作为返回值 清空集合

九、面向对象编程

围绕函数设计程序来处理数据,称为面向过程的编程方式。将数据与功能组合并包装在对象中,称为面向对象的编程方式。比起在 C 中所学的面向过程编程,面向对象编程更适合编写大型程序等情况。

9.1 基本概念

对象是面向对象编程的两个主要方面。类代表一种数据类型 ,其中对象就是类的实例。例如一个 int 类型的变量就是 int 类的对象。类和对象可以使用属于它的普通变量来存储数据,这种从属于对象或类的变量叫作字段。对象还可以使用属于类的函数来实现某些功能,这种属于类的函数就叫作类的方法,除了要用对象.方法名(参数表)的方式来调用之外,方法的使用方式和一般函数完全一样 。字段与方法通称类的属性。字段有两种类型,属于某一类的各个实例或对象的称为对象变量,从属于某一类本身的称为类变量

9.2 自定义类的基本使用方法

9.2.1 自定义类

类似自定义函数的格式,但开头是class,且定义该类的对象所需的参数列表不放在类名后的括号中,而是放在定义在类代码块中的 .init( ) 方法的参数列表中的self参数后面(好长一串定语,还是看下面的一般形式好理解)。在类的代码块中定义类的所有属性。
一般形式:

  1. class 类名(): #这里类名后的括号是空的,也可以不写括号。约定俗成,首字母大写的是类名
  2. def __init__(self,参数1,参数2): #self后面才是定义这个类的对象所需要的参数的列表
  3. self.属性1 = 参数1
  4. self.属性2 = 参数2
  5. def 方法名(self,参数3): #定义该类的方法
  6. 方法代码块
  7. 类变量名 = 表达式 #定义该类的类变量

9.2.1.1 self 参数

方法和普通的函数有一个区别,就是它们被定义时,参数列表的第一个参数是self。这个变量引用的是对象本身,不需要用户对它赋值,且即使是被调用时不需要用户给出任何参数的方法,在被定义时也有这个参数。

9.2.1.2 .init( ) 方法

创建类下的对象时,解释器会自动调用这个方法,把定义对象时给出的参数赋值给对象的对应属性,这样就突破了形参作为局部变量的限制,可以用对象名.属性名的格式,即属性引用,来访问属性,随时调用这些值了。如果定义的类不需要从外界输入参数就可以创建对象,也可以不定义该方法,不过这样的类没什么用。

9.2.2 创建类的对象

一般形式:变量名 = 类名(不含self的参数表)
形同赋值语句,和创建一般变量差不多。但因为这里的变量属于自定义的类,所以一定要说明数据类型(即说明属于哪个类),否则解释器只会当成属于 Python 内置的类来定义变量,然后导致出错。
其实强制类型转换函数就是用给定的参数创建了某个数据类型类中的对象。例如a = int(1.5),就是用参数1.5创建 int 类中的对象。
可以通过对象(在类代码块外时)或 self 参数(在类代码块中时)的 class 属性来引用对象的类。
代码:

  1. class Dog(): #类的一大应用是模拟现实中的一类物体。这里以模拟狗为例
  2. """简单模拟一下狗""" #类也有文档字符串,也可以用help(Dog)看到
  3. def __init__(self, name, age): #定义在类中的函数叫作方法,第一个参数是self
  4. """初始化属性name和age"""
  5. self.name = name #给对象创建字段name和age,把形参中name和age的值存储到其中
  6. self.age = age #(接上↑)这样就可以突破形参作为局部变量的限制,随时调用这两个值了
  7. def sit(self):
  8. """模拟狗被命令蹲下"""
  9. print(self.name.title() + " is now sitting.") #用.来访问字段name,再使用.title()方法
  10. def roll_over(self):
  11. """模拟狗被命令打滚"""
  12. print(self.name.title() + " rolled over!")
  13. my_dog = Dog('willie', 6) #创建一个Dog类中的对象,要给定两个参数
  14. print("My dog's name is " + my_dog.name.title() + ".") #在函数代码块之外用.也能访问字段
  15. print("My dog is", my_dog.age, "years old.")
  16. my_dog.sit() #调用.sit()方法
  17. my_dog.roll_over()

输出:

  1. My dog's name is Willie.
  2. My dog is 6 years old.
  3. Willie is now sitting.
  4. Willie rolled over!

9.3 类变量与对象变量

类变量直接定义在类代码块中,是类中的所有对象共享的,可以被该类的所有对象访问,通过一个对象修改类变量的影响会在所有同类的对象中体现。而对象变量定义在 .init( ) 方法的函数代码块中,是类中的每一个独立对象所分别拥有的变量,不会与其他不同对象的同名字段产生联系。
代码:

  1. # coding=UTF-8
  2. class Robot:
  3. """表示有一个带有名字的机器人。"""
  4. population = 0 #在类代码块中直接定义的是类变量。population用来计数机器人的数量
  5. def __init__(self, name):
  6. """初始化数据。"""
  7. self.name = name #在方法代码块中定义的是对象变量。name用来记录每个机器人的名字
  8. print("(Initializing {})".format(self.name)) #对象变量是对象的字段,前面要有“self.”
  9. Robot.population += 1 #类变量是类的字段,前面要有“Robot”。有机器人被创建时,数量加一
  10. def die(self):
  11. """机器人死了。"""
  12. print("{} is being destroyed!".format(self.name))
  13. Robot.population -= 1
  14. if Robot.population == 0:
  15. print("{} was the last one.".format(self.name))
  16. else:
  17. print("There are still {:d} robots working.".format( Robot.population))
  18. def say_hi(self):
  19. """来自机器人的诚挚问候。"""
  20. print("Greetings, my masters call me {}.".format(self.name))
  21. @classmethod #用装饰器将.how_many()定义为一个类方法
  22. def how_many(cls): #类方法的参数是类名,用cls表示
  23. """打印当前的机器人数量。"""
  24. print("We have {:d} robots.".format(cls.population))
  25. droid1 = Robot("R2-D2")
  26. droid1.say_hi()
  27. Robot.how_many()
  28. droid2 = Robot("C-3PO")
  29. droid2.say_hi()
  30. Robot.how_many()
  31. print("\nRobots can do some work here.\n")
  32. print("Robots have finished their work. So let's destroy them.")
  33. droid1.die()
  34. droid2.die()
  35. Robot.how_many() #类方法是对类名使用的

输出:

  1. (Initializing R2-D2)
  2. Greetings, my masters call me R2-D2.
  3. We have 1 robots.
  4. (Initializing C-3PO)
  5. Greetings, my masters call me C-3PO.
  6. We have 2 robots.
  7. Robots can do some work here.
  8. Robots have finished their work. So let's destroy them.
  9. R2-D2 is being destroyed!
  10. There are still 1 robots working.
  11. C-3PO is being destroyed!
  12. C-3PO was the last one.
  13. We have 0 robots.

9.4 继承

可以在类中定义类,外层的类称为父类,定义在父类中的称为子类。子类会拥有父类所有的属性,称为继承。同时,子类还可以定义自身独有的属性,可以视为父类的扩展版。
定义子类时,不会有比定义父类时多一层缩进。因此,定义子类时,类名后面要跟一个包含了父类名称的元组,这样解释器才知道定义的是哪个父类的子类。如果元组中有超过一个父类名称,则称为多重继承。子类属于多个父类,就可以获得多个父类的全部属性。
如果子类中有 .init( ) 方法,创建子类的对象时解释器会自动调用子类的 .init( ) 方法,而为了让子类正常运行,父类的 .init( ) 方法需要用户主动写代码要求解释器去调用。但如果子类没有 .init( ) 方法,就会自动调用父类的 .init( ) 方法。
代码:

  1. # coding=UTF-8
  2. class DaXueSheng: # 定义父类
  3. '''代表全体大学生。'''
  4. def __init__(self, name, gpa):
  5. self.name = name
  6. self.gpa = gpa
  7. print('入学一名新生: {}。'.format(self.name))
  8. def scoldschool(self):
  9. '''大学生骂学校。'''
  10. print('{}骂道:“野鸡学校!”'.format(self.name))
  11. def __del__(self): # 这就是所谓“析构函数”,主要负责在对象不再被使用时释放它们的内存。
  12. '''大学生自杀。'''
  13. print('{}跳楼了。'.format(self.name)) # 也可以让析构函数释放内存之前干些其他事情。
  14. class THUer(DaXueSheng): # 定义子类时,括号(元组)中写父类名DaXueSheng
  15. '''代表一个清华学生。'''
  16. def __init__(self, name, gpa):
  17. DaXueSheng.__init__(self, name, gpa)
  18. # 调用父类的.__init__()方法获取和父类一样的属性,就不用再说明子类的这些字段怎么定义了
  19. # 子类中已有自动调用的.__init__()方法,所以父类的.__init__()方法只能手动调用
  20. print('他是一位厚德载物的清华人。')
  21. def scoldschool(self):
  22. print('{}骂道:“傻逼华清!”'.format(self.name))
  23. # 子类可以定义自己独有且与父类方法同名的方法
  24. class ZJUer(DaXueSheng):
  25. '''代表一个浙大学生。'''
  26. def __init__(self, name, gpa, rage):
  27. DaXueSheng.__init__(self, name, gpa)
  28. self.rage = rage # 定义该子类对象独有的字段
  29. print('他是一位灿若星辰的浙大人。')
  30. def scoldschool(self):
  31. print('{}骂道:“垃圾三本!”'.format(self.name))
  32. def complain(self):
  33. if (self.rage == True) and (self.gpa <= 3.3):
  34. print('{}抱怨道:“日,我被正态了。”'.format(self.name))
  35. def __del__(self):
  36. print('{}沉到了启真湖底。'.format(self.name))
  37. d = DaXueSheng('李宝妍', 3.98) #定义只属于父类的对象
  38. t = THUer('张满继', 4.00) # 定义子类的对象,给出对应的参数
  39. z = ZJUer('王国绛', 3.30, True)
  40. print() # 默认打印\n,即换行
  41. print('(考试周后...)')
  42. z.complain()
  43. for member in [d, t, z]: # 对全体大学生工作
  44. member.scoldschool()
  45. member.__del__()
  46. # 子类中有定义.tell()方法,就调用子类的,即使两个子类的.tell()方法的内容不同,只是同名而已
  47. # 子类中没有则在父类中寻找同名方法调用

输出:

  1. 入学一名新生: 李宝妍。
  2. 入学一名新生: 张满继。
  3. 他是一位厚德载物的清华人。
  4. 入学一名新生: 王国绛。
  5. 他是一位灿若星辰的浙大人。
  6. (考试周后...)
  7. 王国绛抱怨道:“日,我被正态了。”
  8. 李宝妍骂道:“野鸡学校!”
  9. 李宝妍跳楼了。
  10. 张满继骂道:“傻逼华清!”
  11. 张满继跳楼了。
  12. 王国绛骂道:“垃圾三本!”
  13. 王国绛沉到了启真湖底。

十、输入与输出

10.1 input( ) 函数

以提示字符串为参数。调用 input( ) 函数时,程序暂停运行,提示字符串被打印在屏幕上提示用户输入什么内容,然后将用户输入的内容(不含最后的回车)保存为字符串,作为返回值。可以在 input( ) 函数外套一层强制类型转换函数达到输入非字符串类型数据的目的。
代码:

  1. def reverse(text):
  2. return text[::-1] #切片的步长为-1,就会从后往前取元素。这里是返回了倒序字符串
  3. def is_palindrome(text): #判断是否为回文字符串
  4. return text == reverse(text)
  5. while True: #循环条件永远为True,就会永远循环下去
  6. something = input("Enter text: ") #括号中的是提示字符串,被打印在屏幕上提示用户输入文本
  7. if is_palindrome(something):
  8. print("Yes, it is a palindrome")
  9. else:
  10. print("No, it is not a palindrome")

输入与输出:

  1. Enter text: madam
  2. Yes, it is a palindrome
  3. Enter text: abcd
  4. No, it is not a palindrome
  5. Enter text: ... #会一直等用户输入,一直运行下去

10.2 文件

Python 中有 file 类,可以创建 file 类的对象代表文件,并对它使用 file 类的方法来读写文件。

10.2.1 打开文件

打开文件才能使用文件。

10.2.1.1 文件路径

如果要使用的文件和使用这个文件的 Python 程序在同一个文件夹中,那么给出文件名和扩展名即可,形如'文件名.扩展名',解释器会自动在 Python 程序所在的文件夹中找叫这个名字的文件。否则,就要用文件路径来告诉解释器文件在哪。
如果 Python 程序所在的文件夹中还有子文件夹,而要打开的文件在子文件夹中,就用相对文件路径提示文件的位置(“相对”是指相对 Python 程序所在文件夹的位置),形如'子文件夹名\文件名.扩展名'(注意 Windows 操作系统中文件路径用的是\而不是/,而 Linux 则是正常的)。
如果要打开的文件的位置很随便,那就要用绝对文件路径精确定位并打开,形如'盘符:\文件夹名\子文件夹名\文件名.扩展名'(仍然是以 Windows 操作系统为例)。

10.2.1.2 常用打开方式

文本文件的打开方式:

打开方式 效果
r 只读,文件的指针(类似光标)将会放在文件的开头。这是默认打开方式。
w 创建一个文件并只写。如果该文件已存在则将其覆盖;如果该文件不存在,正常创建新文件。
a 打开一个文件并在文件结尾追加。如果该文件已存在,文件指针将会放在文件的结尾,即新的内容将会被写到已有内容之后;如果该文件不存在,创建新文件并写入。
r+ 打开一个文件用于读写。文件指针将会放在文件的开头。
w+ 创建一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,正常创建新文件。
a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。即新的内容将会被写到已有内容之后;如果该文件不存在,创建新文件用于读写。

在字母后面/字母和加号之间多个“b”就是二进制文件的打开方式了。

10.2.1.3 open( ) 函数

使用方法①:f = open('文件名或文件路径','打开方式')
通过文件名或文件路径找到一个文件,并按照指定打开方式打开文件,将文件地址作为返回值,存储在 file 类的对象 f 中。接下来要对文件进行操作,只要对 f 调用对应的方法即可。
使用方法②:

  1. with open('文件名或文件路径','打开方式') as f:
  2. 代码块

这样写,可以将对文件的操作包装在代码块中,更加清晰。而且当代码块结束时,with … as 语句会自动关闭文件,更加简洁。

10.2.2 读取文件

10.2.2.1 逐行读取

上述 f 作为储存文件地址的变量,可以视为序列,其中的元素就是文件中一行一行的信息,可以使用 for … in 语句逐行遍历文件。要注意文件的每一行结尾都默认有一个 \n 。
代码:

  1. filename = 'pi_digits.txt' #在该程序所在的文件夹里有一个记录π值的文本文件
  2. with open(filename) as file_object: #不给出打开方式,默认以文本文件只读方式打开
  3. for line in file_object: #遍历每一行
  4. print(line.rstrip()) #一行行打印文件内容,就可以显示整个文件的内容
  5. #要用.rstrip()去除每一行结尾自带的\n,因为print()函数已经会自动补充\n了

输出:

  1. 3.1415926535
  2. 8979323846
  3. 2643383279

10.2.2.2 .readlines( ) 方法

以文件中的每一行为一个字符串元素,创建一个列表。无需参数。
上一块代码的等效代码:

  1. filename = 'pi_digits.txt'
  2. with open(filename) as file_object:
  3. lines = file_object.readlines() #把文件内容以列表形式储存好就结束with...as语句,关闭文件
  4. for line in lines: #遍历列表,相当于遍历文件
  5. print(line.rstrip())

10.2.3 用 .write( ) 方法写入文件

一般形式:f.write(字符串)
只能以一个字符串为参数,将字符串写到文件的指针所在位置。该方法不会自动在字符串结尾补充 \n。
注意,打开文件时的打开方式会决定能不能写入文件以及指针在文件的哪。

10.2.4 关闭文件

一般形式:f.close()
如果用 open( ) 函数的使用方法①打开文件,那文件不会自动关闭,使用完文件后要主动调用 .close( ) 方法关闭。不关闭文件会有什么后果就涉及计算机硬件层的原理了,这里不讲。

10.3 Pickle 模块

Python 内置的模块,可以把纯 Python 对象存储到文件中,方便长期保存,反复使用。
将对象存储到文件中,称为封装。需用 open( ) 函数的 wb 打开方式,以只写方式创建一个二进制文件(这里不能用文本文件),然后调用 Pickle 模块的 dump( ) 函数,形如pickle.dump(对象,f)
读取封装好的对象称为拆封,需用 open( ) 函数的 rb 打开方式,以只读方式打开一个二进制文件,再调用 Pickle 模块的 load( ) 函数,形如pickle.load(f)
代码:

  1. import pickle
  2. wcu_list = '世一大.data' #存储对象的文件的扩展名可以是.data也可以是.txt
  3. wcu = ['清华','北大','第三名']
  4. f = open(wcu_list, 'wb') #以二进制只写方式创建一个用来存储对象的文件
  5. pickle.dump(wcu, f) #将对象封装入文件地址为f的文件
  6. f.close() #关闭文件,就是保存文件
  7. del wcu #删除本地已经被保存的对象
  8. f = open(wcu_list,'rb') #以二进制只读方式打开文件
  9. storedlist = pickle.load(f) #取出文件中保存的对象
  10. print(storedlist)

输出:

  1. ['清华', '北大', '第三名']

以 .data 为后缀名的文件不能由用户打开并看其中的内容,但以 .txt 为后缀名的文件可以。因为对象是按照二进制保存的,而 .txt 文件是按照文本文件格式显示给用户看的,所以数据存取格式不同,直接手动打开文件会显示乱码。而像上述代码中一样用 rb 格式读取文件中的数据就能得到原本的对象。
image.png
image.png

十一、异常

11.1 Python 错误处理器

Python 解释器内置错误处理器。程序运行出错时,会打印出错误类型和错误发生位置。通过这些信息我们可以方便地修正代码中的语法错误(逻辑错误是不会报错的,因为逻辑有问题的程序也能被解释器正常运行)。
image.png

11.2 try 语句

一般形式:

  1. try:
  2. try代码块(可能引发异常的代码块)
  3. except 错误类型1: #try语句至少有一个except分支,用来处理异常情况
  4. except代码块1
  5. except 错误类型2:
  6. except代码块2
  7. else: #else和finally可有可无
  8. else代码块
  9. finally:
  10. finally代码块

try 代码块可能会出错,这不一定是因为程序员编的代码本身有问题,也可能是用户操作程序的方法过于诡异。try 语句的存在就是为了让程序在这种情况下不因为异常而无法运行。
当 try 代码块运行出现异常时,try 语句会去后面寻找对应错误类型的 except 语句,执行对应的 except 代码块,然后继续运行程序;如果 try 代码块出错,但没找到对应错误类型的 except 语句,就会启用默认的 Python 错误处理器,终止程序运行,打印错误信息;如果 try 代码块一直到运行完都没出错,就执行 else 代码块,然后继续运行程序。
不论是否发生异常,最后 finally 代码块都会运行。finally 代码块和后面的普通语句的不同之处在于,如果程序的错误类型没在 except 语句中列出,那么程序会先把 finally 代码块运行了再终止运行,后面的普通语句就不会运行了。

11.3 自定义异常

11.3.1 Exception 类

Python 中所有的异常都是 Exception 类的对象,不同错误类型是 Exception 类的不同子类,而具体的一个个异常就是对象。Python 已经预置了很多错误类型,遇到异常就会自动产生对应的 Exception 类对象。

11.3.2 raise 语句

一般形式:raise 错误类型(参数)
可以引发指定类型的异常,即主动创建一个指定错误类型的对象。
因为 Python 内置的错误类型会导致程序无法正常运行,所以会由解释器很容易发现有问题,并自动执行错误处理程序。但自定义的异常往往并不会导致程序不能正常运行,所以要用 raise 语句主动引发自定义的异常。
代码:

  1. class ShortInputException(Exception): #所有错误类型都是Exception类的子类
  2. '''用户输入的字符串太短了'''
  3. def __init__(self, length, atleast):
  4. Exception.__init__(self)
  5. self.length = length
  6. self.atleast = atleast
  7. try:
  8. text = input('Enter something --> ')
  9. if len(text) < 3: #用if语句主动发现异常,用raise语句主动引发异常
  10. raise ShortInputException(len(text), 3)
  11. except EOFError: #内置的错误类型,表示用户输入的文本在不该有EOF的地方出现了EOF
  12. print('Why did you do an EOF on me?')
  13. except ShortInputException as ex: #之后还要调用异常对象的字段,得用as语句把异常对象存入变量ex
  14. print(('ShortInputException: The input was '+ #这里太长,一行放不下,可以分两行写
  15. '{0} long, expected at least {1}').format(ex.length,ex.atleast))
  16. #因为有括号提示到哪里算一个逻辑行,所以不用\也可以。这就是“隐式行连接”
  17. else:
  18. print('No exception was raised.')

十二、零散知识

有这么一章真不是因为我搭建知识框架的能力不够好。原书就有这么一章作为零散知识的垃圾桶收纳盒。

12.1 调试小技巧

12.1.1 pass 语句

一个pass自成一语句,代表一个空代码块,可以用来测试是代码块内还是代码块外有问题。

12.1.2 assert 语句

一般形式:assert 表达式
如果表达式正确,就继续运行程序;如果表达式错误,就引发 AssertionError 错误类型。
调试时插到有疑问的地方,用来判断对象的具体状态,比插入 print( ) 函数打印出对象的状态方便。

至此,《A Byte of Python》的大厦已经落成,所剩的只是一些修饰工作。
不过,它的美丽而晴朗的天空却被两朵乌云笼罩……