Python语言入门教程

(注:此文章为本人所参与的某学生团队培训大一新生时所用,面向零基础同学,因此没有提及较进阶的内容,以及内容可能不是那么专业。)

第一章 Python语言编程基础

在前面的C语言学习中,我们已经了解了编程的一些基本知识,如位运算、函数等。C语言所培养的逻辑思维和问题解决能力,使我们在学习Python时能够更加高效和自信。

在开发中,Python的强大之处就在于丰富的第三方库,这些库极大地扩展了语言的功能和应用范围。例如,NumPy和Pandas为数据处理和分析提供了强大的工具,PyTorch则支持机器学习和深度学习的开发。Flask和Django则可以让新手很快写出Web页面。这些库不仅简化了复杂任务的实现,还通过高效的预构建功能,帮助开发者节省时间和精力。借助这些资源,开发者可以快速构建和原型化项目,从而加速开发进程,并专注于业务逻辑而非底层实现。这种生态系统的丰富性使得Python在数据科学、人工智能和Web开发等领域成为首选语言。

1.1 Python的安装

Python的安装非常简单,你可以直接在官网上下载最新版本的安装包,点击运行安装即可。

Python官方网站

安装之后开始菜单会多出3个图标,从上到下分别是IDLE(Python提供的一个小型IDE)、一些库函数文档和Python终端。

alt text

与C语言不同的是,Python是解释性语言,没有编译链接的过程。这意味着其代码在运行时由解释器逐行翻译和执行,而不是在编译阶段生成机器代码。这种特性使得Python具有较高的灵活性和可移植性,开发者可以直接在交互式环境中测试和调试代码。

1.2 Python用作计算器

你可以在Python终端中输入一些简单的代码,来执行一些简单的操作。如基本的加减乘除甚至是位运算。

Python终端中,每输入一句语句之后按回车即可执行语句,对于复合语句则需要输入多行语句。

alt text

注意,与C语言不同的是,Python中除号返回的结果总是浮点数。即使像4/2这种能整除的,最后也会返回2.0。在Python中想要C语言那样的整除运算需要用“//”(对,就是C语言的注释符号)

alt text

另外,Python可以用双乘号计算次方,如2**16所代表的就是2的16次方。

alt text

1.3 Python中的变量以及数据类型

C语言是静态类型语言,每个变量的数据类型在编译时确定,不能改变;而Python是动态类型语言,每个变量的数据类型在运行时确定,可以通过赋值语句改变。

因此Python中定义变量时,不需要指出变量的类型,只需要一句赋值语句即可。当变量被赋值什么数据类型,这个变量在此时就是什么数据类型,就可以执行相应的操作。

我们可以使用type函数来检测变量的类型,这常用于函数中对形参类型的检测。

在使用变量时我们需要检查类型,以及注意在使用前这个变量被定义过。当然,非必要的时候不要去改变一个变量的数据类型。

alt text

Python中的数据类型主要包括数字类型(整数int、浮点数float和复数complex),字符串类型 (str),布尔值 (bool),以及序列类型(包括列表list、元组tuple和范围range)。此外,还有集合类型(如集合set)、映射类型(字典dict),以及表示空值的 NoneType。

Python中的整数没有范围限制,Python的int类型可以根据需要动态扩展。当整数的值超出当前内存的限制时,Python会自动分配更多内存来存储更大的数值。

Python中用单引号或者双引号的都是字符串,‘abc’和”abc”完全等效,此外还有三个引号开头的字符串,这种字符串可以多行存储,注意的是,它会存储每一行的换行符。

1
2
3
4
5
6
7
8
a = """
Hello
World
"""

b = "\nHello\nWorld\n"

print(a==b)

输出True。

Python中bool值为True和False,注意首字母要大写。此外还有一个特殊的值None,代表空值,逻辑上也为假。

其他的数据类型将在后面详细说明。

1.3.1 Python中变量存储原理

本节内容将在函数章节介绍。

1.4 Python的注释

在Python中,我们使用单行井字号(#)来代表单行注释,使用三个引号开头的字符串代表多行注释。

(#)开头的单行注释会被解释器忽略掉,而三引号开头的多行注释放在函数或者类内部最开始的部分将会被当做函数或者类的简要说明。

1
2
3
4
5
# 这是单行注释

'''
这是多行注释
'''

1.5 第一个Python程序

在刚才的学习中,我们都是在用解释器终端输入语句执行简单任务,然而程序的代码数量十分巨大,我们需要将代码保存到文件中使用。

在Python控制台中,你可以直接输入变量名+回车获取变量的值,但在py代码文件中你需要使用print()函数获取所需要的内容。本教程中,语句前面带有“>>>”说明本例子在控制台运行,不带这个符号的例子是在代码文件中编写的。

Python官方给我们提供了一个简单的IDE,我们打开IDLE,点击File->New File

alt text

输入代码后点击保存,选择保存位置

alt text

之后我们就可以点击Run或者F5运行我们的代码

alt text

另外我们也可以在Windows终端(cmd或者PowerShell)中切换到py文件目录,用解释器跑我们的代码。

1
python hello.py

1.6 PyCharm的安装

虽然IDLE具备基本的IDE功能,很多教程也是建议新手使用这个学习。但是它并没有代码补全功能,界面也十分简陋。我推荐直接使用PyCharm来学习Python,因为反正项目开发的时候也要用PyCharm。

直接在官网下载PyCharm Community版,社区版是免费使用的。

下载 PyCharm Community

建议将这些全部勾选。

alt text

进入PyCharm后我们点击新建项目,选择一个路径即可。

alt text

PyCharm会对每一个Python项目生成独立的虚拟环境,主要是为了实现依赖隔离,避免不同项目之间的库和依赖冲突。这样可以确保每个项目在干净且一致的环境中运行,从而减少环境差异导致的问题。同时,这种方法提升了项目的可迁移性,因为只需复制项目文件及其虚拟环境即可确保所需依赖一同带走。

之后我们在项目名称这里右键点击新建Python文件。

alt text

如果说需要调节字体的话点击文件->设置

alt text

选择编辑器->字体,改变字号即可。

alt text

写完代码后点击右上角的Run即可。

alt text

第二章 字符串、列表、输入输出函数、基本控制语句

为了介绍Python的流程语句,本章先简单介绍Python的字符串和列表的基本操作。在后面的章节我们再详细介绍这两种数据类型的进阶操作。

2.1 字符串

在Python中,无论用单引号还是双引号包裹起来的一个或多个字符,都是字符串

与C语言不同的是,Python的字符串具有许多灵活的操作,比如拼接、比较等。字符串中的“\”代表转义,与C语言的转义字符相同。

2.1.1 字符串的拼接

在Python中,可以使用加号来对两个字符串进行拼接,比如:

1
2
3
>>> s = "abc"
>>> s + "def"
'abcdef'

此外,字符串可以用乘号以及某个数字(*x)表示将字符串重复多次,比如:

1
2
3
4
>>> s = "a"
>>> t = s*10
>>> t
'aaaaaaaaaa'

当然,这两种方式可以复合使用:

1
2
3
4
>>> s = "ab"
>>> t = s*10 + "def"
>>> t
'ababababababababababdef'

需要注意的是,字符串拼接的时候,要求两个拼接元素都是字符串。如果需要将其他类型的元素转化为字符串,需要使用str()函数。

1
2
3
4
>>> n = 100
>>> s = "We have " + str(n) + " apples."
>>> s
'We have 100 apples.'

在Python中,几乎所有类型的变量都可以用str函数转化为字符串,因为很多类都有或者重载了__str__()方法。

2.1.2 字符串的比较

在Python中,字符串的比较可以用双等号、大于号、小于号、大于等于号、小于等于号、不等于号来进行比较。不过最常用的还是双等号和不等号。

1
2
3
4
5
6
7
8
9
>>> s = "abc"
>>> t = "abc"
>>> k = "abd"
>>> s == t
True
>>> t != k
True
>>> s == k
False

2.1.3 字符串的索引和切片

可以使用len()函数获取某个字符串的长度

1
2
>>> len("abcdef")
6

字符串支持索引(下标访问),第一个字符的索引是0。最后一个字符下标为 len(str) - 1。

1
2
3
4
5
>>> s = "Python"
>>> s[0]
'P'
>>> s[len(s)-1]
'n'

在使用索引时,注意不要超过索引范围,否则就会报错:

1
2
3
4
5
>>> s = "Python"
>>> s[100]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range

此外,索引还支持负数,从-1开始计数。用负数索引时,从右边开始计数,右面第一个元素为-1,左面第一个元素则为-len(str)。

1
2
3
4
5
>>> s = "Python"
>>> s[-1]
'n'
>>> s[-len(s)]
'P'

Python的字符串还支持切片,即获取子字符串。格式为:

1
[a:b]   # 从索引a(包含)到索引b(不包含)的字符组成的子串
1
2
3
>>> s = "Python"
>>> s[1:3] # 包含索引1的字符但不包含索引3的字符
'yt'

如果冒号前的数字省略则默认为0,如果冒号后的数字省略则默认为len(str)

1
2
3
4
5
>>> s = "Python"
>>> s[:3]
'Pyt'
>>> s[1:]
'ython'

切片的索引也可以用负数,但是切片的方向不会发生改变(负数索引仅代表位置)。

1
2
3
>>> s = "Python"
>>> s[-3:] # 从倒数第三个字符开始到最后一个字符
'hon'

Python的字符串不可以通过索引进行修改,因为索引返回的只是长度为1的字符串。

1
2
3
4
5
>>> s = "Python"
>>> s[0] = 'p'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

但是可以通过切片+拼接的方式实现字符串的修改:

1
2
3
4
>>> s = "Python"
>>> s = 'p' + s[1:]
>>> s
'python'

2.1.4 字符串的格式化

在Python中,字符串可以这样插入其他变量:

1
2
3
4
>>> n = 10
>>> sum = 100
>>> f"the value of n is {n}, the value of sum is {sum}"
'the value of n is 10, the value of sum is 100'

在字符串的引号前面加上小写字母f,代表这个字符串需要格式化,之后在字符串内部可以用花扩号加上变量名代表要插入的数据。如果字符串前不加小写字母f,则会忽略花扩号:

1
2
3
4
>>> n = 10
>>> sum = 100
>>> "the value of n is {n}, the value of sum is {sum}"
'the value of n is {n}, the value of sum is {sum}'

2.2 列表

Python的列表可以存放多个数据,用方括号(中括号)包裹,用逗号将每个元素分隔开。列表中的元素数据类型可以不同,但习惯上我们将相同的数据类型放在一个列表中。

可以用list()创建一个空表,也可以直接用方括号加元素创建列表。

1
2
3
>>> l = list()      # 创建空列表
>>> l = [] # 创建空列表
>>> l = [1,2,3,4] # 创建带4个数字的列表

和字符串一样,列表也支持索引和切片:

1
2
3
4
5
>>> l = [1,2,3,4]
>>> l[2]
3
>>> l[1:3]
[2, 3]

列表也支持使用加号合并:

1
2
3
4
>>> l = [1,2,3,4]
>>> l += [5,6]
>>> l
[1, 2, 3, 4, 5, 6]

与字符串不同的是,列表可以通过索引修改某个位置的元素的值:

1
2
3
4
>>> l = [0,2,3,4]
>>> l[0]=1 # 修改0号元素的值
>>> l
[1, 2, 3, 4]

Python中的简单赋值绝不会复制数据。当你将一个列表赋值给一个变量时,该变量将引用现有的列表。因此在传值整个列表的时候一定要注意这个问题

1
2
3
4
5
>>> l1 = [1,2,3,4]
>>> l2 = l1 # l2其实是l1的一个引用
>>> l1[0] = 233 # 修改l1的0号元素
>>> l2 # 发现l2也发生了改变
[233, 2, 3, 4]

如何解决这个问题,我们将在函数章节说明。

len()函数可以返回列表中元素的个数:

1
2
3
>>> l = [1,2,3,4]
>>> len(l)
4

列表的元素可以是任意的数据类型,当然也包含列表类型,构成嵌套列表:

1
2
3
4
5
6
7
>>> l = [[1,2,3,4], ['a','b','c'], 12.34]
>>> l[0][1]
2
>>> l[1][2]
'c'
>>> l[2]
12.34

可以用关键字in判断某一个元素是否在列表中:

1
2
3
4
5
>>> l = [1,2,3,4]
>>> 1 in l
True
>>> 5 in l
False

2.3 输入输出函数

在Python中,可以使用print()函数输出字符串,print默认带一个换行,函数的形参可以不是字符串,只要它可以转化为字符串。

1
2
3
4
5
6
7
8
9
>>> print("Hello!")
Hello!
>>> print("Hello!\n") # 多输出一个换行
Hello!

>>> print(23.4)
23.4
>>> print([1,2,3,4]) # 打印列表
[1, 2, 3, 4]

print()函数除了接受一个字符串外,还可以给它的形参end传递一个字符串,代表输出结束后再输出end字符串作为末尾标志。这个形参默认为’\n’。

1
2
>>> print("Hello", end="123")
Hello123>>> # 会发现没有换行了

在Python中,使用input()函数从控制台读取一行数字,遇到换行才结束。input()函数返回的是一个字符串,需要使用int()或float()函数将字符串手动转化为整数或者浮点数。

1
2
3
4
5
6
7
>>> x = input() # 下面一行是键盘的输入
123
>>> x # x是字符串
'123'
>>> y = int(x)
>>> print(y*2)
246
1
2
3
4
>>> x = input()
123 456 7 8 9
>>> x
'123 456 7 8 9'

input()函数可以传入一个字符串,此时input()函数将先输出传入的字符串,再等待输入。

1
2
3
4
5
n = input("Please enter n:")

# 相当于:
print("Please enter n:", end='')
n = input()

2.4 基本控制语句

本节将简单介绍Python中的流程控制语句——条件语句与循环语句。

2.4.1 Python使用缩进定义代码块的范围

C/C++、Java等其他语言,使用花扩号来定义代码块的范围。然而Python是特殊的,Python使用缩进定义代码块的范围。缩进在 Python 中起着至关重要的作用,因为它决定了代码的逻辑结构。

在Python代码文件中,通常用4个空格或者一个制表符(按TAB键)作为一个缩进单位。同一个代码文件必须使用相同的缩进单位

每条语句属于哪部分完全是靠缩进区分的,因此不能像C语言那样压行写代码。正确的缩进不仅可以避免语法错误,还可以提高代码的可读性。

举个例子:

1
2
3
4
5
for i in range(100):
if i % 2 == 0: # 4个空格,代表属于for循环下的语句
print(str(i) + " is even") # 8个空格,属于if语句
else: # 4个空格,属于for语句
print(str(i) + " is odd") # 8个空格,属于else语句

2.4.2 Python中的真值和假值

在Python中,None、False、0、空字符串、空列表、空元组、空字典和空集合为假值,其他的为真值。

2.4.3 pass、and、or、not关键字

在Python中,pass是一个空语句的占位关键字,代码执行到此处什么都不执行,常用于语法上需要语句但程序不需要执行任何操作。Python的函数定义、类定义、条件语句和循环语句语法上都需要语句,如果你不想执行任何操作,那么就可以用pass语句占位。

在Python中,表示多个条件的复合关系需要用到与(and)、或(or)、非(not)关键字。这些关键字用于条件连接,并不是位运算。运算结果与C语言的(&& || !)一样。

cond1 cond2 cond1 and cond2
False False False
False True False
True False False
True True True
cond1 cond2 cond1 or cond2
False False False
False True True
True False True
True True True
cond1 not cond1
False True
True False

2.4.4 if条件语句

条件语句由if——elif——else组成,基本格式为:

1
2
3
4
5
6
7
8
9
10
11
12
if condition1:
# 满足条件1
pass
elif condition2:
# 不满足条件1,满足条件2
pass
elif condition3:
# 不满足条件1、2,满足条件3
pass
else:
# 不满足条件1、2、3
pass

例如,用户输入成绩,根据成绩进行分级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 从用户输入成绩
score = int(input("Please enter your score: "))

# 判断成绩等级
if score >= 90:
print("Grade: A")
elif score >= 80:
print("Grade: B")
elif score >= 70:
print("Grade: C")
elif score >= 60:
print("Grade: D")
else:
print("Grade: F")
1
2
Please enter your score: 59
Grade: F

同样的,Python也支持条件嵌套,但一定要注意缩进值

还是举这个例子:某动物园购票,分学生票、老年票、儿童票和成人票。如果顾客能够拿出老年证或者学生证,就可以购买相应的票。如果没有相关证件,则根据年龄进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 提示用户输入年龄
age = int(input("Enter your age: "))

# 提示用户是否有学生证
has_student_id = int(input("Do you have a student ID? (1 for Yes, 0 for No): "))

# 提示用户是否为老年人
is_senior = int(input("Are you a senior citizen? (1 for Yes, 0 for No): "))

# 检查年龄是否合法
if age > 0:
if is_senior:
print("You are eligible for a senior discount.")
elif has_student_id:
print("You are eligible for a student discount.")
else:
if age < 18:
print("You are eligible for a child discount.")
elif age <= 64:
print("You are eligible for a standard adult ticket.")
else:
print("You are eligible for a senior discount.")
else:
print("Invalid age entered.")

2.4.5 while循环语句

while循环语句的结构为:

1
2
3
while cond:
# 循环执行直到cond为假值
pass

例如,计算1+2+3+…+n的和:

1
2
3
4
5
6
7
8
# 初始化总和和当前数字
sum = 0
number = 1
n = int(input("Enter a positive integer: "))
while number <= n:
sum += number
number += 1
print(f"The sum of numbers from 1 to {n} is: {sum}")

2.4.6 for迭代语句

Python的for语句与C语言不同,Python的for循环语句用来枚举列表、字符串等一些有序序列的元素,用法为:

1
2
for 元素 in 可迭代序列:
pass

比如,枚举某个列表存放的字符串,并输出它以及长度:

1
2
3
lang = ["CPP", "Python", "Java", "Assembly", "C"]
for s in lang:
print(f"{s} {len(s)}")

输出为:

1
2
3
4
5
CPP 3
Python 6
Java 4
Assembly 8
C 1

也可以用来枚举字符串中的每个字符:

1
2
3
4
5
s = "Hello World!"
i = 0
for c in s:
print(f"index of {i} is: {c}")
i += 1
1
2
3
4
5
6
7
8
9
10
11
12
index of 0 is: H
index of 1 is: e
index of 2 is: l
index of 3 is: l
index of 4 is: o
index of 5 is:
index of 6 is: W
index of 7 is: o
index of 8 is: r
index of 9 is: l
index of 10 is: d
index of 11 is: !

2.4.7 range()函数

内置函数range()用于生成等差数列:

1
2
for i in range(5):
print(i)
1
2
3
4
5
0
1
2
3
4

生成的序列不包含终点。

range函数可以传一个数字、两个数字或者三个数字。(原理:函数形参的默认值)

当range只接受一个参数n时,代表生成0n-1的序列,当接受两个参数a与b时,生成ab-1的序列,当接受第三个参数c时,代表生成的序列每两项的差值为c:

1
2
3
4
5
6
7
8
9
print("range(5)")
for i in range(5):
print(i)
print("range(2,5)")
for i in range(2, 5):
print(i)
print("range(2,10,2)")
for i in range(2, 10, 2):
print(i)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
range(5)
0
1
2
3
4
range(2,5)
2
3
4
range(2,10,2)
2
4
6
8

2.4.8 break和continue语句

与其他编程语言一样,break代表直接中止循环,continue代表继续下一轮的循环。

在循环体执行过程中,如果遇到了break语句,那么就中断当前的循环并跳出循环体。如果break出现在嵌套循环中,只会退出当前所在的循环层级。

例如,下面是一个简单的程序,让用户猜测预设的数字,程序会提示用户猜测的数字是“太大”还是“太小”,直到用户猜对为止。(这个在C语言教程中提到过)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 预设的数字
secret_number = 42
guess = None
print("Guess the number (between 1 and 100):")
# 无限循环,直到猜对数字
while True:
guess = int(input())
if guess < secret_number:
print("Too low! Try again:")
elif guess > secret_number:
print("Too high! Try again:")
else:
print("Congratulations! You've guessed the right number.")
break # 猜对了,退出循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Guess the number (between 1 and 100):
100
Too high! Try again:
50
Too high! Try again:
25
Too low! Try again:
37
Too low! Try again:
43
Too high! Try again:
40
Too low! Try again:
41
Too low! Try again:
42
Congratulations! You've guessed the right number.

continue语句用于跳过当前循环中的剩余部分,并直接进入下一次迭代。它常用于控制循环的执行流程,跳过不需要处理的特定条件。

例如,打印一个列表中的所有偶数:

1
2
3
4
5
6
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print("Even numbers in the list:")
for number in numbers:
if number % 2 != 0:
continue
print(number)
1
2
3
4
5
6
Even numbers in the list:
2
4
6
8
10

2.4.9 循环的else子句

在for或while循环中可能对应一个else子句。如果循环在未执行break的情况下结束,将会执行else子句的内容。

1
2
3
4
5
while cond:
pass
else:
# 只有while循环结束时未执行break才会执行这部分
pass

比如说,寻找2到10以内的质数:

1
2
3
4
5
6
7
8
9
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(f'{n} is not a prime number')
# 执行break,不执行for语句的else
break
else: # 注意这个else属于第二个for,而不是if的
# range(2,n)都不能整除n,说明n没有因子
print(f'{n} is a prime number')
1
2
3
4
5
6
7
8
2 is a prime number
3 is a prime number
4 is not a prime number
5 is a prime number
6 is not a prime number
7 is a prime number
8 is not a prime number
9 is not a prime number

第三章 函数和模块

3.1 Python中变量的存储方式

在高级语言中,变量是内存数据以及其地址的代表。C语言中,我们的变量可以直接存储数据,并且每个变量都有其唯一的地址,称之为指针。C语言这样直接把变量的数据存储在变量区的方式叫做值语义。在C语言中,哪怕是指针变量,也是直接存储的值。(指针变量存储的就是一个地址信息)

而在Python中,变量的存储方式是通过对象引用来实现的。这种存储方式叫引用语义。

在理解Python的变量存储方式前,需要了解两个事实:

  • 一切都是对象:在Python中,所有的数据类型(包括基本数据类型如整数、浮点数、字符串等)都是对象。每个对象都有一个类型和一个值。
  • 对象引用:变量实际上是对对象的引用,而不是直接存储数据值。赋值操作只是在变量与对象之间建立了一个引用关系。

以基本数据类型举例(不可改变的对象)

不可改变的对象,如整数、浮点数、字符串和元组。当这些对象的值被改变时,实际上是创建了一个新的对象,原来的对象不受影响。

不可改变的对象传递给变量的时候,你可以简单的认为他是值传递的,跟C语言值语义变量在效果上是一样的。

当你给一个变量赋值时,比如 a = 10,Python会创建一个整数对象10,然后让变量a指向这个对象。

如果你再做b = a,此时b也会引用同一个对象(即整数10),而不是创建一个新的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 不可变对象
a = 10
b = a # b 也引用整数 10
print(id(a), id(b)) # 两者的 id 相同

a += 1 # a 的值改变
print(a) # 输出 11
print(b) # 仍然输出 10,因为 b 仍然引用原来的对象

def swap(a, b):
tmp = a
a = b
b = tmp

a = 2
b = 3
swap(a, b)
print(a, b) # a和b仍然为2和3,因为a仍然指向2的引用,b仍然指向3的引用

以列表举例(可以改变的对象)

可以改变的对象,如列表、字典和集合。当这些对象的内容改变时,原对象仍然存在,内容被修改的是同一个对象。因此在传递引用给函数形参的时候就会出现问题:

1
2
3
4
5
# 可变对象
list1 = [1, 2, 3]
list2 = list1 # list2 也引用 list1
list2.append(4) # 修改 list2
print(list1) # 输出 [1, 2, 3, 4],list1 也被改变
1
2
3
4
5
6
7
def add(l, x):
l.append(x)

l = [1, 2, 3]
print(l) # 输出[1, 2, 3]
add(l, 4)
print(l) # 输出[1, 2, 3, 4],函数可以改变外部传递过来的列表!

简要概括

你如果没法消化吸收刚才的内容,可以暂时不去应扣。你暂时只需要知道:

  • 复制列表、字典的时候用切片来复制(如果列表里还嵌套列表的话这样也不行……)
  • 给函数形参传递列表、字典的时候需要慎重考虑一下
  • 干脆不写需要传递列表、字典的函数,完全可以使用元组代替。

3.2 函数

3.2.1 函数的定义

在Python中,函数的定义形如:

1
2
3
def 函数名(形参列表):
# 语句
pass

由关键字def开头,后接函数名称,括号内为形参列表。

函数内部的第一行通常为三引号的注释,代表函数的文档。通常函数的编写者要在此处简单介绍函数,如函数的功能、形参、以及返回值类型。

例如,下面用定义了一个Fibonacci数列的函数

1
2
3
4
5
6
7
8
9
10
def Fibo(n):
"""
返回第n项的Fibonacci数列值,n应该为大于0的整数
返回值为整数
"""
if n<1:
return # n不符合要求直接退出函数,此时函数的返回值为None
if n==1 or n==2:
return 1
return Fibo(n-1) + Fibo(n-2)

3.2.2 函数的返回值

函数的返回值类型由函数内部的return语句决定,换句话说,函数内部可以返回任何类型的数据,函数的编写者需要在注释里明确说明返回值的类型。如果函数没有返回值,则函数的返回值为None。

比如下面的函数,如果a不是int,则返回None,否则返回布尔值、字符串或者整数。

1
2
3
4
5
6
7
8
def fun(a):
if type(a) != int:
return
if a == 1:
return True
if a == 2:
return "Hello"
return a

3.2.3 函数的形参

在Python中定义函数的时候,函数的形参在函数名后面用括号定义,每个形参之间使用逗号分开:

1
2
3
4
5
6
def f1(a, b, c):
# 定义了3个形参的函数
pass
def f2(a, b):
# 定义了2个形参的函数
pass

上面定义的两个函数,f1需要给出三个形参的值,f2需要给出两个形参的值。

3.2.3.1 形参的默认值

Python的函数可以为参数指定默认值。这是一个非常有用的方式。调用函数时,可以使用比定义时更少的参数,例如:

1
2
3
4
5
6
7
8
9
10
11
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
reply = input(prompt)
if reply in {'y', 'ye', 'yes'}:
return True
if reply in {'n', 'no', 'nop', 'nope'}:
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)

该函数可以用以下方式调用:

  • 只给出必选实参:ask_ok('Do you really want to quit?')
  • 给出一个可选实参:ask_ok('OK to overwrite the file?', 2)
  • 给出所有实参:ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

需要注意的是,默认值只在定义函数的时候计算出来,所以:

1
2
3
4
5
6
7
i = 5
def f(arg=i):
print(arg)

if __name__ == "__main__":
i = 6
f()

仍然输出为5。

重要警告: 默认值只计算一次。默认值为列表、字典或类实例等可变对象时,会产生与该规则不同的结果。例如,下面的函数会累积后续调用时传递的参数:

1
2
3
4
5
6
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))

输出结果为:

1
2
3
[1]
[1, 2]
[1, 2, 3]

原因:默认值存储的是这个列表的引用。

不想在后续调用之间共享默认值时,应以如下方式编写函数:

1
2
3
4
5
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L

3.2.3.2 通过形参名传递参数

在Python中,可以使用 形参名=值 的形式调用函数,指定给一个形参传递值。

使用 形参名=值 的形式称为关键字参数,只使用值的参数称为位置参数。

假设定义了以下函数:

1
2
3
4
5
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")

该函数接受一个必选参数(voltage)和三个可选参数(state, action 和 type)。该函数可用下列方式调用:

1
2
3
4
5
6
parrot(1000)                                          # 1 个位置参数
parrot(voltage=1000) # 1 个关键字参数
parrot(voltage=1000000, action='VOOOOOM') # 2 个关键字参数
parrot(action='VOOOOOM', voltage=1000000) # 2 个关键字参数
parrot('a million', 'bereft of life', 'jump') # 3 个位置参数
parrot('a thousand', state='pushing up the daisies') # 1 个位置参数,1 个关键字参数

以下的调用都是不符合语法的:

1
2
3
4
parrot()                     # 缺失必需的参数
parrot(voltage=5.0, 'dead') # 关键字参数后存在非关键字参数
parrot(110, voltage=220) # 同一个参数重复的值
parrot(actor='John Cleese') # 未知的关键字参数

函数调用时,关键字参数必须跟在位置参数后面。所有传递的关键字参数都必须匹配一个函数接受的参数(比如,actor 不是函数 parrot 的有效参数),关键字参数的顺序并不重要。

3.3 模块

Python中, import 关键字用于导入其他模块的函数、变量、类等资源。

Python把一些常用的函数定义存入不同的文件,供其他代码使用。这个文件就是模块。使用模块有一个好处,不同程序调用同一个函数时,不用把函数定义复制到各个程序。

同时你也可以导入自己的py脚本当作自定义的模块。

3.3.1 使用import导入Python内置模块

Python内置了许多模块,比如os模块、math模块、random模块、socket模块等等……因此在写Python项目的时候,你可以先去互联网查一查,有没有相关功能的模块。

在本教程中举几个常用的例子,比如说os模块:

1
2
3
4
import os

print(os.getcwd()) # 输出当前的运行目录
print(os.listdir('.')) # 列出当前目录下的所有文件和子目录

请注意,在使用模块里的函数时,你需要加上模块的名字,然后加上点引出模块内部的函数。

这样的做法有一点好处就是,可以防止模块的函数与你代码自定义的函数发生冲突。

Python中常用的模块还有:

1
2
3
4
5
6
7
8
9
import math

# 计算 C(5, 2)
print(math.comb(5, 2)) # 输出 10
print(math.sqrt(16)) # 输出 4.0
# 计算正弦和余弦
angle = math.pi / 4 # 45 degrees
print(math.sin(angle)) # 输出 0.7071...
print(math.cos(angle)) # 输出 0.7071...
1
2
import random
print(random.randint(1, 100)) # 输出 1 到 100 之间的随机整数
1
2
3
import datetime
now = datetime.datetime.now()
print(now) # 输出当前日期和时间

3.3.2 使用from … import导入模块的部分内容

如果你觉得模块名过于长,你可以使用 from ... import ... 导入模块的部分内容。

比如:

1
2
3
4
5
6
7
8
9
from math import comb, sqrt, sin, cos, pi

# 计算 C(5, 2)
print(comb(5, 2)) # 输出 10
print(sqrt(16)) # 输出 4.0
# 计算正弦和余弦
angle = pi / 4 # 45 degrees
print(sin(angle)) # 输出 0.7071...
print(cos(angle)) # 输出 0.7071...
1
2
from random import randint
print(randint(1, 100)) # 输出 1 到 100 之间的随机整数
1
2
3
from datetime import datetime
now = datetime.now()
print(now) # 输出当前日期和时间

需要注意的是,使用from导入模块的内容,可能会覆盖模块内原有的重名定义,也有可能原有的重名定义覆盖到你的导入。

3.3.3 给导入模块起别名

如果模块名本身就被使用了,或者使用from导入时,模块的某个函数会和已定义的函数重名,你可以用 as 给模块起一个别名:

1
2
from random import randint as rdi
print(rdi(1, 100)) # rdi 为randint的别名
1
2
import datetime as dt
print(dt.datetime.now()) # dt为datetime模块的别名

3.3.4 使用pip安装第三方模块

Python的强大就在于其具有丰富的第三方模块,但这些模块都不是自带的,python可以使用pip安装指定的模块

打开Windows终端,输入 pip install 包名 就可以安装你想要去安装的第三方模块,以flask模块举例:

alt text

这样你的Python就安装了flask模块,可以使用flask框架的一些东西了。

对于Pycharm来说,它会给每一个项目都生成一个小型Python运行环境(因为有些项目需要使用某个模块的旧版本,而另一些项目需要同样模块的新版本),因此你需要单独的给每一个项目的虚拟环境安装模块。

点击右下角的“Python 3.x”的按钮,它会弹出一系列项目的环境:

alt text

选择你当前打开的项目,点击“解释器设置”。

alt text

在项目,这个选项有一个Python解释器,这里会列出你当前项目的Python环境安装了什么模块,你可以点击右侧左上角的加号,添加你想要安装的包。

alt text

然后输入名字,选择你需要安装的模块,安装就可以了。

题外话 这么多知识记不住怎么办?

本来想在第一次培训课上就说这个事情的,但总是忘掉,于是暂时写在教程上吧。

对于新手来说,突然高强度接受这么多知识肯定是受不了的。(Python官方文档比我写的还长,讲了很多更抽象的用法,有一堆冷门的我也记不住……)

你需要做的是只需要了解基本语法就可以了,模块以及模块的函数完全可以在需要用的时候现去上互联网进行搜索,甚至现在还可以像AI提问。

之后我们培训的框架开发更是如此,一个框架会有许多许多的模块以及函数,刚去学习根本记不住几个(甚至用了好长时间也不会记住一个框架的所有内容),这个时候学会在需要用的时候上网搜索即可。

在学习一个技术的时候,不断的查看文档或者去提问是一个非常有效的方式。项目开发并不是期末考试,不会要求你写的所有代码都是在你脑子里的。对于初学者来说,能会去使用框架就可以了。

第四章 列表、集合、字典

本章将重点讲一下Python中常用的四个数据类型。

4.1 列表

4.1.1 列表类的方法

列表的常用方法如下(其中list为一个列表对象):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
list.append(x)
# 在列表末尾添加一个元素。

list.extend(otherList)
# 将其他列表的元素添加到list列表的尾部。

list.insert(i, x)
# 在指定位置插入元素。第一个参数是插入元素的索引。
# 因此,a.insert(0, x) 在列表开头插入元素, a.insert(len(a), x) 等同于 a.append(x) 。

list.remove(x)
# 从列表中删除第一个值为 x 的元素。未找到指定元素时,触发ValueError异常。

list.clear()
# 删除列表里的所有元素。

list.index(x[, start[, end]])
# 返回列表中第一个值为 x 的元素的零基索引。未找到指定元素时,触发ValueError异常。
# 可选参数start和end是切片符号,相当于搜索子序列。
# 返回的索引是相对于整个序列的开始计算的,而不是 start 参数。

list.count(x)
# 返回列表中元素x出现的次数。

list.sort(*, key=None, reverse=False)
# 排序列表中的元素。key为比较函数,注意这两个形参必须以关键字参数的形式给出

list.reverse()
# 翻转列表中的元素。

list.copy()
# 返回列表的浅拷贝。相当于 a[:] 。

这些方法的简单使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
>>> fruits.count('apple')
2
>>> fruits.count('tangerine')
0
>>> fruits.index('banana')
3
>>> fruits.index('banana', 4) # 从 4 号位开始查找下一个 banana
6
>>> fruits.reverse()
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
>>> fruits.append('grape')
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
>>> fruits.sort()
>>> fruits
['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']

注意insert, remove或sort等仅修改列表的方法都不会打印返回值,它们返回默认值None。

4.1.2 列表推导式

列表推导式创建列表的方式更简洁。常见的用法为,对序列或可迭代对象中的每个元素应用某种操作,用生成的结果创建新的列表。

例如,创建平方值的列表,我们用之前学过的方法为:

1
2
3
4
5
6
>>> squares = []
>>> for x in range(10):
... squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

但是你可以写成这样:

1
2
3
>>> squares = [x**2 for x in range(10)]
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

列表推导式的方括号内包含以下内容:一个表达式,后面为一个 for 子句,然后,是零个或多个 for 或 if 子句。结果是由表达式依据 for 和 if 子句求值计算而得出一个新列表。 举例来说,以下列表推导式将两个列表中不相等的元素组合起来:

1
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)]

等价于:

1
2
3
4
5
6
7
8
>>> combs = []
>>> for x in [1,2,3]:
... for y in [3,1,4]:
... if x != y:
... combs.append((x, y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

4.2 元组

列表和字符串有很多共性,例如,索引和切片操作。这两种数据类型是序列。

本节介绍另一种标准序列类型:元组。

元组由多个用逗号隔开的值组成,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> t = (12345, 54321, 'hello!')
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # 元组可以嵌套:
>>> u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
>>> # 元组是不可变对象:
>>> t[0] = 88888
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

元组与列表很像,但使用场景不同,用途也不同。元组的元素是不可变的,通过解包或索引访问。

序列解包的方法如下:

1
2
3
4
5
6
7
8
>>> t = (12345, 54321, 'hello!')
>>> x, y, z = t # 将t的三个元素分别传给x y z
>>> x
12345
>>> y
54321
>>> z
'hello!'

4.3 集合

Python还支持集合这种数据类型。集合是由不重复元素组成的无序容器。基本用法包括成员检测、消除重复元素。集合对象支持合集、交集、差集、对称差分等数学运算。

创建集合用花括号或 set() 函数。注意,创建空集合只能用 set(),不能用 {},{} 创建的是空字典。

1
2
3
4
>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket) # 显示重复项已被移除
{'orange', 'banana', 'pear', 'apple'}

可以用in关键字快速检验某个元素是否在集合内部:

1
2
3
4
>>> 'orange' in basket                 # 快速成员检测
True
>>> 'crabgrass' in basket
False

集合可以由列表、字符串等序列初始化,此时集合的初始值为序列的每一个元素:

1
2
3
4
5
6
7
>>> # 演示针对两个单词中独有的字母进行集合运算
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a # a 中独有的字母
{'a', 'r', 'b', 'c', 'd'}
>>> b
{'c', 'l', 'm', 'a', 'z'}

两个集合直接可以用基本运算符号代表差集(减号)、并集(|,位或号)、交集(&,位与号)、对称差集(^,异或号)

可以简单想一想为什么用这些算术符号定义集合的操作。

1
2
3
4
5
6
7
8
>>> a - b                              # 存在于 a 中但不存在于 b 中的字母
{'r', 'd', 'b'}
>>> a | b # 存在于 a 或 b 中或两者中皆有的字母
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b # 同时存在于 a 和 b 中的字母
{'a', 'c'}
>>> a ^ b # 存在于 a 或 b 中但非两者中皆有的字母
{'r', 'd', 'b', 'm', 'z', 'l'}

4.4 字典

另一个非常有用的Python内置数据类型是字典,它是一种映射关系。
列表和字符串可以通过索引访问元素,而字典是通过键访问元素的。(形象的来说,字典的索引可以是其他的数据类型,但这个数据类型需要是不可以改变的)

举例说明:字符串和数字可以当作键,而列表和集合就不能当作键。对于元组来说,如果元组内部不含可改变类型也可以当作键。

常用的是把字符串当作键值。

字典的存储方式为 键:值,一个键对应一个值。

字典的键必须是唯一的。花括号 {} 用于创建空字典。另一种初始化字典的方式是,在花括号里输入逗号分隔的键值对。

形象的来说,使用字典,你可以给一个字符串赋一个值,来实习字符串到其他数据类型的转换。

1
2
3
4
5
6
>>> tel = {'jack': 4098, 'sape': 4139}  # 使用 键:值 的形式初始化字典
>>> tel['guido'] = 4127 # 给不存在的键赋值,相当于插入一个新键
>>> tel
{'jack': 4098, 'sape': 4139, 'guido': 4127}
>>> tel['jack'] # 通过键获取值
4098

字典的主要用途是通过关键字存储、提取值。用 del 可以删除键值对。用已存在的键存储值,该键的旧值会被取代。通过不存在的键提取值,则会报错。

1
2
3
4
5
6
>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'jack': 4098, 'guido': 4127, 'irv': 4127}

对字典执行 list(d) 操作,返回该字典中所有键的列表。检查字典里是否存在某个键,可以使用关键字in。

1
2
3
4
5
6
7
8
9
>>> tel = {'jack': 4098, 'sape': 4139}
>>> list(tel) # 返回的是键的列表
['jack', 'sape']
>>> 'guido' in tel # 判断键是否存在
False
>>> 'jack' in tel
True
>>> 4098 in tel # 值不是键
False

dict() 函数可以直接用键值对的列表创建字典:

1
2
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'guido': 4127, 'jack': 4098}

当你需要同时遍历字典的键与值,可以使用items()方法:

1
2
3
4
5
6
>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.items():
... print(k, v)
...
gallahad the pure
robin the brave

当你想获取字典的值的列表,你可以使用 list(d.values())

1
2
3
4
5
6
7
>>> tel = {'jack': 4098, 'guido': 4127, 'irv': 4127}
>>> list(tel.values())
[4098, 4127, 4127]
>>> list(tel.keys())
['jack', 'guido', 'irv']
>>> list(tel)
['jack', 'guido', 'irv']

title: 第五章 类与对象

第五章 类与对象

5.1 面向对象编程的基本概念

5.1.1 类

面向对象编程(Object-Oriented Programming, OOP)是一种程序设计模式,旨在通过将数据和行为封装在对象中,增强代码的模块性、重用性和可维护性。

面向对象编程是相对于面向过程编程来讲的,更贴近事物的自然运行模式。

比如说人类,具有身高、体重、姓名、性别等属性,同时还具备吃饭、呼吸、学习、摆烂等方法。类似的,在编程的时候,将多种具有一定联系的变量和函数封装在一起组成的一个集体,就是——类。

用编程语言实现人类,就是定义一个Person类,具有身高、体重、姓名性别这些变量,以及具有吃饭、呼吸、学习等函数。

通常在OOP中,将类的成员变量称之为类的属性,将类的成员函数称之为类的方法

5.1.2 对象

对象是OOP的核心概念。它是程序中某一个类的实例。比如上述定义的Person类,小明就可以看作是Person类的一个实例,他具有Person类的所有属性以及方法。

可以简单理解为:对象是类的一个实例,类是对象的模板。

5.1.3 封装、继承、多态、抽象

封装是OOP的基本特性之一,它将数据和操作这些数据的函数封装在一起,隐藏内部实现细节,只暴露必要的接口。封装带来的好处包括信息隐藏和控制访问。通过封装,调用者无需关系类的内部的具体实现,只需要使用类提供的方法即可。

继承是OOP的一种机制,允许一个类(子类)继承另一个类(父类)的属性和方法。继承可以提高代码的重用性和组织性。子类可以重用父类的代码,还可以重写父类的方法以实现特定功能。

多态是指同一操作可以作用于不同类型的对象,表现出不同的行为。这使得程序能够根据对象的实际类型调用相应的方法,增强了灵活性。

抽象是指将复杂的现实世界简化为可管理的模型,强调对象的特性而隐藏不必要的细节。在OOP中,可以使用抽象类和接口来定义基本的结构和行为。

以上的概念将会在代码中进一步说明。

(如果用C++术语来描述的话,类成员(包括数据成员)通常为public,所有成员函数都为virtual。如果你没学过C++的话,可以忽略这句话)

5.2 类定义语法

最简单的类定义为以下结构:

1
2
3
4
5
6
class ClassName:
<语句-1>
.
.
.
<语句-N>

与函数定义 (def 语句) 一样,类定义必须先执行才能生效。\

在类里可以定义成员变量(属性)和成员函数(方法),不过类里的成员函数需要一个特殊的形参。

类对象支持两种操作:属性引用和实例化。

属性引用是指通过类名加一个点号,引出类的属性:

1
2
3
4
5
6
7
8
9
class MyClass:
"""一个简单的示例类"""
i = 12345 # 类变量

@staticmethod # 静态方法,下面讲
def f(): # 类的方法
return 'hello world'
def f2(self): # 类的方法,为什么要写self将在下面讲
return 'hello world'

定义之后你去执行 MyClass.i 就会获得12345这个数据。

执行 MyClass.f() 就会获得字符串。

5.3 类的实例化

类的实例化使用函数表示法。可以把类对象视为是返回该类的一个新实例的不带参数的函数。 举例来说(假设使用上述的类):

1
2
3
4
5
6
7
8
9
10
class MyClass:
"""一个简单的示例类"""
i = 12345 # 类变量

@staticmethod
def f(): # 类的方法
return 'hello world'
def f2(self): # 类的方法
return 'hello world'
x = MyClass()

创建类的新实例并将此对象分配给局部变量x。此时x就是MyClass类的一个对象。

此时我们创建的对象是一个空对象,仅仅只是把类的属性和方法“复制”了过来。许多时候我们需要让对象在初始化的时候具有某种状态,在类里有这么一个特殊的函数:

1
2
def __init__(self):
self.data = []

当一个类定义了 __init__() 方法时,在创建类的对象时会自动执行这个函数的代码,这个函数称之为构造函数。

注意,第一个self参数不可以省略。

当然,构造函数可以传递几个参数:

1
2
3
4
5
6
class Person:
def __init__(self, name):
self.name = name
print(f"Hello, {name}!")

a = Person("Kotori")

将自动输出 Hello, Kotori!

5.4 方法的self参数

在上面的类定义中,你会发现所有的函数第一个形参都为self(除了@staticmethod)。
在Python中,self参数是实例方法的第一个参数,它代表类的实例本身。通过self,你可以访问实例的属性和其他方法。

形象的来说,self参数就是对象本身,不是类本身

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person:
def __init__(self, name):
self.name = name
print(f"Hello, {name}!")
def getName(self):
return self.name

a = Person("Alice")
b = Person("Bob")
print(a.name)
print(b.name)
print(a.getName())
print(b.getName())

在实例化a对象的时候,调用的初始化函数中的self就是a对象的引用,在实例化b对象的时候,调用的初始化函数中的self就是b对象的引用。
在a对象调用getName的时候,方法中的self参数就是a的引用,b对象调用getName的时候,方法中的self参数就是b的引用。

在Python中,调用对象的方法时,会自动传递对象的引用给self,但是在类里定义对象方法的时候,需要显示写出self参数,即对象方法的第一个参数始终为self

5.5 类的变量和实例变量

一般来说,实例变量用于每个实例的唯一数据,而类变量用于类的所有实例共享的属性和方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Dog:
kind = 'canine' # 类变量被所有实例所共享
def __init__(self, name):
self.name = name # 实例变量为每个实例所独有
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # 被所有的 Dog 实例所共享
'canine'
>>> e.kind # 被所有的 Dog 实例所共享
'canine'
>>> d.name # 为 d 所独有
'Fido'
>>> e.name # 为 e 所独有
'Buddy'

简单来说,类的变量直接定义在类里面,如例子的kind变量,这样的变量可以被所有类的实例化访问到(也可以直接通过类名进行访问)。而通过实例方法,给self赋值的变量,属于实例变量,只能被这个实例本身所使用。

类的变量可以供所有实例共享,不建议使用这样的变量。(你可以用来存储常量)

5.6 静态方法

刚才说了,类的所有的方法第一个形参都为self(除了@staticmethod)。

@staticmethod 修饰的方法称之为静态方法,调用静态方法的时候不会给方法传递默认的self了,因此静态方法不能访问实例变量,因此很少用。

5.7 继承

使用类的继承,可以轻松的将其他类的属性和方法复用。结构如下:

1
2
3
4
5
6
7
8
class 子类(父类):
pass
class DerivedClassName(BaseClassName):
<语句-1>
.
.
.
<语句-N>

只是在先前的类定义中,用括号将父类括起来。

简单来说,子类直接拥有父类的属性和方法,并可以定义属于自己的属性和方法。
如果子类的成员与父类成员冲突,则子类成员会覆盖掉父类成员。

子类的构造函数内,需要调用父类的构造函数,使用如下结构:

1
2
3
class Derived(Base):
def __init__(self):
super().__init__() # 如果有其他形参需要给出

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Parent:
def __init__(self):
self.parent_attribute = "I am a parent"

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

class Child(Parent): # Child 继承 Parent
def __init__(self):
super().__init__() # 调用父类的构造函数
self.child_attribute = "I am a child"

def child_method(self):
print("This is a method from the child class.")

def parent_method(self): # 重写父类的方法
print("This is an overridden method from the child class.")

# 使用示例
child_instance = Child()
print(child_instance.parent_attribute) # 输出: I am a parent
print(child_instance.child_attribute) # 输出: I am a child
child_instance.parent_method() # 输出: This is an overridden method from the child class.

在这个例子中,Child类继承了Parent类的属性parent_attribute和方法parent_method,同时定义了自己的属性child_attribute和方法child_method。

总结

类是由变量与函数组成的具有一定意义的集合体。类是一个模板,在使用的时候需要实例化为一个对象。类的方法第一个参数需要始终为self,self参数就是对象本身,可以通过self.创建属于对象自己的变量。在调用对象的方法时需要使用点号引出。
一个类可以继承先前定义好的类,可以实现父类代码的复用。

在本基础教程中,没有涉及多态、抽象、以及多继承等复杂的类。

第五章 文件读写、错误与异常

5.1 文件读写

5.1.1 打开文件

open() 函数返回一个文件对象,最常使用的是两个位置参数和一个关键字参数:

1
2
# 函数最常用的形参,其中encodeing为关键字参数
open(filename, mode='r', encoding=None)

第一个实参是文件名字符串。第二个实参是包含描述文件使用方式字符的字符串。mode 的值包括 ‘r’ ,表示文件只能读取;’w’ 表示只能写入(现有同名文件会被覆盖);’a’ 表示打开文件并追加内容,任何写入的数据会自动添加到文件末尾。’r+’ 表示打开文件进行读写。