Python 程序设计基础教程

Python 程序设计基础教程

撰稿人:南星六月雪

在这里插入图片描述

第 一 章

变量与简单数据类型

1.1 变量

先来观察以下程序:

world = "Hello Python!"
print(world)

world = "Hello Python,I love you!"
print(world)

运行这个程序,将看到两行输出:

Hello Python!
Hello Python,I love you!

本程序中,我们首先添加了一个名为 world 的变量(variable)。每个变量指向一个(value),在本例中指向的值为文本"Hello Python!"。然后将变量 world 的值打印到屏幕上。我们发现,在程序中可以随时修改变量的值,而 Python 将始终记录变量的最新值。

1.1.1 变量的命名与使用

在使用变量时,请务必牢记以下要求:

1、变量名只能包含字母、数字和下划线,且不能以数字开头。(尽管在 Python 3 中,变量名也可以包含如汉字之类的 Unicode 字符,但不建议使用)

2、不要将 Python 关键字和函数名用作变量名。Python 总共有 33 个关键字,详见如下

3、变量名应简短又具有描述性,尽量使用小写字母,但少用小写字母 l 和大写字母 O,因为容易被错看成数字 1 和 0 。

4、Python 中的变量与 C语言相比,不需要声明,就可以直接使用,但是如果在计算过程中的话,一个变量在计算表达式中使用时,必须要先有初值,否则程序也会无法进行。

1.1.2 变量是标签

变量通常被认为是用来存储值,但准确来说,在 Python 内部,应该使用定义“变量是可以被赋值的标签”来说明变量的工作原理,也就是说变量指向特定的值。

对于 C语言来说,认为变量是一个容器来存储值是合理的,但在 Python 中,变量是指向 Python 对象的标签,对象位于解释器的命名空间中,但是任意数量的标签可以指向同一个对象,当对象发生变化时,所有指向它的变量的值都会发生变化。例如:

a = [1,2,3]
b = a
c = b
b[1] = 5
print(a,b,c)

这段程序的输出结果为:

[1,5,3][1,5,3][1,5,3]

之所以这样,是因为三个标签都指向了同一个对象,如果对象发生了变化,则三个标签都会反映出来。如果变量指向的是常量或者不可变值,上述区别就不是很明显了:

a = 1
b = a
c = b
b = 5
print(a, b, c)

这段程序的输出结果为:

1 5 1

所以重要的区别在于标签所指向的是对象还是常量。

1.2 字符串

字符串(string)就是一系列字符。在 Python 中,用引号引起的都是字符串,其中引号可以是单引号也可以是双引号。而如果希望在字符串中包含引号和撇号,一般可以有三种方法:

"I told my friend,'Python is my good friend'!"

'I told my friend,"Python is my good friend"!'

'''I told my friend,"Python is my good friend"!'''
1.2.1 使用方法修改字符串

Python 中对字符串用方法进行修改,常见的总共有 6 个。详见下表:

1.2.2 在字符串中使用变量

在一些情况下需要在字符串中使用变量的值。例如,可能想使用两个变量分别表示名和姓,再合并两个值以显示姓名:

first_name = 'Bella'
last_name = 'Smith'
full_name = f'{first_name} {last_name}'
print(full_name)

上述程序的输出结果为:

Bella Smith

这种字符串称为 f 字符串。f 是 format(设置格式)的简写。总而言之,就是现在左引号前加上字母 f,再将要插入的变量放入花括号中。这样,Python 在显示字符串时,将把每个变量替换为其值。

同时我们还可以学习一个新的方法函数,使用 title() 方法可以将姓名设置成合适的格式,使用如下:

first_name = 'bella'
last_name = 'smith'
full_name = f'{first_name} {last_name}'
print(f"{full_name.title()}")

上述程序输出结果为:

Bella Smith
1.2.3 对于空白的处理

在编程中,空白泛指任何非非打印字符,如空格、制表符和换行符。我们可以通过空白使输出更容易阅读,但同时,我们也需要检查输入中是否有多余的空白,并删除这些空白。

在字符串中添加**制表符(\t)换行符(\n)**可以使程序的输出更加清晰易懂。例如:

>>> print("Languages:\n\tPython\n\tC\n\tJavaScript")
Languages:
    Python
    C
    JavaScript

而要确保字符串左端右端两端没有空白,可以分别使用 rstrip()lstrip()strip() 方法即可,例如:

>>> Love_one = ' plume '
>>> Love_one.rstrip()
' plume'
>>> Love_one.lstrip()
'plume '
>>> Love_one.strip()
'plume'
>>> Love_one
' plume '

但如你所见,再次询问 Love_one 的值时,它依然包含多余的空白,因此别忘了要永久删除这个字符串的空白,就必须将删除操作的结果关联到变量:

>>> Love_one = ' plume '
>>> Love_one = Love_one.strip()
>>> Love_one 
'plume'
1.2.4 其他操作杂烩

1、还有一个字符串常用操作是删除前缀和后缀。为了实现这一功能,只需在变量名后面使用 .removeprefix() 或是 .removesuffix() 方法即可,但注意它和删除空白的方法一样,保持原本的字符串不变,如果要保留结果,需要将其重新赋给原来的变量或是新的变量,例如:

>>> website = "http://jw.ustc.edu.cn"
>>> deleteweb = website.removeprefix("http://")
>>> print(deleteweb)
jw.ustc.edu.cn

而后缀效果类似,读者可以自行尝试。

2、如果需要把一些字符串形式的表达式实现其原本的功能,Python 也提供了一个函数,eval(str),它的具体功能见案例:

>>> str = "1 + 2"
>>> print(str)
1 + 2
>>> print(eval(str))
3
1.3 数

Python 中总共有三种数,分别是整数、浮点数和复数,三种类型存在一种逐渐"扩展"或"变宽"的关系:整数 -> 浮点数 -> 复数,例如:123 + 4.0 = 127.0 (整数+浮点数 = 浮点数)。下面依次展开讲述。

1.3.1 整数

Python 中的整数与 C语言中不同之处是没有许多中不同类型的整数,比如,C语言中整数包括长整数,短整数等等,但 Python 中只有一种整数!关于 Python 整数,只需要知道两点:

1、Python 整数没有限制,因此存在函数 pow(x,y):计算 x y x^y xy ,想算多大算多大,例如:

>>> pow(2,100)
1267650600228229401496703205376

2、整数的 4 种进制表示方式,分别为:十进制 1010,99,-217;二进制,以 0b 或 0B 开头 0b010,-0B101;八进制,以 0o 或 0O 开头 0o123,-0O456;十六进制,以 0x 或 0X 开头 0x9a 或 -0X89。

1.3.2 浮点数

浮点数是带有小数点及小数的数字,关于 Python 浮点数,只需要知道三点:

1、浮点数的取值范围和小数精度都存在限制,但在常规计算中可以忽略,其中取值范围数量级约为 − 1 0 307 -10^{307} 10307 1 0 308 10^{308} 10308 之间,精度数量级为 1 0 − 16 10^{-16} 1016

2、浮点数之间的运算存在不确定尾数,例如:

>>> 0.1 + 0.3
0.4
>>> 0.1 + 0.2
0.30000000000000004

这是因为在计算机中浮点数都是按照二进制存储,而根据已有知识我们知道浮点数在二进制中的表示方式是无限小数,在计算机中只存储了前 53 位,约 1 0 − 16 10^{-16} 1016 ,因此在从二进制转换为十进制中,浮点数值的大小发生了一些细微的偏差,从而导致了尾数的出现以及其不确定性。

但是由于不确定尾数一般发生在 1 0 − 16 10^{-16} 1016 左右,round() 函数就十分有效,函数 round(x, d):对x四舍五入,d是小数截取位数,例如:

>>> round(0.1 + 0.2,1) == 0.3
True

3、浮点数还可以用科学计数法表示,使用字母e或E作为幂的符号,以10为基数,格式如下:< a > e < b >,表示为 a ∗ 1 0 b a*10^b a10b ,例如:4.3e-3 值为0.0043,9.6E5 值为960000.0

1.3.3 复数

Python 中复数的定义与数学中的一致,但不使用数学中的 i i i 作为虚数单位,而是使用 j j j 作为虚数单位,a+bj 被称为复数,其中,a是实部,b是虚部。

对于复数,也有两个函数分别可以将一个复数的实部和虚部取出来,它们是 number.real 获得实部,number.imag 获得虚部,例如:

>>> a = 1.23e-4+5.6e89j
>>> print(a.real)
0.000123
>>> print(a.imag)
5.6e+89
1.4 数值运算操作符与操作函数
1.4.1 操作符

操作符是完成运算的一种符号体系,最基本的一些如下表:

操作符及使用描述
x + y加,x与y之和
x – y减,x与y之差
x * y乘,x与y之积
x / y除,x与y之商 10/3结果是3.3333333333333335
x // y整数除,x与y之整数商 10//3结果是3
+ xx本身
- yy的负值
x % y余数,模运算 10%3结果是1
x ** y幂运算,x的y次幂, x y x^y xy ,当y是小数时,开方运算 10**0.5 结果是 10 \sqrt{10} 10

其中,操作符也包含像 C语言中一样的赋值操作符,如:+=,-=等等

1.4.2 操作运算函数

操作运算函数是一些以函数形式进行数值运算,基本的一些如下表:

值得注意的是,divmod(x, y) 的输出结果为元组形式,关于元组的问题,我们之后进行讨论。

第 二 章

列表及其基本操作

2.1 列表是什么

列表(list)由一系列按特定顺序排列而成的元素组成,外围用方括号( [ ] )表示列表,用逗号分隔其中的元素。同一个列表中可以装有任何东西,且其中的元素之间可以没有任何关系。由于列表中往往有多个元素,在命名时常常用复数名称来命名。例如:

Schools = ['Zhenghai','Hanger','Xuejun']

那么如何访问列表元素呢?这一点和 C语言中的格式类似,具体见下示例:

>>> Schools = ['Zhenghai','Hanger','Xuejun']
>>> print(Schools[0])
Zhenghai

从上例可见,对于列表中元素的使用需要使用到列表的索引,与 C语言相似,第一个列表元素的索引为 0,而不是 1。但与 C语言不同之处在于,Python 中提供了一种倒序索引的方式,即将最后一个列表元素的索引记为 -1,之前的元素索引依次类推,这样在不知道列表长度的情况下也可以很容易的访问到最后一个元素。具体见下示例:

>>> Schools = ['Zhenghai','Hanger','Xuejun']
>>> print(Schools[-1])
Xuejun
2.2 修改、添加和删除元素

由于我们经常需要创建一个动态列表,即列表中的元素不断地增加与减少,因此学会修改、添加和删除列表中的元素十分重要!具体内容见下:

2.2.1 修改元素

修改是最容易的,这与访问列表元素的过程类似。要修改列表元素,先指定列表名和要修改的元素索引,再指定该索引位置上的新值。例如:

Schools = ['Zhenghai','Hanger','Xuejun']
print(Schools)
Schools[2] = 'Yueqing'
print(Schools)

那么上述程序的输出结果则为:

['Zhenghai','Hanger','Xuejun']
['Zhenghai','Hanger','Yueqing']
2.2.2 添加元素

Python 提供了多种在已有列表中添加新数据的方式,具体如下:

1、在列表末尾添加新元素,**append(sup)**方法将元素添加到列表的末尾,而不影响其他元素,例如:

>>> Schools = ['Zhenghai','Hanger','Xuejun']
>>> Schools.append('Yueqing')
>>> print(Schools[3])
Yueqing

用这种方法可以很容易地先创建一个空列表,在依次增加元素

2、在列表中插入新元素,**insert(index,sup)**方法将元素 sup 插入索引 index 处,原来在该索引位置以及之后的元素全部向后移一位,例如:

>>> Schools = ['Zhenghai','Hanger','Xuejun']
>>> Schools = [1,'Yueqing']
>>> print(Schools)
['Zhenghai','Yueqing','Hanger','Xuejun']

这种方法方便调整顺序的操作。

2.2.3 删除元素

Python 提供了多种在已有列表中删除数据的方式,具体如下:

1、使用 del 语句删除元素,先看具体示例:

>>> Schools = ['Zhenghai','Hanger','Xuejun']
>>> del Schools[1]
>>> print(Schools)
['Zhenghai','Xuejun']

从上述程序中可以看出,del 语句的格式为:del 列表名[索引],那么就删除了该索引处的列表元素,之后的所有元素一次向前移一位。

2、使用 pop( ) 方法删除元素,先看具体示例:

>>> Schools = ['Zhenghai','Hanger','Xuejun']
>>> school_pop = Schools.pop()
>>> print(Schools,' ',school_pop)
['Zhenghai','Hanger'] Xuejun

>>> Schools.append('Xuejun')
>>> school_pop2 = Schools.pop(1)
>>> print(Schools,' ',school_pop2)
['Zhenghai','Xuejun'] Hanger

从上述程序中可以看出,方法 pop( index ) 删除了列表中的最后一个元素,但同时我们可以把删除的元素赋给另一个变量,从而之后可以继续使用这个值,而不会导致值的丢失。这也是 pop( ) 方法的优点之一,但要注意的是 pop( ) 方法的格式中必须要赋值,不是选择性地赋值,并且 pop(index) 中的索引值可以添加,从而固定删除某一个索引处的元素,倘若不加索引,默认删除最后一个元素。

3、根据值删除元素,remove(value) 方法让 Python 确定值出现在列表的什么位置,并将该元素删除,示例见下:

>>> Schools = ['Zhenghai','Hanger','Xuejun']
>>> Schools.remove('Zhenghai')
>>> print(Schools)
['Hanger','Xuejun']

从上述程序中看出,remove()方法的确删除了元素 ‘Zhenghai’ ,但要注意的是 remove()方法只删除第一个指定的值。如果要删除的值在列表中出现多次,就需要使用循环,确保每个值都被删除。

2.3 管理列表
2.3.1 使用 sort()方法对列表永久排序

Python 方法 **sort()**可以轻松实现对列表的排序,但我们要清楚,排序只能是对于同一数据类型进行排序,比如:数字的排序就是按照数字从小到大排,字符串的排序就是按照从第一个字母开始依次比较排列顺序。因此,如果一个列表中既有数字又有字符串这是不能比较大小的,当然也就不能使用 sort()方法。并且与 C语言不同,单个字符在 Python 中也是一个字符串,不存在把单个字符当作数字与其他数字进行排序的现象。具体见下:

>>> provinces = ["Zhejiang","Anhui","Jiangsu"]
>>> provinces.sort()
>>> print(provinces)
["Anhui","Jiangsu","Zhejiang"]

为什么说是永久排序呢?因为使用 sort()方法后原来的列表就已经发生了变化,不再是原来的样子了,因此称为永久列表。不难发现,使用 sort()方法是从小到大排序,如果我们想从大到小排序怎么办呢?只需使用 sort(reverse = True) 即可,具体见下:

>>> provinces = ["Zhejiang","Anhui","Jiangsu"]
>>> provinces.sort(reverse = True)
>>> print(provinces)
["Zhejiang","Jiangsu","Anhui"]
2.3.2 使用 sorted()函数对列表进行临时排序

那么有没有不对原来列表进行修改,从而临时排序的方式呢?Python 提供了函数 sorted(),可以临时的将列表排序,具体见下:

>>> Lists = [3,1,4,2]
>>> print(sorted(Lists))
[1,2,3,4]
>>> print(Lists)
[3,1,4,2]

从上述程序中可见,原来的列表并没有发生变化。而如果想要将列表反向排序,同样可加上参数 reverse = True,具体见下:

>>> Lists = [3,4,1,2]
>>> print(sorted(Lists,reverse = True))
[4,3,2,1]
2.3.3 其他操作杂烩

1、反向打印列表,可使用 **reverse()**方法,具体见下:

>>> stationaries = ["pencil","eraser","ruler","pen"]
>>> stationaries.reverse()
>>> print(stationaries)
["pen","ruler","eraser","pencil"]

2、确定列表长度,可使用 **len()**函数,具体见下:

>>> stationaries = ["pencil","eraser","ruler","pen"]
>>> len(stationaries)
4
第 三 章

从循环的角度看列表

3.1 遍历列表

本章我们介绍第一个程序结构,循环结构。那么,如何遍历列表呢?我们使用 for 循环来实现,它的基本格式为:“ for <循环变量> in <遍历结构>: ”。具体见下:

performers = ["Mary","Bella","Smith"]
for i in performers:
    print(i)

这个程序中,我们首先定义了列表 performers,接着定义了一个 for 循环,要注意 Python 中的 for 循环与 C语言的差别。下面我们来解释一下循环中所用的关键字 in。in 的含义是包含,言外之意就是从 performers 这个列表中依次取出每一个元素,存储在 i 中,并进行循环中的操作。因此,上述程序的输出结果为:

Mary
Bella
Smith

观察这个程序,还有一个与 C语言不同之处也就不难发现,那就是 Python 中的代码块并不是用 { } 进行分割,也是使用缩进来表示一个代码块。例如上述程序,for i in performers 之后,先加 “:”,再换行缩进,从而表示这个循环体。因此,在 Python 程序中,正确地使用缩进尤为重要,在同一个程序中,每个代码块的缩进格式必须保持相同,否则,程序就无法区分不同的代码块。当然,还有一个要牢记的点是冒号不能遗漏,否则,也会导致语法错误。

3.2 数值列表的使用
3.2.1 使用 range()函数创建数值列表

Python 提供了函数 range(),使我们可以轻松生成一系列的数。例如:

for value in range(1,3):
    print(value)

上述程序输出结果为:

1
2

这是因为 range()函数是左闭右开。而如果 range()中只有一个参数,这样它将从 0 开始。例如:range(6)返回的数为 0 ~ 5。

那么如何创建一个数值列表呢?我们可以使用 list()函数将 range()的结果直接转换为列表。同时,还可以对 range()进行指定步长,从而根据步长来生成数。什么是步长?先看下例:

>>> numbers = list(range(1,6))
>>> print(numbers)
[1,2,3,4,5]
>>> even_numbers = list(range(2,11,2))
>>> print(even_numbers)
[2,4,6,8,10]

在上述示例中,我们最后创建的数值列表中发现只有偶数,这是因为range(2,11,2)的含义为从 2 开始数,然后不断地加 2,直到到达或超过终值(11)

3.2.2 对数值列表进行简单统计计算

Python 中提供了几个统计中常用的函数,可以帮助我们找出数值列表中的最大值,最小值和总和,具体见下例:

>>> numbers = [1,2,3,4,5,6,7,8,9,10]
>>> min(numbers)
1
>>> max(numbers)
10
>>> sum(numbers)
55
3.2.3 列表推导式

列表推导式(list comprehension)将 for 循环和创建新元素的代码合并为一行,并可以自动追加新的元素。下面的示例可以用来创建 2~10 中所有偶数的平方数列表:

squares = [value**2 for value in range(2,11,2)]
print(squares)

这个程序的输出结果为:

[4,16,36,64,100]
3.3 切片的使用

之前我们一直在讨论如何处理列表的所有元素或是单个元素,若是想要处理列表的部分元素,在 Python 中就叫作切片(slice)。

创建切片,类似于 range()函数使用指定的第一个元素和最后一个元素的索引,同样是左闭右开。例如:

>>> vegetables = ['carrot','beans','potato','tomato']
>>> print(vegetables[0:3])
['carrots','beans','potato']

若是想从第一个元素开始切片,可以不指定起始索引,例如:

>>> vegetables = ['carrot','beans','potato','tomato']
>>> print(vegetables[0:3])
['carrots','beans','potato']

要想让切片终止与末尾,也可类似的语法,省略结束索引,这样即使是负数也可以输出到列表末尾的所有元素。例如:

>>> vegetables = ['carrot','beans','potato','tomato']
>>> print(vegetables[1:])
['beans','potato','tomato']
>>> print(vegetables[-2:])
['potato','tomato']

接上一节,如果想要遍历部分列表,也需要使用切片,例如:

vegetables = ['carrot','beans','potato','tomato']
for vege in vegetables[0:1]:
    print(vege)

这个程序的输出结果为:

carrot

而如果想要复制整个列表,可以同时省略起始索引和终止索引,例如:

>>> vegetables = ['carrot','beans','potato','tomato']
>>> print(vegetables[:])
['carrots','beans','potato','tomato']
3.4 元组的介绍

元组与列表极其类似,我们在此主要介绍不同之处。那么最重要的一点就是,列表是由 [ ] 来表示,而元组是由()来表示,并且元组中的元素不可修改。这便是元组与列表最大的不同。例如:

>>> number = (1,2,3,4,5,6,7,8,9,10) 

而其余的操作与列表类似,故不再赘述。

不过,虽然元组不可修改,但可以给元组的变量赋值,即重新定义整个元组。例如:

dimentions = (200,50)
for dimention in dimentions:
    print(dimention)
    
dimentions = (100,40)
for dimention in dimentions:
    print(dimention)

以上程序的输出结果为:

200
50
100
40
第 四 章

用户的基本输入与输出

4.1 输出函数的使用

在本章之前,已经通过一些示例展示了 print( ) 函数的一些基本使用方式,本节我们具体介绍输出函数的一些内容

4.1.1 print()函数的语法

以下是 print()函数的语法格式:

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

objects — 对象(复数),复数形式表示可以一次输出多个对象。输出多个对象时,需要用 ’, ‘分隔。
sep — 用来间隔打印出的多个对象,默认值是一个空格。
end — 用来设定以什么结尾。默认值是换行符 \n,我们可以换成其他字符串。
file — 要写入的文件对象。
flush — 输出是否被缓存通常决定于 file,但如果 flush 关键字参数为 True,流会被强制刷新。

其中,sep,end,file,flush是关键字不可替换,其余所有字符串都是可以替换的。

4.1.2 一句输出多个字符串
str1 = 'hello Ningbo,'
print("输出 :", str1, 'Nice to meet you!')

上述程序的输出结果为:

输出: hello Ningbo, Nice to meet you! 

假如想要在不同对象之间添加间隔符,示例如下:

print("hello", "USTC", "I love you", sep=",")  # 设置间隔符

上述程序的输出结果为:

hello,USTC,I love you
4.1.3 字符串的格式化打印

Python 中主要有两种不同方式的格式化打印,我们分别介绍:

1、与 C语言类似的格式化打印,先看示例:

weight = 21
print("My name is %s and weight is %d kg!" % ('Zara', weight))

与 C语言中不同的是引号外的部分,括号中的格式化符号和格式化操作符辅助指令与 C语言差异性不多。尽管在 Python 中不区分单个字符和字符串,但在这里仍然可以使用 %c 表示单个字符,并且使用 ASCLL 码。

2、用 str.format(),先看示例:

weight = 21
print("My name is {} and weight is {} kg!".format('Zara', weight))

用 str.format() 看似乎实现的效果和第 1 种一样,但是实际上它更灵活、更强大。它的灵活体现在:

① 可以设置格式化参数的顺序,如下面的示例:

>>> print("{1} {0} {1}".format("hello", "world"))  # 设置指定位置
'world hello world'  # 这是运行结果

②可以设置名字,如下面的示例:

# 直接设置名字
print("网名:{name}, 地址 {url}".format(name="plume", url="123"))

# 通过字典设置参数
site = {"name": "plume", "url": "123"}
print("网名:{name}, 地址 {url}".format(**site))

# 通过列表索引设置参数
my_list = ['plume', '123']
print("网名:{0[0]}, 地址 {0[1]}".format(my_list))  # "0" 是必须的

所以说用这种方式更具有一般性,功能更强大。

4.2 用户输入的使用

在 Python 中用户输入通常使用的是 input()函数,以下是它的一些不同的使用类型

1、同行依次输入多个值,并按照指定要求分隔

a, b, c = map(int, input().split())  	 # 这条语句就能够表示输入的句法。
#  表示的是一次能够输入多个值,依照空格进行分割。
a, b, c = map(int, input().split(","))   # 这种情况是按照逗号进行分割。

2、同行依次输入多个值,并按要求分隔后存储到列表中

#对于含有空格的一行数的输入
list1 = list(map(int, input().split()))
list1 = list(map(int, input().split(",")))  #按照逗号进行分割。
# 一次性的输入n个数据,并且保存到列表中。
# 如果取第几个数,就是用列表的指针的形式

3、使用添加列表方法来存储输入的数据

b = input().split()  # 依次输入这些数据。
ll = []              # 创建列表。
for i in b:
    ll.append(eval(i))

4、输入 m 行 n 列的二维列表,类似于 C语言中的二维数组

# 输入一个m行n列的元素
m, n = map(int, input().split())  	 # 这条语句就能够表示输入的句法
# 在这里M是行数字,N是列数字。
A = m*[0*n]
for i in range(m):
    A[i] = input().split(" ")
    for j in range(n):
        A[i][j] = int(A[i][j]) 
print(A)

在使用 input()时,应注意给出清晰的提示,准确地指出希望用户提供怎样的信息,这部分内容放在 input()函数的括号中,用引号引起。

第 五 章

if 语句与 while 语句的使用

5.1 if 语句的使用

上一章我们提到了 for 循环的作用,现在我们首先介绍使用 if 语句进行条件测试,从而判断语句的执行与否。

5.1.1 条件测试

每条 if 语句的核心都是一个值为 True 或是 False 的表达式,这种表达式称为条件测试。Python 根据条件测试的值是 True 还是 False 来决定是否执行 if 语句中的代码。大多数的条件测试都是涉及到两个变量的比较,Python 中一些比较常见的比较符列于下表:

符号描述
==判断两侧的表达式是否相等
!=判断两侧的表达式是否不等
>,<,>=,<=判断两侧数值的大小
and检查两个条件是否都为 True
or检查两个条件,只要一个满足,就通过整个条件测试
in判断特定的值是否在列表或元组中

补充:如果想要判断一个列表或是元组是否为空,我们也有一定的方法,具体见下:

menu = []
if menu:
    print('False')
else:
    print('True')

上述程序首先创建一个空列表,然后进行检查

5.1.2 if 语句

if 语句有多种类型,针对不同的问题我们要选择不同的 if 语句进行使用。下面开始更深入地介绍不同种类的 if 语句:

1、最简单的 if 语句:

if conditional_test:
    do something

这就是最简单的形式,其中 do something 可以是多行代码。如果 conditional_test 的结果为 True ,那么就执行 do something 中的代码。

2、if - else 语句:

age = 17
if age >= 18:
    print('I have been an adult.')
else:
    print('I\'m still a child')

if - else 语句块类似于简单的 if 语句,但其中 else 语句能指定条件测试未通过时要执行的操作。上述程序的输出结果为:

I'm still a child

3、if - elif - else 语句:

age = 12
if age < 4:
    price = 0
elif age < 18:
    price = 12
elif age < 60:
    price = 24
else:
    price = 0
print(price)

if - elif - else 语句块中 elif 可以重复多次,但最终只会执行其中的一个,也就是第一次遇到通过了的条件测试的位置,并跳过余下的条件测试。值得注意的是书写 elif 的条件测试时要避免有重复范围出现。上述程序的输出结果为:

12

同时,Python 中允许省略 else 代码块。由于只要不满足任何 if 和 elif 中的条件测试,其中的代码就会执行。这可能引入无效甚至恶意的数据。因此,有时使用一个 elif 代码块来代替 else 代码块。这样就可以确保仅当满足相应条件时,代码才会执行。

5.2 while 语句的使用

for 循环用于针对集合中的每一个元素执行一个代码块,而 while 循环则不断地运行,直到指定的条件不再满足为止。

5.2.1 使用 while 循环

首先,我们展示 while 循环的基本格式:

number = 1
while number <= 5:
    print(number,end = ' ')
    number += 1

上述程序的输出结果为:

1 2 3 4 5 

整体与 C 语言差异不大,但仍然有部分内容我们重新介绍

5.2.2 内容补充

1、可以使用 while 实现用户选择性退出,例如:

message = ''
while message != 'quit':
    message = input()
    print(message)

上述程序实现了多个输出,并可判断何时停止输入

2、善于使用标志(flag)。标志是一个变量,用来充当程序的交通信号灯。可以让程序在标志为 True 时继续运行,标志为 False 时停止运行。标志在程序需要满足多个条件才继续运行的情况下非常有用,例如:

number = '131-3639-5518D'
sign = True
i = 0
result = ''
while sign:
    message = number[i] 
    if message = 'D' or message = '-':
        sign = False
    result += message
print(result)

上述程序中定义了标志 sign,当其为 True 时,循环继续,否则,循环停止。从而可以实现判断多个条件,且使程序简洁易懂。

5.3 在循环中使用 break 和 continue
5.3.1 使用 break 退出循环

与 C语言类似,如果想要直接退出循环,不再运行剩下的代码,可以使用 break 语句。例如:

prompt = "\nPlease enter the name of a city you have visited:"
prompt += "\n(Enter 'quit' when you are finished)"
while True:
    city = input(prompt)
    if city == 'quit':
        break
    else:
        print(f"I'd love to go to {city.title()}!")

上述程序可以实现如下输出结果:

Please enter the name of a city you have visited:
(Enter 'quit' when you are finished) new york
I'd love to go to New York

Please enter the name of a city you have visited:
(Enter 'quit' when you are finished) quit
5.3.2 使用 continue 进入下一轮循环

与 C语言类似,如果希望直接返回循环开头,并根据条件测试结果决定是否继续执行循环,可使用 continue 语句。例如:

even_number = 0
while even_number <= 100:
    even_number += 1
    if even_number % 2 == 1:
        continue
    print(even_number,end = ' ')

上述程序实现了输出 1~100 中所有偶数的目的

第 六 章

字典

6.1 简单字典

先看示例:

>>> grade = {'jia':97,'yi':95}
>>> print(grade['jia'])
97

在 Python 中,字典(dictionary)是一系列键值对(key-value pair)。每个键都与一个值关联,可以使用键来访问与之关联的值。与键关联的值可以是数、字符串、列表,甚至可以是字典。也就是说,几乎任何 Python 对象都可用作字典中的值。

如上述代码块中,grade 就是一个字典,用花括号括起,里面键值对一一对应。请注意,不同的键可以有相同的值。总而言之,字典与 C语言中的结构体有几分类似。

6.2 使用字典
6.2.1 访问字典中的值

要访问字典中的值,可指定字典名并把键放在后面的方括号内,如下所示:

income = {'jia':1000,'yi':100}
print(income['jia'])

这样就会返回字典 income 中与键 ‘jia’ 关联的值。

如果要修改字典中的值,也同样的依次指定字典名、用方括号括起来的键和与之关联的新值,如下所示:

income = {'jia':1000,'yi':100}
income['jia'] = 999
print(income['jia'])

这样就修改了键 ‘jia’ 的值。

6.2.2 添加键值对

字典是一种动态结构,随时可以添加键值对。要添加键值对,只需依次指定字典名、用方括号括起来的键和与之关联的值。如下所示:

income = {'jia':1000,'yi':100}
print(income)
income['bin'] = 10000
print(income)

上述程序的输出结果为:

{'jia':1000,'yi':100}
{'jia':1000,'yi':100,'bin':10000}

这样的添加方式会保留定义时的元素排列顺序,即这样的添加是依次向后添加。有些时候,我们也可以直接用 { } 来创建空字典,再依次添加元素。

6.2.3 删除键值对

对于需要删除的信息,与列表相同,我们可以用 del 语句将相应的键值对彻底删除。具体如下:

income = {'jia':1000,'yi':100}
print(income)
del income['yi']
print(income)

上述程序的输出结果为:

{'jia':1000,'yi':100}
{'jia':1000}
6.2.4 使用 get( ) 来访问值

使用放在方括号内的键从字典中获取感兴趣的值,可能会引发问题:如果指定的键不存在,就会出错。因此,为了避免这样的错误,可使用 get( ) 方法在指定的键不存在时返回一个默认的值。get( ) 方法的第一个参数用于指定键,是必不可少的;第二个参数为当指定的键不存在时要返回的值,是可以自行选择的,如果没有第二个参数,Python 将会返回值 None,这是一个特殊值,并不是错误。具体如下:

income = {'jia':1000,'yi':100}
point_value = income.get('bin','No point')
print(point_value)

上述程序的返回值为:

No point
6.3 遍历字典
6.3.1 遍历所有键值对

遍历的过程,我们仍然选择使用熟悉的 for 循环,但是对于字典的遍历,要用到 items( ) 方法,具体见下:

user = {
    'username':'HXR'
    'first':'Hin'
    'Last':'XiRan'
}
for key,value in user.items():
    print(f"\nKey:{key}")
    print(f"Value:{value}")

这些代码让 Python 遍历字典中的每个键值对,并将键赋给变量 key,将值赋给变量 value。

6.3.2 分别遍历所有键和所有值

1、遍历字典中所有键:在 Python 中,提供了方法 keys( )。具体见下:

user = {'username':'HXR''first':'Hin''Last':'XiRan'}
for key in user.keys():
    print(key.title())

上述代码让 Python 遍历字典中的每个键,并将其赋给 key。

2、遍历字典中所有值:在 Python 中,提供了方法 values( )。具体见下:

user = {'username':'HXR''first':'Hin''Last':'XiRan'}
for value in user.values():
    print(value)

上述代码让 Python 遍历字典中的每个值,并将其赋给 value。

第 七 章

函数

7.1 定义函数

先看一个简单函数:

def greet_user():
    """显示简单问候语"""
    print("Hello!")
greet_user()

上述程序就是最简单的函数结构。第一行使用关键字 del 告诉 Python,要定义一个函数,这就是函数的定义。然后向 Python 指出函数名,还可以在括号中指出函数为完成任务需要什么样的信息。这里函数名为 greet_user( ) ,不需要任何信息就能工作,因此括号中是空的,最后定义以冒号结尾。

第二行称为文档字符串(docstring)。这是用来在生成文档时,Python 会查找紧跟在函数定义后的字符串。这些字符串通常前后分别用三个双引号引起,能够包含多行。

第三行是函数体的工作内容。而要使用这个函数,则必须调用它。函数调用让 Python 执行函数中的代码。

7.2 传递实参

定义中括号内的变量称为形参,调用时使用的变量称为实参

7.2.1 位置实参

在调用函数时,Python 必须将函数调用中的每个实参关联到函数定义中的一个形参。最简单的方式是基于实参的顺序进行关联。以这种方式关联的实参称为位置实参。例如:

def stu_grade(name,grade):
    """记录学生姓名与成绩"""
    print(f"my name is {name}.\n")
    print(f"my grade is {grade}.")
stu_grade("sj","100")

这样的调用可以根据需要使用多次,从而提高工作效率。但要注意,如果实参的顺序不正确,结果可能会很奇怪,因此要注意使用时实参的顺序是否有错误。

7.2.2 关键字实参

关键字实参是传递给函数的名值对(name-value pair)。这样直接将实参中名称和值关联起来,因此向函数传递实参时就不会混淆了。还是上面的程序,例如:

def stu_grade(name,grade):
    """记录学生姓名与成绩"""
    print(f"my name is {name}.\n")
    print(f"my grade is {grade}.")
stu_grade(name = "sj",grade = "100")

这样的调用有利于避免顺序错误的情况出现。

7.2.3 默认值

在编写函数时,可以给每个形参指定默认值。这样,如果在调用函数时给形参提供了实参,Python将使用指定的函数值,否则将使用形参的默认值。这样针对函数的典型用法很有好处,例如:

def stu_grade(name,grade="100"):
    """记录学生姓名与成绩"""
    print(f"my name is {name}.\n")
    print(f"my grade is {grade}.")
stu_grade("sj")

这样的定义是因为 grade 经常是固定值,那么在调用时就不需要再给出这个实参也可以运行程序。 同时,对于一些变量,可以通过默认值,使其变成可选用的参数。比如说:一个函数的任务是输出一个人的姓名,但一个人不一定同时有 first name,middle name和 last name。所以可以将 middle name 的形参设置默认值为空字符串,从而在输出时保持正确的结果。

7.3 返回值

函数返回的值称为返回值。在函数中,可以使用 return 语句将值返回到调用函数的那行代码。例如:

def menu(name,price):
    """返回菜单中菜名与价格"""
    menus = f"{name}:{price}"
    return menus
me = menu("egg","1.5")
print(me)

这样就实现了返回特定值的作用。但是函数不仅可以返回字符串,它几乎可以返回任何类型的值,包括列表和字典等复杂的数据结构,这一点与 C 语言非常类似。

7.4 传递任意数量的实参

有些时候不知道函数需要多少个实参,因此 Python 提供了形参 *toppings 实现接受任意多个参数,当然 toppings 这个名字可以任意选择,例如:

def make_pizza(*toppings):
    """打印顾客点的所有配料"""
    print(toppings)
make_food('vegetable')
make_food('sausage','cabbage')

注意,使用 *toppings 相当于将所有形参封装到一个元组中供函数体使用。而使用 **toppings 相当于将所有形参封装到一个字典中。因此上述程序的输出结果为:

('vegetable')
('sausage','cabbage')

但是,如果要让函数接收不同类的实参,必须将函数定义中接受任意数量的实参的形参放在最后。这样,可以结合使用位置实参与任意数量的实参,例如:

def make_pizza(size,*toppings):
    """概述要制作的比萨"""
    print(f"\nMaking a {size}-inch pizza with the following toppings:")
    for topping in toppings:
        print(f"-{topping}")
make_pizza(16,'mushroom','cheese')

这样上述程序的输出结果为:

Making a 16-inch pizza with the following toppings:
-mushroom
-cheese

那么如何使用任意数量的关键字实参呢?这就要用到上文提到的 **toppings 来接收了。例如:

def build_profile(first,last,**user_info):
    """创建一个字典,其中包含我们知道的有关用户的一切"""
    user_info['first name'] = first
    user_info['last name'] = last
    return user_info
user_profile = build_profile('albert','einstein',location = 'princeton',field = 'physics')
print(user_profile)

先观察上述程序的输出结果为:

{'location': 'princeton', 'field': 'physics', 'first name': 'albert', 'last name': 'einstein'}

我们发现,return 的是一个字典,其中顺序是从任意形参处开始输入的实参,最后添加上函数体中增加的两个键与值。

7.5 将函数存储在模块中
7.5.1 导入整个模块

模块是拓展名为 .py 的文件,包含要导入程序的代码。下面我们创建了一个 test.py 的文件,具体见下:

def make_pizza(size,*toppings):
    """概述要制作的比萨"""
    print(f"\nMaking a {size}-inch pizza with the following toppings:")
    for topping in toppings:
        print(f"-{topping}")

接下来,在 test.py 所在的目录中创建一个名为 making_pizza.py 的文件。这个文件先导入刚创建的模块,再调用。导入使用的是 import 语句,具体见下:

import test
test.make_pizza(16,'mushroom','peppers')

当用 Python 读取文件时,代码行 import test 会让 Python 打开文件 test.py ,并将其中所有内容复制到这个程序中运行。

7.5.2 导入特定函数

还可以只导入模块中的特定函数,具体见下:

from module_name import function_name1,function_name2,function_name3

用逗号分隔函数名,这样就可以导入任意多个函数。如果使用这样的语法,在调用函数时则无需使用句点。调用的具体过程如下:

from test import make_pizza
make_pizza(16,'mushroom','pepper')
7.5.3 使用 as 给函数指定别名

如果要导入的函数名称太长或者可能与程序中既有的名称冲突,可指定别名,具体见下:

from test import make_pizza as mp
mp(16,'mushroom','pepper')

如上述程序展示的那样,可以使用别名 mp( ) 使程序简化。

7.5.4 使用 as 给模块指定别名

还可以给模块指定别名,从而调用过程更方便。具体见下:

import test as t
t.make_pizza(16,'mushroom')
7.5.5 导入模块中的所有函数

使用星号(*)运算符可以让 Python 导入模块中的所有函数:

from test import *
make_pizza(16,'mushroom','pepper')

import 语句中的星号让 Python 将模块 test 中的每个函数都复制到这个程序文件中。这样就无需使用点号了,不过在大型程序中慎用。因为如果出现函数重名的问题,Python 会选择覆盖函数,而不是全部导入,所以存在一定的风险。

第 八 章

*面向对象编程:类

8.1 类的创建和类的实例

Python 是一种面向对象的编程语言,其中类是非常重要的概念。类是一种用户定义的数据类型,它代表着一类具有相同属性和方法的对象的集合。实例则是类的具体实现,是类的一个个体,可以使用类定义的属性和方法。

1、定义类:使用关键字 class
2、创建一个类的对象:可以通过类名后面加括号来创建一个实例

__ init __( ) 作为类的构造方法,用来初始化类的实例,self 表示类的实例本身。self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self。其他方法则按照正常函数的形式定义。类可以定义属性和方法,属性是类的数据成员,方法是类的函数成员。类的方法与普通的函数只有一个特别的区别:它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。看下面实例:

class Dog:
    def __init__(self, name, breed, age):
        self.name = name
        self.breed = breed
        self.age = age

my_dog = Dog('Buddy', 'Golden Retriever', 6)

上面的代码中,“Dog”类表示狗类,它包含名字、品种和年龄这三个属性。

创建了一个 “my_dog” 的实例,该实例有名为 “Buddy” 的名字、品种为 “Golden Retriever”、年龄为 6 岁。

此时,my_dog 变量就代表了一个狗类的实例,可以通过访问它的属性来获取相应的信息,例如:

print(my_dog.age)

输出结果为:6,同时,也可以使用对象的方法来修改属性或进行其他操作:

class Dog:
    def __init__(self, name, breed, age):
        self.name = name
        self.breed = breed
        self.age = age

    def bark(self):
        print('Woof woof!')

my_dog = Dog('Buddy', 'Golden Retriever', 6)

my_dog.bark()  # 输出 “Woof woof!”
8.2 类的属性

在 Python 中,类的属性可以被理解为类的变量。Python 的公有属性私有属性以及实例属性静态属性之间是存在关联的,具体关系如下:

1、公有属性:指没有加前缀双下划线__的属性,可以在类内外被访问,也可以被继承和重写。

2、私有属性:指加了前缀双下划线__的属性,只能在类内被访问和修改,而在类外部无法访问或修改。

3、实例属性:指定义在 __ init __ 方法中,以 self.属性名 的形式定义的属性,每个实例都独立拥有一个自己的实例属性,它们随实例创建和销毁。

4、静态属性:指在类下直接定义的属性,可以使用类名直接访问,它们是类的属性,每个实例都共享一个静态属性。

公有属性和私有属性是属于对象或类中的实例属性或静态属性的一种访问方式,也就是说,公有属性和私有属性可以同时作为实例属性和静态属性存在。对于 Python 中的公有属性和实例属性的关系,可以通过实例的 self.属性名 来访问和修改;而对于 Python 中的私有属性,则需要在属性名前面加上双下划线"__",才能被认定为私有属性,无法通过实例调用,只能通过类内部的方法进行访问和修改。对于静态属性,则是直接定义在类下,可以使用类名进行访问和修改。

8.2.1 实例属性和静态属性

类的属性有两种类型:实例属性和类属性。

类型定义特点
实例属性类的实例的属性各个实例相互之间不产生影响
静态属性/类属性类本身的属性类的所有实例都共享一份类属性,各个实例相互之间会产生影响
属性实例属性静态属性
定义方式和作用定义在实例方法中,仅对当前实例有效定义在类中,对所有实例共享同一份
访问方式通过实例进行访问通过类名进行访问
访问时用的是哪个对象属性实例属性值只有一个静态属性值,共用同一个
生命周期随着实例的创建和销毁而创建和销毁随着类的创建和销毁而创建和销毁
作用域实例内部作用域内类的内部作用域内
示例self.name = '张三' (在 init 中)name = '李四' (在类中)

**1、实例属性:**实例属性是指属于某个对象的属性,每个对象都有自己独立的实例属性,相互之间不会产生影响。实例属性的赋值一般在类的初始化方法 __ init __ 中进行,也可以在对象创建之后通过赋值来添加实例属性。例如,我们定义一个人类 Person,可以在初始化方法中定义实例属性 name 和 age,如下所示:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

**2、静态属性:**类属性是指属于整个类的属性,所有对象共享一份类属性,相互之间会产生影响。类属性一般在类的定义中直接定义,也可以在类的其他方法中添加。类的属性是反映类内部状态的变量,可以在类的定义中定义和修改,也可以在类的其他方法中访问和使用。理解好类的属性对于深入理解 Python 面向对象编程非常重要。例如,我们为人类 Person 添加一个类属性 species,表示人类的种类,如下所示:

class Person:
    species = 'human'  # 定义类属性

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def get_species(self):  # 定义实例方法
        return self.species

1、在类属性的访问和修改上,可以通过类名或对象名来访问。例如,我们可以通过 Person.species 来访问类属性,也可以通过 person1.species 来访问对象 person1 的类属性。

2、类属性的修改同样可以通过类名或对象名来实现。如果通过对象名修改类属性,则会创建一个同名的实例属性,该实例属性会覆盖掉类属性的值,不会改变类属性的值。例如,我们修改 person1.species 的值为 ‘homo erectus’,则会创建一个实例属性 species,它的值为 ‘homo erectus’,但是不会改变类属性 Person.species 的值。

8.2.2 公有属性和私有属性

在 Python 中,类的属性(或者成员变量)也可以分为公有属性和私有属性。

属性类型声明方式外部访问方式内部访问方式
公有属性未加任何修饰符直接访问直接访问
私有属性在属性名前面加双下划线 __不能直接访问,可以通过访问方法间接访问可以直接访问

**1、公有属性:**公有属性是指可以从类的外部直接访问和修改的属性,一般在类的构造方法中用 self 关键字来定义,例如:

class Student:
    def __init__(self, name, age):
        self.name = name  # 公有属性
        self.age = age  # 公有属性

在这个例子中,nameage 都是公有属性,我们可以通过实例对象直接访问和修改它们的值:

s = Student('Lucy', 20)
print(s.name)  # 'Lucy'
s.age = 21
print(s.age)  # 21

**2、私有属性:**私有属性是指不能从类的外部直接访问和修改的属性。在 Python 中,可以通过在属性名前面加上双下划线 __ 来定义私有属性,例如:

class Student:
    def __init__(self, name, age):
        self.__name = name  # 私有属性
        self.__age = age  # 私有属性

在这个例子中,__name__age 都是私有属性,我们无法直接访问和修改它们的值:

s = Student('Lucy', 20)
print(s.__name)  # AttributeError: 'Student' object has no attribute '__name'
s.__age = 21  # AttributeError: 'Student' object has no attribute '__age'

这是因为 Python 解释器会自动将属性名 __name__age 替换成 _Student__name_Student__age,从而防止属性被意外访问和修改。

虽然无法直接访问和修改私有属性,但是我们可以通过类内部定义的方法来访问和修改它们的值,例如:

class Student:
    def __init__(self, name, age):
        self.__name = name  # 私有属性
        self.__age = age  # 私有属性

    def get_name(self):
        return self.__name

    def set_age(self, age):
        self.__age = age

在这个例子中,我们定义了一个 get_name() 方法来获取私有属性 __name 的值,定义了一个 set_age() 方法来修改私有属性 __age 的值。这样,我们就可以通过这些方法来访问和修改私有属性的值:

s = Student('Lucy', 20)
print(s.get_name())  # 'Lucy'
s.set_age(21)
print(s._Student__age)  # 21

需要注意的是,这种方式虽然可以访问和修改私有属性的值,但是完全破坏了封装的原则,不建议频繁使用。在实际编程中,一般优先使用公有属性,只在有特殊需求的时候才使用私有属性。并且,Python 中也没有真正的私有属性,只有一种约定俗成的方法来模拟私有属性的效果,这也是 Python 的一种“鸭子类型”的体现。

8.3 类的方法

Python 的实例方法、类方法和静态方法是类中的三种不同类型的方法,与公共方法和私有方法的关系是有些类似的。

1、实例方法是定义在类中、需要传入 self 参数的方法,可以访问实例属性和类属性,也可以修改它们。这种方法是最常用的一种方法。

2、类方法是通过 @classmethod 装饰器定义的方法,第一个参数为 cls(代表类本身),可以访问和修改类属性,但不能访问实例属性。类方法可以通过类本身调用,也可以通过实例调用,实例调用时会自动将该实例的类传递给 cls 参数。

3、静态方法是通过 @staticmethod 装饰器定义的方法,与类和实例无关,不能访问实例属性和类属性。静态方法与函数类似,只不过它们是定义在类中的。

4、公共方法是可以在类内外部通过实例或者类名调用的方法,不需要使用任何修饰符限制访问权限。

5、私有方法需要在方法名前添加双下划线声明为私有方法,只能在类内部被访问和使用。

因此,可以说实例方法、类方法和静态方法是三种不同的方法类型,而公共方法和私有方法是访问权限的控制,它们之间没有直接的关联。

8.3.1 实例方法、类方法和静态方法

在 Python 中,类的方法分为三类:普通方法、类方法和静态方法。

在这里插入图片描述

需要注意的是,所有的类方法和静态方法都是无法直接访问实例属性的,因为它们不需要隐式传递实例对象作为参数。

反之,实例方法可以访问和修改实例属性和类属性,因为它总是通过隐式的方式传递实例对象作为第一个参数。 在实际编程中,可以根据实际需要选择合适的方法类型来完成相应任务。

隐式传递: 在 Python 中,实例方法和类方法的第一个参数通常被命名为 self 或 cls,它们会自动传递给方法。这种参数传递的方式称为隐式传递(implicit passing),因为在调用方法时不需要显式地传递这些参数,Python 会自动处理这个过程。 例如,当你使用obj.method() 调用对象的实例方法时,Python 会自动将对象本身作为第一个参数 self 隐式传递给方法,从而让我们能够在方法中通过 self 参数访问对象的实例属性。 隐式传递在 Python 中是非常常见的,它可以帮助我们省去很多冗杂的代码,并更加方便地调用方法。 但需要注意的是,在方法定义时必须显式声明这些参数,才能保证正确的隐式传递。否则,在方法调用时可能会出现参数不匹配的错误。

1、实例方法/普通方法

实例方法/普通方法(instance method)是最常见的类方法,它的第一个参数必须是 self,代表类的实例对象。这样我们才能在方法中访问实例对象的属性和方法。比如,我们可以定义一个简单的类 Person,并在其中定义一个普通方法 greet,用于问候人:

class Person:
    def __init__(self, name):
        self.name = name

    def greet(self):
        print(f"Hello, my name is {self.name}!")

在这个例子中,我们定义了一个类 Person,并在其中定义了一个方法 greet,第一个参数为 self。在方法的实现中,我们通过 self.name 访问了实例对象的属性 name。这样,我们就可以通过下面的方式创建实例对象,并调用 greet 方法:

person = Person('Tom')
person.greet()  # 输出 "Hello, my name is Tom!"

2、类方法

类方法是指跟类相关而不是跟对象相关的方法,用 classmethod 装饰器来定义。在类方法中,第一个参数必须是 cls,它不是对象,而是类本身。类方法可以访问类的私有属性,也可以修改。

类方法(class method)是针对整个类对象进行操作的,并且第一个参数必须是 cls,代表类对象本身。可以使用类名来调用类方法,也可以使用类的实例对象来调用。比如,我们可以在 Person 类中定义一个类方法 count,用于记录已经创建的实例对象个数:

class Person:
    count = 0

    def __init__(self, name):
        self.name = name
        Person.count += 1

    def greet(self):
        print(f"Hello, my name is {self.name}!")

    @classmethod
    def get_count(cls):
        return cls.count

在这个例子中,我们添加了一个类属性 count,并在 __init__ 方法中对此进行了修改。可以看到,在类方法 get_count 中,我们通过 cls.count 访问类属性 count。这样,我们就可以通过下述方式来统计实例对象的个数:

person1 = Person('Tom')
person2 = Person('Jerry')
print(Person.get_count())  # 输出 2
class MyClass:
    class_attribute = "This is a class attribute"

    def __init__(self):
        self.instance_attribute = "This is an instance attribute"

    @classmethod
    def class_method(cls, x):
        print("This is a class method, and the class attribute is:",
              cls.class_attribute)
        print("The parameter is:", x)

MyClass.class_method(123)  
# 调用类方法,输出:This is a class method, and the class attribute 
# is: This is a class attribute  
# The parameter is: 123

3、静态方法

静态方法是指在类中定义的,不强制要求传递类或实例的方法。用 staticmethod 装饰器来定义。静态方法既不是类方法也不是实例方法,就像普通的函数一样,可以直接通过类名调用,也可以通过实例调用。

静态方法(static method)不需要 self 或 cls 参数,因此静态方法不能访问实例属性或类属性。和类方法一样,静态方法可以通过类名来调用,也可以通过类的实例对象调用。比如,我们可以在 Person 类中定义一个静态方法 add,用来将两个数字相加:

class Person:
    @staticmethod
    def add(a, b):
        return a + b

在这个例子中,我们使用 @staticmethod 装饰器将 add 方法声明为静态方法。在方法实现中,我们直接返回了两个数的和。这样,我们就可以通过下述方式调用静态方法:

print(Person.add(1, 2))  # 输出 3

总之,类的方法是定义在类中的函数,用来实现类的功能。其中,普通方法必须包含 self 参数,类方法必须包含 cls 参数,而静态方法不需要包含 selfcls 参数。

class MyClass:
    class_attribute = "This is a class attribute"

    def __init__(self):
        self.instance_attribute = "This is an instance attribute"

    @staticmethod
    def static_method():
        print("This is a static method, and the class attribute is:"
              , MyClass.class_attribute)

MyClass.static_method()  
# 调用静态方法,输出:This is a static method, and the class attribute 
# is: This is a class attribute

my_instance = MyClass()
my_instance.static_method()  
# 调用实例方法,输出:This is a static 
# method, and the class attribute is: This is a class attribute

通过上述例子可以看到,静态方法和类方法都是属于类的方法,并不依赖于类的实例化对象。但是类方法必须传递第一个参数 cls,而静态方法不需要传递类或实例的参数。

8.3.2 公共方法与私有方法
类型命名规则可见性调用方式
公有方法以小写字母开头在类的内部和外部都可以被访问使用实例对象或类对象调用
私有方法以双下划线开头仅在类的内部可以被访问只能在类的内部调用

需要注意的是,虽然 Python 中有私有方法这一概念,但并不会在语言层面上强制限制私有方法的访问,而是通过**“属性名重整”**机制来实现私有化。在类定义中,以两个下划线开头的方法名会自动在前面添加一个下划线和类名,变成一个长名称,这个名称就成为了实际的方法名。因此,如果在类的外部访问一个私有方法,需要使用重整后的名称。

在 Python 中,类的方法包括公有方法和私有方法。公有方法是指可以从类的外部直接访问和使用的方法。也就是说,公有方法的访问权限不受限制,无需特殊的访问约定。私有方法则需要在方法名前面加上双下划线 “ __ ” 声明。私有方法的访问权限只限于类内部,在类的外部无法直接访问和使用。这种访问权限的控制可以帮助我们隐藏一些类的内部实现细节,避免外部代码的非法访问和篡改。

下面是一个 Python 类的例子,其中定义了一个公有方法和一个私有方法:

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.age = age
    def show(self): # 公有方法
        print("My name is", self.__name)
        self.__say_hello()
    def __say_hello(self):# 私有方法
        print("Hello, World!")

在这个例子中,show 方法是一个公有方法,可以在类的外部直接使用。而 __say_hello 方法则是一个私有方法,只能在类的内部使用。

在调用私有方法时,需要使用特殊的语法来访问,即在方法名前面加上双下划线 “ __ ”。例如,在 show 方法中调用 __say_hello 方法可以使用以下语法:

self.__say_hello()

需要注意的是,由于 Python 中对于双下划线 ‘’ _ ‘’ 的私有属性和方法在名称前加"类名"进行了实现,所以不能直接 outside._类名__私有属性/方法 来访问。

8.3.3 类的专有方法

一个类创建的时候,本身就会包含一些方法,主要有以下方法:

**1、__ init __方法 :**构造函数,在生成对象时调用。在 Python 中,构造函数是用于初始化一个类实例的方法。它被称为构造函数的原因是,它在创建新对象时的工作类似于建筑物的构造。

构造函数是另一种特殊的方法,它使用双下划线( __ )作为前缀和后缀,并且通常被命名为__ init__。当你创建类的实例时,这个构造函数会被自动调用,用来初始化这个实例。

构造函数是一个可选的方法,所以你不需要为每个类都定义一个构造函数。如果你没有定义一个自己的构造函数,Python 将为你提供一个默认构造函数,它不做任何事情。

当你定义一个构造函数时,你可以将参数传递给它,以便在实例化新对象时设置实例的属性。通常,构造函数的参数包括 self 和其他参数。self 参数是一个指向实例本身的引用,在构造函数中用于访问实例属性和方法。

以下是一个示例狗类的构造函数的代码:

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

在这个狗类中,__ init __() 构造函数被定义为接受两个参数:名字和年龄。它使用这些参数来初始化实例属性 name 和 age。

当你创建了一个新的 Dog 实例时,你必须为这些参数提供值,如下所示:

my_dog = Dog("Pecky",12)

在这个例子中,我们将 “Pecky” 和 12 作为参数传递给构造函数,以创建一个名为 my_dog 的新 Dog 实例。使用构造函数将这些值分配给实例的 name 和 age 属性。

这就是 Python 构造函数的基本概念和用法。它允许你为类的实例提供自定义初始化逻辑,以便更好地满足你的应用程序的需求。

**2、__ del __方法:**析构函数,释放对象时使用。Python 中的析构函数是一种特殊的函数,也被称为 __ del __ 函数。它在对象被解释器销毁时自动执行,用于释放这个对象所占用的资源。当一个对象不再被引用时,Python 解释器会自动调用其析构函数。

具体来说,析构函数通常被用于清理对象的资源,比如关闭数据库连接,释放内存等。对象销毁时会自动执行析构函数,这可以避免在程序运行过程中出现资源泄露等问题。

Python 中的析构函数通常不需要手动调用。当对象的引用计数为零,也就是对象不再被引用时,析构函数会被自动调用。需要注意的是,由于 Python 的垃圾回收机制是基于引用计数的,因此循环引用可能导致析构函数无法正确执行,在处理循环引用时需要特别注意。

下面是一个简单的析构函数的示例代码:

class MyClass:
    def __del__(self):
        print("Object is destroyed")

在此示例中,当 MyClass 的对象被销毁时,Python 解释器会自动调用 __ del __ 函数并输出 “Object is destroyed”。

**3、__ repr __方法:**该方法主要实现 “自我描述” 功能——当直接打印类的实例化对象时,系统将会自动调用该方法,输出对象的自我描述信息,用来告诉外界对象具有的状态信息。但是,object 类提供的 __ repr __ ( ) 方法总是返回一个对象(类名 + obejct at + 内存地址),这个值并不能真正实现自我描述的功能!输出实例化的 Person 类对象,返回的是一个在内存中的对象。而当输出 person 实例对象时,实际上输出的是 __ repr __ 方法的返回值,也就是说下边两个输出的效果一样 :

class Person():
    def __init__(self,name,age):
        self.name = name
        self.age = age
person = Person('zk', 20)
print(person)
print(person.__repr__())

上述程序输出结果为:

<__main__.Person object at 0x00000180EFC57590>
<__main__.Person object at 0x00000180EFC57590>

因此,如果你想在自定义类中实现 “自我描述” 的功能,那么必须重写 __ repr __ 方法。

当然,类似的专有方法还有很多,我们总结之后,得到以下内容:

8.4 类的继承

在 Python 中,类的继承是指子类继承父类的属性和方法。子类可以在不重写任何父类方法的情况下,继承并执行父类中的方法。Python 中类的继承是面向对象编程中一个重要的特性,它允许我们定义一个类作为另一个类的扩展,并继承其属性和方法。

8.4.1 继承语法

继承的语法非常简单,只需要在子类定义时在类名后加上父类名即可:

class Parentsclass:
    pass
class Childrenclass(Parentsclass):
    pass

在上述示例中,ChildClass 继承了 ParentClass 的所有属性和方法。

8.4.2 继承种类

在 Python 中,类的继承有三种种类:单继承、多继承和多层继承。

类型描述继承方式应用场景
单继承一个子类只有一个直接的父类使用单个类的名称简单继承关系、子类仅需要继承一个父类的方法和属性
多继承一个子类有多个直接的父类使用多个类的名称,用逗号隔开需要从多个父类中继承不同特性,需要灵活的继承体系
抽象基类继承定义接口规范,供子类继承和实现使用 abc 模块定义抽象基类定义规范接口,约束子类的实现方式

需要注意的是,Python 语言本身不强制要求使用某一种继承方式,开发人员可以根据具体的需求和设计要求进行选择。但一般来说,单继承是最简单也最常见的继承方式,多继承需要慎重考虑,避免出现多重继承导致的命名冲突和复杂度高的问题,而抽象基类的继承则更多地用于接口和规范的定义。

1、单继承:

单继承是指一个子类只继承一个父类的属性和方法。示例如下:

class ParentClass:
    def parent_method(self):
        print("This is a method from ParentClass.")

class ChildClass(ParentClass):
    def child_method(self):
        print("This is a method from ChildClass.")

c = ChildClass()
# This is a method from ParentClass.
c.parent_method()  
# This is a method from ChildClass.
c.child_method()   

在上述示例中,ChildClass 继承了 ParentClassparent_method() 方法,并且可以在子类中访问和使用。

2、多继承:

多继承是指一个子类继承多个父类的属性和方法。在 Python 中,多继承的语法是在定义子类时指定多个父类,并用逗号分隔它们。示例如下:

class ParentClass1:
    def parent_method1(self):
        print("This is a method from ParentClass1.")

class ParentClass2:
    def parent_method2(self):
        print("This is a method from ParentClass2.")

class ChildClass(ParentClass1, ParentClass2):
    def child_method(self):
        print("This is a method from ChildClass.")

c = ChildClass()
# This is a method from ParentClass1.
c.parent_method1()  
# This is a method from ParentClass2.
c.parent_method2() 
# This is a method from ChildClass.
c.child_method()   

在上述示例中,ChildClass 继承了两个父类的属性和方法。

3、多层继承:

多层继承是指一个子类继承一个父类,而这个父类本身也是另一个父类的子类。示例如下:

class GrandParentClass:
    def grandparent_method(self):
        print("This is a method from GrandParentClass.")

class ParentClass(GrandParentClass):
    def parent_method(self):
        print("This is a method from ParentClass.")

class ChildClass(ParentClass):
    def child_method(self):
        print("This is a method from ChildClass.")

c = ChildClass()
# This is a method from GrandParentClass.
c.grandparent_method() 
 # This is a method from ParentClass.
c.parent_method()      
 # This is a method from ChildClass.
c.child_method()       

在上述示例中,GrandParentClassParentClass 的父类,而 ParentClass 又是 ChildClass 的父类。

8.4.3 访问父类的属性和方法

我们先总体上有一个认识:子类可以访问父类的公有属性、公有方法,但无法直接访问父类私有属性、私有方法。下面通过程序展示,如何访问相应的属性和方法:

class ParentClass:
    parent_public_var = "This is a public variable in parent class."
    
    __parent_private_var = "This is a private variable in parent class."

    def parent_public_method(self):
        print("This is a public method from ParentClass.")

    def __parent_private_method(self):
        print("This is a private method from ParentClass.")

class ChildClass(ParentClass):
    def child_method(self):
        # 访问父类的公有属性
        print(self.parent_public_var) 
        # 访问父类的公有方法
        print(self.parent_public_method()) 
        # 访问父类的私有属性,要加上 _ParentClass
        print(self._ParentClass__parent_private_var) 
        # 访问父类的私有方法,要加上 _ParentClass
        print(self._ParentClass__parent_private_method())
		# 访问父类的私有属性,报错
		print("5--", self.__parent_private_var)
        # 访问父类的私有方法,报错
		print("6--", self.__parent_private_method()) 

c = ChildClass()
c.child_method()

在上述示例中,我们在子类 ChildClass 中访问了父类 ParentClass 的公有属性和方法、私有属性和方法,并给出了相应的访问方式。

需要注意的是,访问私有属性和方法时需要使用类名前缀加两个下划线 “__”,否则会出现 AttributeError。但 Python 中还有一种访问方式,具体见下:

1、使用 super() 函数:

super() 函数可以用于获取当前子类继承自父类的实例,从而调用父类的方法或属性。其用法如下:

class Parent:
    def __init__(self, name):
        self.name = name
        self.age = 50

class Child(Parent):
    def __init__(self, name, grade):
        # 调用父类的 __init__ 方法
        super().__init__(name)  
        self.grade = grade

p = Child("Tom", 4)
print(p.name)  # 访问父类的属性

在子类的 __init__ 方法中,使用 super().__init__(name) 调用父类的 __init__ 方法,从而完成对父类的属性进行初始化。这种方式可以确保子类的属性和父类的属性均被正确初始化。

2、使用父类的类名:

在 Python 中,也可以使用父类的类名来调用父类的方法或属性,其用法如下:

class Parent:
    def __init__(self, name):
        self.name = name
        self.age = 50

class Child(Parent):
    def __init__(self, name, grade):
        # 调用父类的 __init__ 方法
        Parent.__init__(self, name)  
        self.grade = grade

p = Child("Tom", 4)
print(p.name)  # 访问父类的属性

在子类的 __ init __ 方法中,使用 Parent.__ init __ (self, name) 调用父类的 __ init __ 方法,完成对父类的属性进行初始化。这种方式不推荐使用,因为如果子类继承自多个父类,那么使用父类的类名访问父类的方法会比较麻烦。此时需要使用 super() 函数来代替。

8.4.4 修改父类的属性和方法

在 Python 中,如果要修改父类的属性,一般有以下两种方式:

1、直接修改父类属性:

如果父类的属性是公有(即没有使用双下划线开头),可以通过直接访问父类的属性来修改其值,例如:

class Parent:
    x = 1  # 父类属性

class Child(Parent):
    pass

p = Parent()
print(p.x)  # 输出 1

c = Child()
print(c.x)  # 输出 1

Parent.x = 2  # 直接修改父类属性值
print(p.x)  # 输出 2
print(c.x)  # 输出 2,因为 Child 类继承自
            # Parent 类,所以也会受到影响

需要注意的是,如果父类的属性是私有(即使用双下划线开头),则不能直接访问和修改该属性,因为私有属性是无法被子类继承和访问的。

2、重写父类属性:

如果不想修改父类的属性值,而是想在子类中重新定义同名的属性,可以使用方法重写(Override)的技术来实现,例如:

class Parent:
    x = 1  # 父类属性

class Child(Parent):
    x = 2  # 子类重新定义的属性值

p = Parent()
print(p.x)  # 输出 1

c = Child()
print(c.x)  # 输出 2,因为 Child 类重新定义了 x 属性

Parent.x = 3  # 修改父类属性值

print(p.x)  # 输出 3
print(c.x)  # 输出 2,因为子类重写父类属性后,不会受到父
            # 类属性值的影响

需要注意的是,如果在子类中重写了父类的属性后,实例访问该属性时,会优先使用子类的属性值,而不是父类的属性值。

如果想修改父类中的方法,我们也有两种方式:

1、重写:

如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,

Python 类中的方法重写(Method Overriding)指的是在子类中重新定义或修改从父类继承而来的方法,使得子类对象能够按照自己的需求进行特定的操作。在方法重写过程中,子类方法覆盖了父类中同名的方法,并改变了它的行为。

方法重写的常见应用场景是子类具有和父类相同的方法名和参数列表,但是含义或实现不同,这时就需要使用方法重写来覆盖父类的默认实现。

下面是一个简单的示例代码来演示方法重写:

class Animal:
    def make_sound(self):
        print("The animal makes a sound.")

class Cat(Animal):
    def make_sound(self):
        print("The cat meows.")

animal = Animal()
animal.make_sound()  # The animal makes a sound.

cat = Cat()
cat.make_sound()  # The cat meows.

在上面的代码中,Animal 类定义了一个 make_sound 方法,该方法输出了一句话“the animal makes a sound”。Cat 类继承了 Animal 类,并重写了 make_sound 方法,使得其输出了一句话“the cat meows”,这就是方法重写的示例。

总之,方法重写是一种重要的面向对象编程的特性,使得子类可以定制化自己独有的行为,并且可以覆盖父类的默认实现。

2、使用 super() 函数:

在继承中,我们可能需要在子类中重写或修改父类的方法,但是又需要保留父类原来的实现。为了实现这个目标,我们可以使用 super() 函数调用父类的方法。super() 函数可以让我们在子类中调用父类的方法,从而实现方法的重写和修改,并保留父类原来的实现。示例如下:

class ParentClass:
    def parent_method(self):
        print("This is the original implementation of parent_method.")

class ChildClass(ParentClass):
    def parent_method(self):
         # 调用父类的 parent_method() 方法
        super().parent_method()  
        print("This is the modified implementation of parent_method from ChildClass.")

c = ChildClass()
c.parent_method()

在上述示例中,ChildClass 中的 parent_method() 方法重写了父类 ParentClass 中的 parent_method() 方法,但是又调用了父类的实现,并在此基础上实现了新的行为。

8.4.5 子类的类型判断

使用 isinstance() 函数,判断 class 的类型

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class User1(object):
    pass

class User2(User1):
    pass

class User3(User2):
    pass

if __name__ == '__main__':
    user1 = User1()
    user2 = User2()
    user3 = User3()
    # isinstance()就可以告诉我们,一个对象是否是某种类型
    print(isinstance(user3, User2))
    print(isinstance(user3, User1))
    print(isinstance(user3, User3))
    # 基本类型也可以用isinstance()判断
    print(isinstance('两点水', str))
    print(isinstance(347073565, int))
    print(isinstance(347073565, str))

输出的结果如下:

True
True
True
True
True
False
8.4.5 类的多态

多态的概念是指对不同类型的变量进行相同的操作,它会根据对象(或类)类型的不同而表现出不同的行为。事实上,我们经常用到多态的性质,比如:

>>> 1 + 2
3
>>> 'a' + 'b'
'ab'

对两个整数进行 + 操作,会返回它们的和,对两个字符进行相同的 + 操作,会返回拼接后的字符串。也就是说,不同类型的对象对同一消息会作出不同的响应。

Python 中的多态是指不同的类对象通过调用相同的方法名,可以得到不同的结果。Python 之所以支持多态,是因为所有的类对象都是从 object 类派生出来的,因此默认都会有一些 magic method,例如 __ str __ 、 __ repr __ 、__ add __ 、__ lt __等,可以被调用,改变类对象的默认行为。

通过实现 Python 类的多态,可以带来很多的好处。在编写代码时,我们可以利用这个特性来提高代码的可读性、可扩展性和可维护性,从而更好地满足不同的需求。

实现 Python 类的多态需要满足两个前提条件:继承和重写。在 Python 中,继承是通过在类定义时使用括号引入父类实现的。子类继承了父类的属性和方法,在子类中可以对方法进行重写,给方法的默认实现加上一些个性化的东西。

例如,我们定义一个 Pet 类和其两个子类 Dog 和 Cat。在 Pet 类中定义了一个 speak() 方法,用于输出一个动物叫声的字符串。而在 Dog 和 Cat 子类中,我们可以通过重写这个方法,对生成的动物叫声字符串进行个性化的修改。由于 Dog 和 Cat 都继承了 Pet,所以在输出它们的叫声字符串时会有不同的表现,这就体现了 Python 类的多态。具体见下:

class Pet:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        pass

class Dog(Pet):
    def speak(self):
        return "汪汪!"

class Cat(Pet):
    def speak(self):
        return "喵喵!"

dog = Dog("小黑")
cat = Cat("花花")

print(dog.speak())  # 输出:汪汪!
print(cat.speak())  # 输出:喵喵!

注意:

1、父类定义的方法名不能和子类中的方法名相同,否则会被子类中的定义覆盖。

2、子类实例调用方法时,将会优先调用子类中自己的方法,如果子类中没有实现,才会从父类中寻找。

类的多态的优点:

1、提高代码的灵活性和可扩展性,使得同一段代码可以适用于不同的类实例,从而更好地满足不同的需求。

2、可以减少代码重复,提高代码的可维护性和可读性。

3、可以降低程序的复杂度,提高程序的可理解性。

第 九 章

文件和异常

9.1 读取文件
9.1.1 读取文件的全部内容

要读取一个文件,需要一个包含若干行的文本文件。先创建一个文件如下:

文件名:test.txt ⟶ \longrightarrow 内容:3.1415926535
8979323846
2643383279

将这个文件保存到所要使用的程序所在的目录中。下面用这个程序打开并读取文件:

from pathlib import Path
path = Path('test.txt')
contents = path.read_text()
print(contents)

那么最终的输出结果为:

3.1415926535
  8979323846
  2643383279

要使用文件的内容,首先要把路径告诉 Python。路径(path)指的是文件或文件夹在系统中的准确位置。Python 提供了 pathlib 模块来方便操作。从 pathlib 模块导入 Path 类。Path 对象指向一个文件,可以用来核实他是否存在,读取文件的内容,以及将新数据写入文件。使用 read_text()方法读取文件的全部内容。read_text()将该文件的全部内容转换为一个字符串返回。因此,在最后输出 contents 的内容时,输出的是文件的全部内容。

但仔细看会发现,该输出不同之处在于结尾多了一个空行。这是因为 read_text()在到达文件末尾时会返回一个空字符串,而这个空字符串会显示为一个空行。若是想要删除这个空行,可以使用 rstrip()。

contents = path.read_text().rstrip()
9.1.2 相对文件路径与绝对文件路径

在编程中,指定路径有两种方式。第一种,相对文件路径让 Python 到相对于当前运行程序的所在目录的指定位置去查找。由于文件夹 text_files 位于文件夹 pathon_work 中,因此需要创建一个以 text_files打头并以文件名结尾的路径,如下所示:

path = Path('test_files/filename.txt')

第二种,绝对文件路径让 Python 在整个计算机中寻找文件的准确位置。下面是文件绝对路径的表示方式:

path = Path('/home/eric/data_files/test_files/filename.txt')
9.1.3 访问文件中的各行

我们可以使用 splitlines()方法将一长串字符串转换为一系列行,再使用 for 循环以每次一行的方式检查文件中的各行。

from pathlib import Path
path = Path('test.txt')
contents = path.read_text()
lines = contents.splitlines()
for line in lines:
    print(line)

splitlines()方法返回一个列表,其中包含文件中所有的行,而我们将这个列表赋给变量 lines,然后,遍历列表并打印它们:

3.1415926535
  8979323846
  2643383279
9.1.4 使用文件的内容

想要使用文件内容首先就从字符串开始,先看以下程序:

from pathlib import Path
path = Path('test.txt')
contents = path.read_text()

lines = contents.splitlines()
pi_string = ''
for line in lines:
    pi_string += line
    
print(pi_string)

上述程序的输出结果为:

3.1415926535 8979323846 2643383279

不难发现,变量 pi_string 存储的字符串包含原来位于每行左端的空格。要删除这些空格,可对每行调用 lstrip()。这样就获得了完整的字符串,其中包含准确到30位小数的圆周率值。如果想把文件中的内容解释为数值,可以使用 int ( ) 函数或是 float ( ) 函数。

9.2 写入文件
9.2.1 写入一行

写入一行程序可以使用 write_text ( ) 将数据写入该文件了。首先我们说明 write_text 的几点幕后工作原理。一、如果 path 变量对应的路径指向的文件不存在,就创建它。二、将字符串写入文件后,它会确保文件得以妥善地关闭。再来看以下程序:

from pathlib import Path
path = Path('programming.txt')
path.write_text("I love Programming")

这样就向文件中输入了一行数据 I love programming。注意 Python 只能将字符串写入文件。如果要将数据存储到文本文件中,必须先使用函数 str ( ) 将其转换为字符串形式。

9.2.2 写入多行

如果想要写入多行数据可以先创建一个需要导入的字符串,其中使用了转义字符 \n。例如:

from pathlib import Path

contents = 'I love programming.\n'
contents += 'I love creating new games.\n'
contents += 'I also love working with data.\n'

path = Path('programming.txt')
path.write_text(contents)

这样最后写入文件的内容就是多行的。

9.3 异常的处理

Python 对一些程序执行过程中的错误定义为异常(exception)的特殊对象。如果我们编写了处理该异常的代码,程序将继续运行;如果我们未对异常进行处理,程序将停止,并显示一个 traceback,其中包含对异常的报告。

9.3.1 使用 try - except 代码块

当我们认为这一段程序可能发生错误时,可编写 try - except 代码块来处理可能引发的异常。例如,如果在程序中将数除以0,会导致异常,但我们可以用 try - except 代码块来告诉 Python,如果发生了异常,应该怎么处理。

try:
    print(5/0)
except ZeroDivisionError:
    print("You can't divide by zero!")

这样,如果 try 代码块中的代码运行没有问题,Python 将会跳过 except 代码块;如果 try 代码块中的代码导致错误,Python 将查找与之匹配的 except 代码块并运行其中的代码。

9.3.2 else 模块

当有部分代码只有在 try 代码块中的代码正确执行后才需要执行时,我们就可以把这部分代码放到 else 模块中。这样不会让用户看到 traceback 并且减少了不确定的危机。具体示例见下:

#按照空格划分输入的两个整数
first_number,second_number = map(int,input().split())
try:
    answer = first_number / second_number
except ZeroDivisionError:
    print("You can't divide by zero!")
else:
    print(answer)
9.3.3 检查文本

在使用文件中,一种常见的问题是找不到文件,可能这个文件不存在,或是这个文件存储在别的位置。例如:

from pathlib import Path
path = Path('noexist.txt')
contents = path.read_text(encoding = 'utf-8')

由于文件不存在,Python 会返回 trackback,大体如下:

Traceback (most recent call last):
  File "D:\python\test.py", line 3, in <module>
    contents = path.read_text(encoding = 'utf-8')
  File "C:\Users\hxr\AppData\Local\Programs\Python\Python312\Lib\pathlib.py", line 1027, in read_text
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
  File "C:\Users\hxr\AppData\Local\Programs\Python\Python312\Lib\pathlib.py", line 1013, in open
    return io.open(self, mode, buffering, encoding, errors, newline)
FileNotFoundError: [Errno 2] No such file or directory: 'noexist.txt'

阅读 traceback,我们首先从最后一行看,FileNotFoundError 是异常的类型,再看第二行告诉我们异常的位置,再下面一行是具体哪句代码出现了问题,我们发现是读取全部文件内容出了问题,也就是说不能读取内容。那么为了处理这个异常,我们将代码改成如下:

from pathlib import Path
path = Path('noexist.txt')
try:
    contents = path.read_text(encoding = 'utf-8')
except FileNotFoundError:
    print("Sorry,the file noexist.txt does not exist.")

这样就不会有 traceback 出现,隐藏了部分可能的信息泄露。

9.3.4 静默失败

在上一个示例中,我们告诉了用户有一个文件找不到。但有时我们希望程序在发生异常时保持静默,什么也不做,那么就会用到 pass 语句,具体见下:

from pathlib import Path
path = Path('noexist.txt')
try:
    contents = path.read_text(encoding = 'utf-8')
except FileNotFoundError:
    pass

这样在出现错误时,什么也不会发生。

9.4 存储数据

在许多时候,我们需要在用户关闭程序时,保存他们提供的信息。一种简单的方式就是使用模块 json 来存储数据。模块 json 使得可以将简单的 Python 数据结构转换为 JSON 格式的字符串,并在程序再次运行时从文件中加载数据。模块 json 作为一种轻量级数据格式,不仅很有用,也易于学习。

9.4.1 使用 json.dumps() 和 json.loads()

下面两个程序我们分别使用 json.dumps() 来存储一组数据,再使用 json.loads() 来读取它们。具体如下:

from pathlib import Path
import json

numbers = [1,2,3,4,5,6]

path = Path('numbers.json')
contents = json.dumps(numbers)
path.write_text(contents)

这个程序导入 json 模块后,创建了数值列表。并指定要将其存储到文件中,使用拓展名 .json 来指出文件存储的数据为 JSON 格式。接下来,使用 json.dumps ( ) 函数生成一个字符串,最后将其写入文件中。下面我们再写一个程序,使用 json.loads ( ) 来将这个列表读取到内存中:

from pathlib import Path
import json

path = Path('numbers.json')
contents = path.read_text() 
numbers = json.loads(contents)

print(numbers)
9.4.2 一个程序保存和读取用户生成的数据

本来可以用 try - except 代码块,以便在文件不存在时采取合适的措施,但其实可以不这样做,我们利用 pathlib 模块提供的一个便利方法。具体见下:

from pathlib import Path
import json

path = Path("username.json")
if path.exists():
    contents = path.read_text()
    username = json.loads(contents)
    print(f"Welcome back,{username}!")
else:
    username = input("What's your name?")
    contents = json.dumps(username)
    path.write_text(contents)
    print(f"We'll remember you when youu come back,{username}!")

如果指定文件或文件夹存在,exist ( ) 方法返回 True,否则返回 False。这里使用 path.exist ( ) 来确定是否存储了用户名。

第 十 章

海龟库绘图

10.1 理解海龟

turtle 库又名海龟库,1969年诞生,主要用于程序设计入门的一种绘图方式,是python语言标准库之一,入门级的图形绘制函数库。

之所以把它称为海龟库,是因为它的绘图功能是通过把落笔点想象成一个海龟,把绘图窗口想象成一片海洋,那么海龟绘图的过程就是海龟的运动轨迹。海龟由程序控制,可以变换颜色、改变宽度等。这便是海龟库的原理。

由于绘图功能主要是以实践为主,下面将一次给出绘图所用的函数,简要介绍其功能后,需要自行寻找案例进行练习。

10.2 绘图函数分类展示
10.2.1 绘图函数窗体布局

在导入 turtle 库之后,我们首先要创建的就是绘图的窗体布局,使用 setup ( ) 函数。

turtle.setup(width,height,startx,starty)

其中的四个参数含义如下:

而有两个特殊位置:turtle.setup (800,800,0,0) 位于屏幕的左上角,turtle.setup (800,800) 位于屏幕的正中心。

10.2.2 绘图坐标体系

在绘图之前,我们还要了解海龟库绘图的坐标体系,这样才能更好地控制海龟的移动。

**1、空间坐标体系 · 绝对坐标:**在开始绘画前,海龟在正中心,为(0,0)。使用函数 turtle.goto (x, y) 表示让此时处在任意位置的海龟到达 (x, y) 位置上。

**2、空间坐标体系 · 海龟坐标:**对于海龟的当前行进方向无论朝向哪个方向都叫做前进方向,反方向是后退方向,海龟运行的左侧叫左侧方向,运行的右侧叫做右侧方向

**3、角度坐标体系 · 绝对角度:**水平向右为 0 o 0^o 0o ,使用函数 turtle.seth (angle) 实现此时处于任意方向的海龟转向 angle 角度。

**4、角度坐标体系 · 海龟角度:**使用函数 turtle.left(angle) 使海龟向左旋转 angle;使用函数 turtle.right(angle) 使海龟向右旋转 angle。

10.2.3 RGB绘图体系

RGB色彩模式,是指由三种颜色构成的万物色。RGB指红绿蓝三个通道的颜色组合,覆盖视力所能感知的所有颜色 RGB没色取值范围0-255整数或0-1小数。下面是一些常用的 RGB色彩系数:

10.2.4 画笔控制函数

1、绘图状态:

1、pendown ( ):落下画笔,意味着接下来海龟的移动显示在窗体上。

2、penup ( ):抬起画笔,意味着接下来海龟的移动不再显示。

3、pensize ( ):画笔的宽度,画笔设置后一直有效,直至下次重新设置。turtle.pensize(width) 别名为 turtle.width(width)。

2、颜色控制:

1、color ( ):颜色

2、pencolor ( ):修改画笔颜色的函数,color 为颜色字符串或r,g,b值

3、fillcolor ( ):填充颜色

3、填充: 4、更多绘图控制:

1、filling() 是否填充 1、reset() 重置

2、begin_fill() 开始填充 2、clear() 清空

3、end_fill() 结束填充 3、write() 书写

10.2.5 运动控制函数

1、forward() 前进 2、backward() 后退

3、right() 右转 4、left() 左转

5、goto() 前往定位 6、setx() 设置 x 坐标

7、sety() 设置y坐标 8、seth() 设置朝向

9、home() 返回原点 10、circle() 画圆

11、dot() 画点 12、stamp() 印章

13、clearstamp() 清除印章 14、clearstamps() 清除多个印章

15、undo() 撤消 16、speed() 速度

10.2.6 海龟状态函数

1、position() 位置 2、towards(x, y) 目标与当前位置的绝对角度

3、xcor() 当前位置 x 坐标 4、ycor() 当前位置 y 坐标

5、heading() 当前海龟朝向 6、distance (x, y) 当前位置与 (x, y) 的距离

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值