python基础知识

引言

作为我第一门系统学习的解释性脚本语言,先理解解释型语言和编译型语言的区别
编译型源代码–>编译器–>可执行文件 (一次编译,多次运行,效率高,移植性差)
解释型源代码–>解释器
故首先需按安装python解释器,本文以3.10.11为准
在没有IDE的情况下,我们有两种方法执行python代码:

  1. 交互式命令行

  2. 运行文本文件
    交互式命令行
    运行文本文件
    首先我们需要用dos命令找到安装的python解释器(若将其添加到全局PATH中,可省略)
    dir 显示当前路径下的文件夹
    cd 转到对应路径(仅限同一盘符)cd …/ 回退一级
    D: 转移到对应盘符
    tab 补全输入内容

为了便捷,接下来都将以**Pycharm(22.10.3 professional)**作为集成开发环境辅助学习


基本语法

首先补充基本的输入输出函数
ctrl+左键点击可查看内置函数的源码
input && print

1
2
3
name = input("请输入姓名:>")
age = input("请输入年龄:>")
print(f"姓名:{name} 年龄:{age}")

input默认接收的均为字符串,可用类型转换函数type(s)转换

语法结构

1.语句分隔符

python中通常以换行符作为语句分隔符
若想在同一行中执行多句语句,也可用;作为分隔(不推荐,不符合pep8规范)

python代码应符合pep8规范,在pycharm中,绿色波浪线代表违反规范,按Ctrl+Alt+L可一键调整

一行写一条语句,大大提高代码的可读性和规范性

1
2
print(1+1)
print("welcome"); print("BYF")

注意:python代码缩进要求严格,允许空行,不可缩进

2.注释

python中用#单行注释
三引号(单引号或双引号均可)多行注释

1
2
3
4
5
import random  # 导入获取随机数的random模块
print(round(random.random()*100)) # 生成一到100的随机整数
'''
这是多行注释
'''

变量

特性:

  1. 多次使用(不会被垃圾回收机制回收)
  2. 值可修改 (和编译型语言不同,修改时指向变化,原值被回收)

变量名应符合规范

基本数据类型

基本数据类型:

  1. 整型与浮点型
  2. bool类型(True、False)
  3. 字符串

python内置函数type()返回数据类型名

1
2
3
4
print(type(10))  #<class 'int'>
print(type(2.5)) #<class 'float'>
print(type(10 > 5)) #<class 'bool'>
print(type("hello world")) #<class 'str'>

内置函数bool,可求变量或表达式的bool值
零值:所有数据类型中有且仅有一个值的bool类型为False 字符串 ""列表 [] 字典 {}

字符串作用:

  1. 展示文本
  2. 储存数据
  3. 传递字节
1
2
3
4
5
6
7
s1 = "hello a1"  # 方式1
s1 = 'hello a2' # 方式2
s3 = """
1. 购买道具
2. 攻击
3. 逃跑
""" # 方式3

字符串基本操作

转义符 \

  1. 给予普通符号特殊功能
  2. 将具有特殊功能的符号普通化
符号 功能
\" 双引号
\\ 反斜杠
\n 换行符
\t 制表符
\r 回车符
\' 单引号

格式化输出

1
2
3
4
5
6
7
name = "byf"
username = "Oracle"
passwd = 123456
# 方式1(不推荐)
print("hello %s!,your username is %s,your passwd is %d" % (name, username, passwd))
# 方式2(3.6版本后可用)
print(f"hello {name}!,your username is {username},your passwd is {passwd}")

字符串序列操作

python支持负索引,从右(-1)到左依次减一
字符串是不可变的,不能像列表一样修改其中某个数据

索引操作 查询字符 : 字符串[索引]
print(s[0])


切片操作 获取字符串 : 字符串[开始索引:结束索引:步长(默认为1)](顾头不顾尾)
print(s[0:5])缺省默认从头尾取
步长为2时,隔一个取一个字符,以此类推
步长为负时,从右向左取
一个有趣的应用

1
2
# 字符串倒序
print(s[::-1])

拼接操作 字符串1+字符串2 || 字符串*int

1
2
3
s1 = "hello "
s2 = "Oracle"
print(s1+s2)

计算字符串长度 内置函数len
print(len(s1))

中文算作一个字符


in判断 判断某个成员是否存在(返回bool类型)

1
2
s1 = "hello Oracle"
print("hello" in s1) # True

字符串内置方法

内置函数 功能 实例
upper 将字符串中所有字符转化为大写 s1.upper()
lower ~转化为小写 s1.lower()
startswith 判断字符串是否以…开头(返回bool) s1.startswith(“hello”)
endswith 判断是否以…结尾 s2.endswith(".jpg")
isdigit 判断是否为纯数字字符串 s2.isdigit()
strip 去除字符串两端的空格或换行符 s2.strip()
join 把列表中的字符串按一分隔符拼接 “,”.join(list)
split 把字符串按一分隔符分割,存到列表中 cities.split(",")

其他方法

  1. find 搜索指定字符串,返回索引,没有返回-1
  2. index 功能同上,但找不到时报错
  3. count 计算字符串中子串出现的顺序
  4. replace 将字符串中字串替换为指定字符串
  5. center 将字符串左右添加填充符到指定长度
  6. encode 将字符串按指定方式编码

replace 缺省默认全部替换,传入int型可指定替换个数

运算符

计算运算符:+-*/%
比较运算符:><>=<===!=

返回类型为bool

赋值运算符:=+=-=/=*=%=
逻辑运算符:andornot

python范围判断可以直接写成 18<age<35

成员运算符:in

流程控制语句

多分支语句

注意四个空格的缩进和英文冒号

1
2
3
4
5
age = int(input("输入你的年龄:>"))
if age >= 18:
print("欢迎访问")
else:
print("禁止访问")

多分支

1
2
3
4
5
6
7
8
9
10
11
12
weight = float(input("请输入你的体重(公斤):>"))
height = float(input("请输入你的身高(米):>"))
BMI = weight / height ** 2
if BMI < 18.5:
print("偏瘦")
elif BMI < 24:
print("正常")
elif BMI< 28:
print("超重")
else:
print("肥胖")

循环语句

while循环

1
2
3
4
count = 1
while count <= 100:
print("count =", count)
count += 1

for循环
对一个序列(如字符串、列表、元组)进行遍历操作

1
2
3
4
5
lis = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for i in lis:
print("i", i)
for i in range(2, 100, 2):
print("i", i)

break 退出整个循环
continue 退出当前循环


高阶数据类型

列表

[]内以逗号分隔,按照索引存放各种数据类型
可修改指定索引位置对应的值

列表的操作
追加:数据被追加到尾部

1
2
3
letter = ['a', 'b', 'c', 'd']
letter.append('e')
print(letter)

插入:可插入列表中任一位置(也可用此方法嵌套列表)

1
2
3
4
5
6
7
letter = ['a', 'b', 'd', 'e']
letter.insert(2, 'c')
print(letter)

letter = ['a', 'b', 'd', 'e']
letter.insert(2, ['x', 'y', 'z'])
print(letter[2][1]) # y

合并:把另一列表的值合并进来

1
2
3
4
letter1 = ['a', 'b']
letter2 = ['c', 'd', 'e']
letter1.extend(letter2)
print(letter1)

del删除:直接删除指定位置元素

1
2
3
letter = ['a', 'b', 'c', 'd', 'e']
del letter[2]
print(letter) #['a', 'b', 'd', 'e']

pop弹出:弹出列表最后一个元素并返回(也可指定位置)

1
2
3
letter = ['a', 'b', 'c', 'd', 'e']
a = letter.pop()
print(letter)

remove删除:指定元素名删除,如有多个删除从左到右第一个

clear清空:清除列表中所有元素

index查找:返回从左到右第一个指定元素的索引
切片操作和字符串类似

sort排序

1
2
3
letter = [1,2,7,3,8,4,5,9]
a = letter.sort()
print(a)

reverse反转

1
2
3
letter = [1,2,7,3,8,4,5,9]
a = letter.reverse()
print(a)

字典

为满足快速查询,引入key-value的字典类型,以花括号包裹
key必须为不可变的数据类型(如数字、字符串)

1
2
3
4
5
6
7
dic = {
"x": "hello",
"y": "world",
"z": "!"
}
print(dic["x"], dic["y"], dic["z"])
# dic.get("") 方法也可取用,优点是找不到不报错,返回None

字典也可以进行嵌套

增删改查操作

  • 增加:dic["a"] = "error" 直接对新key值赋值即可添加
  • 删除:del dic["x"]dic.pop("y")dic.clear()前两个为清除指定key,后一个为清空
  • 修改:操作同增加,若key值存在,即会修改value值
  • 查找 dic.keys()返回一个包含字典所有KEY的列表;dic.values()返回一个包含字典所以value的列表;dic.items()返回一个包含所有(键、值)元组的列表

循环

  1. for k in dic.keys()
  2. for k,v in dic.items()
  3. for k in dic(推荐,效率更快)

元组

元组(tuple)是Python中另一种内置的存储有序数据的结构。
元组与列表类似,也是由一系列按特定顺序排列的元素组成,可存储不同类型的数据,
如字符串、数字甚至元组。然而,元组是不可改变的,创建后不能再做任何修改操作。
元组可作为字典的键值,而列表不行
元组由()包裹

删除元组
元组中的元素值是不允许更改的,但可以使用del语句整个删除

1
2
3
4
data = range(1, 11)
tupleName = tuple(data)
del tupleName
print(tupleName)

元组常见操作

元组是不可变序列,虽然不能对单个元素值进行修改,但可以重新赋值以达到更改效果
len(tup)
max(tup)
min(tup)
元组和列表的相互转换
tuple()函数接收一个列表,返回一个元组
list()函数接收一个元组,返回一个列表

文件操作

python3在内存中默认用Unicode编码,但存在文件里或网络发送,用utf-8编码

字节类型就是二进制格式,通常用十六进制来表示,编码后转成bytes字节类型,可防止显示乱码

操作文件流程:

  1. 打开文件
  2. 读 || 修改
  3. 保存 && 关闭

打开文件(文本模式)

  • r 只读模式
  • w 创建模式,若文件已经存在,则覆盖旧文件
  • a 追加模式,新数据会写到文件末尾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#  w mode
f = open("name_list", mode="w")
f.write("张三\n")
f.write("李四\n")
f.write("王五\n")
f.close()
# r mode
f = open("name_list", mode="r")
print(f.read()) # 读出所有内容
print(f.readline()) # 读一行
# a mode
f = open("name_list", mode="a")
f.write("oracle\n")
f.close()

文件循环遍历

循环遍历时,将文件每一行的数据以列表形式存入line中

1
2
3
4
5
6
7
f = open("name_list")
for line in f:
line = line.split()
age = int(line[1])
if age >= 18:
print(line)

二进制文件操作(图片/视频)

encodin=None,该参数告诉你的解释器当亲要打开文件的编码,若缺省,则用解释器的默认编码,也就是utf-8
图片和视频底层也是二进制,用二进制形式打开

1
2
3
4
5
6
7
f = open("yuan.png", "rb")
for line in f:
print(line)

f = open("name_list", "wb")
s = "路飞 18"
f.write((s.encode("utf-8")))

rb 二进制只读
wb 二进制只写

其他常用功能
seek 把操作文件的光标移动到指定位置

1
2
3
4
5
6
7
8
f = open("name_list", mode="r", encoding="UTF-8")
f.seek(11)
# f.write("张三 18\n")
# f.write("李四 20\n")
# f.write("王五 21\n")
# f.write("赵六 19\n")
print(f.readline()) # 李四 20
f.close()

按字节操作,utf-8编码中中文占三个字节,西欧符号占两个字节,英文和数字占一个字节
写文件时会覆盖光标后的文本

经常搭配tell函数使用,其作用是返回当前光标的位置

flush 把文件从内存buffer里强制刷新到硬盘
truncate 以光标位置截断文件,将光标后数据删除

其他打开方式(混合模式)
w+ 写读:创建一个新文件,可写入文本,也可将写入的内容读出来
r+ 读写:能读能写,但都是些在文件最后,类似追加
a+ 追加读:写入数据均以追加的形式

文件修改
前面提到的文件修改会将当前光标后的数据替换掉,而我们正常需求一般是在光标位置前插入文本数据。要实现这点,需打开文件后,先将数据读到内存中,添加数据后再重新写入硬盘

练习
编写一个全局文本检索替换脚本
要求在命令行中执行python replace.py old_str new_str filename

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import sys

old_str = sys.argv[1]
new_str = sys.argv[2]
filename = sys.argv[3]
f = open(filename, mode="r+", encoding="UTF-8")
# 加载到内存
data = f.read()
count = data.count(old_str)
new_data = data.replace(old_str, new_str)
# 清空文件
f.seek(0)
f.truncate()
# 把新内容写入硬盘
f.write(new_data)
print(f"成功替换字符'{old_str} to'{new_str},共计{count}处'")
f.close()

成功

另一种方法:
创建一个新文件,将原文件数据按行读入新文件,并加以替换,最后将原文件删除

函数编程

特性:

  1. 减少重复代码
  2. 使程序可扩展
  3. 使程序易维护

基本语法

1
2
3
4
5
def sayhello(name):
print(f"hello,my name is {name}")


sayhello("Oracle")

值得注意python中函数形参和实参传入机制与其他编程语言不同,与传入的数据类型(可变 or 不可变有关),可参考Python中变量的本质

python也可使用默认参数调用函数,语法和C++类似,缺省参数定义需在最后。一般按位置关系确定对应关系的参数,但也可指定参数名,指定参数调用时必须放在默认参数之后

非固定参数
若在函数定义时不确定要传入多少参数,可以使用非固定参数

1
2
3
4
5
6
7
8
def stu_register(name, age, *args, **kwargs):
print(name, age, args, kwargs)


# *args 会把多传入的参数变成一个元组的形式
# **kwargs 会把传入的指定参数变成一个字典的形式
stu_register("Oracle", 19, "M", "IT", address="吉林省")
# >>>Oracle 19 ('M', 'IT') {'address': '吉林省'}

函数返回值
若想在函数外部想获取函数内部产生的值,可以用return语句将值返回
python可同时返回多个值,写成return a,b,c
其值存放在元组中(a, b, c)
函数执行到return就代表函数结束,后面的代码不会执行

全局变量与局部变量

1
2
3
4
5
6
7
8
9
10
a = 1               # 全局变量
def func():
b = 2 # 局部变量
print(a) # 可访问全局变量a,无法访问它内部的c

def inner():
c = 3 # 更局部的变量
print(a) # 可以访问全局变量a
print(b) # b对于inner函数来说,就是外部变量
print(c)

global:指定当前变量使用外部的全局变量(直接使用最外层的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a = 1
print("函数outer调用之前全局变量a的内存地址: ", id(a))
def outer():
a = 2
print("函数outer调用之时闭包外部的变量a的内存地址: ", id(a))
def inner():
global a # 注意这行
a = 3
print("函数inner调用之后闭包内部变量a的内存地址: ", id(a))
inner()
print("函数inner调用之后,闭包外部的变量a的内存地址: ", id(a))
outer()
print("函数outer执行完毕,全局变量a的内存地址: ", id(a))
# 函数outer调用之前全局变量a的内存地址: 494384192
# 函数outer调用之时闭包外部的变量a的内存地址: 494384224
# 函数inner调用之后闭包内部变量a的内存地址: 494384256
# 函数inner调用之后,闭包外部的变量a的内存地址: 494384224
# 函数outer执行完毕,全局变量a的内存地址: 494384256

若有多层嵌套,且仅想使用上一层的变量,可使用nonlocal关键字声明

递归函数
python语言也支持递归函数
举例一个简单的高斯求和函数

1
2
3
4
5
6
7
8
def Gaosi(n):
if n <= 0:
return 0
else:
return n + Gaosi(n - 1)


print(Gaosi(100))

常用内置函数

内置函数 功能
all() 遍历整个列表,若所有元素bool值均为true则返回true,反之为false
any() 遍历整个列表,若任一元素bool值为true则返回true,反之为false
chr() 返回指定数字对应ASCII码对应的字符
dict() 生成一个字典,确省则为空字典
dir() 显示对象所有的属性和方法。最棒的辅助函数之一
abs() 返回指定数据的绝对值
bin()、oct()、hex() 三个函数是将十进制数分别转换为2/8/16进制
ord() 与chr()相反,返回某个ASCII字符对应的十进制数
divmod() 除法,同时返回商和余数的元组
enumerate() 枚举函数,在迭代对象的时候,额外提供一个序列号的输出
eval() 将字符串直接解读并执行
gloabals() 列出当前环境下所有的全局变量。注意要与global关键字区分
hash() 为不可变对象,例如字符串生成哈希值的函数
help() 返回对象的帮助文档
id() 返回对象的内存地址,常用来查看变量引用的变化,对象是否相同等
isinstance() 判断一个对象是否是某个类的实例
locals() 返回当前可用的局部变量
max()/min() 返回给定集合里的最大或者最小的元素。可以指定排序的方法
reversed() 反转,逆序对象
round() 四舍五入
sum() 对可迭代的对象求和
sorted() 排序方法。有key和reverse两个重要参数

有个别遗漏可查看python内置函数

进阶知识

面向对象编程

面向对象的思想与其他编程语言相同,主要记录一下python独特的语法
类和实例
Python使用class关键字来定义类,其基本结构如下:

1
2
class 类名(父类列表):
pass

类名通常采用驼峰式命名方式,尽量让字面意思体现出类的作用。Python采用多继承机制,一个类可以同时继承多个父类(也叫基类、超类),继承的基类有先后顺序,写在类名后的圆括号里。继承的父类列表可以为空,此时的圆括号可以省略。但在Python3中,即使你采用类似class Student:pass的方法没有显式继承任何父类的定义了一个类,它也默认继承object类。因为,object是Python3中所有类的基类。

下面是一个学生类:

1
2
3
4
5
6
7
8
9
10
class Student:
classroom = '101'
address = 'beijing'

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

def print_age(self):
print('%s: %s' % (self.name, self.age))

可以通过调用类的实例化方法(有的语言中也叫初始化方法或构造函数)来创建一个类的实例。默认情况下,使用类似obj=Student()的方式就可以生成一个类的实例。但是,通常每个类的实例都会有自己的实例变量,例如这里的name和age,为了在实例化的时候体现实例的不同,Python提供了一个def init(self):的实例化机制。任何一个类中,名字为__init__的方法就是类的实例化方法,具有__init__方法的类在实例化的时候,会自动调用该方法,并传递对应的参数。比如:

1
2
li = Student("李四", 24)
zhang = Student("张三", 23)

实例变量和类变量
实例变量和类变量
实例变量
实例变量指的是实例本身拥有的变量。每个实例的变量在内存中都不一样。Student类中__init__方法里的name和age就是两个实例变量。通过实例名加圆点的方式调用实例变量。

我们打印下面四个变量,可以看到每个实例的变量名虽然一样,但他们保存的值却是各自独立的:

1
2
3
4
5
6
7
8
9
print(li.name)
print(li.age)
print(zhang.name)
print(zhang.age)
------------------------
李四
24
张三
23

类变量
定义在类中,方法之外的变量,称作类变量。类变量是所有实例公有的变量,每一个实例都可以访问、修改类变量。在Student类中,classroom和address两个变量就是类变量。可以通过类名或者实例名加圆点的方式访问类变量,比如:

1
2
3
4
Student.classroom
Student.address
li.classroom
zhang.address

在使用实例变量和类变量的时候一定要注意,使用类似zhang.name访问变量的时候,实例会先在自己的实例变量列表里查找是否有这个实例变量,如果没有,那么它就会去类变量列表里找,如果还没有,弹出异常。

Python动态语言的特点,让我们可以随时给实例添加新的实例变量,给类添加新的类变量和方法。因此,在使用li.classroom = '102’的时候,要么是给已有的实例变量classroom重新赋值,要么就是新建一个li专属的实例变量classroom并赋值为‘102’。看下面的例子:

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
33
34
>>> class Student:              # 类的定义体
classroom = '101' # 类变量
address = 'beijing'

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

def print_age(self):
print('%s: %s' % (self.name, self.age))


>>> li = Student("李四", 24) # 创建一个实例
>>> zhang = Student("张三", 23) # 创建第二个实例
>>> li.classroom # li本身没有classroom实例变量,所以去寻找类变量,它找到了!
'101'
>>> zhang.classroom # 与li同理
'101'
>>> Student.classroom # 通过类名访问类变量
'101'
>>> li.classroom = '102' # 关键的一步!实际是为li创建了独有的实例变量,只不过名字和类变量一样,都叫做classroom。
>>> li.classroom # 再次访问的时候,访问到的是li自己的实例变量classroom
'102'
>>> zhang.classroom # zhang没有实例变量classroom,依然访问类变量classroom
'101'
>>> Student.classroom # 保持不变
'101'
>>> del li.classroom # 删除了li的实例变量classroom
>>> li.classroom # 一切恢复了原样
'101'
>>> zhang.classroom
'101'
>>> Student.classroom
'101'

为了防止发生上面的混淆情况,对于类变量,请坚持使用类名.类变量的访问方式,不要用实例去访问类变量。

封装、继承和多态
封装
封装的思想不多赘述,只举具体实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Student:
classroom = '101'
address = 'beijing'

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

def print_age(self):
print('%s: %s' % (self.name, self.age))

bao = Student("Oracle", 18)
bao.print_age()
# 以下是错误的用法
# 类将它内部的变量和方法封装起来,阻止外部的直接访问
print(classroom)
print(adress)
print_age()

继承
继承最大的好处是子类获得了父类的全部变量和方法的同时,又可以根据需要进行修改、拓展。其语法结构如下:

1
2
class Foo(superA, superB,superC....):
class DerivedClassName(modname.BaseClassName): ## 当父类定义在另外的模块时

Python支持多父类的继承机制,所以需要注意圆括号中基类的顺序,若是基类中有相同的方法名,并且在子类使用时未指定,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
25
# 父类定义
class people:

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

def speak(self):
print("%s 说: 我 %d 岁。" % (self.name, self.age))

# 单继承示例
class student(people):

def __init__(self, name, age, weight, grade):
# 调用父类的实例化方法
people.__init__(self, name, age, weight)
self.grade = grade

# 重写父类的speak方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级" % (self.name, self.age, self.grade))

s = student('ken', 10, 30, 3)
s.speak()

Python3的继承机制不同于Python2。其核心原则是下面两条,请谨记!
子类在调用某个方法或变量的时候,首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。
根据父类定义中的顺序,以深度优先的方式逐一查找父类!

super()函数
我们都知道,在子类中如果有与父类同名的成员,那就会覆盖掉父类里的成员。那如果你想强制调用父类的成员呢?使用super()函数!这是一个非常重要的函数,最常见的就是通过super调用父类的实例化方法__init__

语法:super(子类名, self).方法名(),需要传入的是子类名和self,调用的是父类里的方法,按父类的方法需要传入参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A:
def __init__(self, name):
self.name = name
print("父类的__init__方法被执行了!")
def show(self):
print("父类的show方法被执行了!")

class B(A):
def __init__(self, name, age):
super(B, self).__init__(name=name)
self.age = age

def show(self):
super(B, self).show()

obj = B("jack", 18)
obj.show()

多态
先举个例子

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
33
34
35
36
37
38
39
40
41
42
class Animal:

def kind(self):
print("i am animal")


class Dog(Animal):

def kind(self):
print("i am a dog")


class Cat(Animal):

def kind(self):
print("i am a cat")


class Pig(Animal):

def kind(self):
print("i am a pig")

# 这个函数接收一个animal参数,并调用它的kind方法
def show_kind(animal):
animal.kind()


d = Dog()
c = Cat()
p = Pig()

show_kind(d)
show_kind(c)
show_kind(p)

# ------------------
# 打印结果:

# i am a dog
# i am a cat
# i am a pig

看上去与其他面向对象语言的多态没有区别,实际上,由于Python的动态语言特性,传递给函数show_kind()的参数animal可以是 任何的类型,只要它有一个kind()的方法即可。动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

1
2
3
4
5
6
7
class Job:

def kind(self):
print("i am not animal, i am a job")

j = Job()
show_kind(j)

可能有人会觉得,这些内容很自然啊,没什么不好理解,不觉得多态有什么特殊,Python就是这样啊!

如果你学过JAVA这一类强类型静态语言,就不会这么觉得了,对于JAVA,必须指定函数参数的数据类型,只能传递对应参数类型或其子类型的参数,不能传递其它类型的参数,show_kind()函数只能接收animal、dog、cat和pig类型,而不能接收job类型。就算接收dog、cat和pig类型,也是通过面向对象的多态机制实现的。

成员保护和访问限制
在类似JAVA的语言中,有private关键字,可以将某些变量和方法设为私有,阻止外部访问。但是,Python没有这个机制,Python利用变量和方法名字的变化,实现这一功能。
在Python中,如果要让内部成员不被外部访问,可以在成员的名字前加上两个下划线__,这个成员就变成了一个私有成员(private)。私有成员只能在类的内部访问,外部无法访问。

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
class People:
title = "人类"

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

def print_age(self):
print('%s: %s' % (self.__name, self.__age))

def get_name(self):
return self.__name

def get_age(self):
return self.__age

def set_name(self, name):
self.__name = name

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

obj = People("jack", 18)
obj.get_name()
obj.set_name("tom")

python的有趣特性
以双下划线开头的数据成员是不是一定就无法从外部访问呢?其实也不是!本质上,从内部机制原理讲,外部不能直接访问__age是因为Python解释器对外把__age变量改成了_People__age,也就是_类名__age(类名前是一个下划线)。因此,投机取巧的话,你可以通过_ People__age在类的外部访问__age变量:

1
2
obj = People("jack", 18)
print(obj._People__name)

由于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
25
26
class People:
title = "人类"

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

def print_age(self):
print('%s: %s' % (self.__name, self.__age))

def get_name(self):
return self.__name

def set_name(self, name):
self.__name = name


obj = People("jack", 18)
obj.__name = "tom" # 注意这一行
print("obj.__name: ", obj.__name)
print("obj.get_name(): ", obj.get_name())

# -------------------
# 打印结果:
# obj.__name: tom
# obj.get_name(): jack

此时的obj.__name= 'tom',相当于给obj实例添加了一个新的实例变量__name,而不是对原有私有成员__name的重新赋值。

此外,有些时候,你会看到以一个下划线开头的成员名,比如_name,这样的数据成员在外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的标识符时,意思就是,“虽然我可以被外部访问,但是,请把我视为私有成员,不要在外部访问我!”。

还有,在Python中,标识符类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊成员,特殊成员不是私有成员,可以直接访问,要注意区别对待。同时请尽量不要给自定义的成员命名__name____iter__这样的标识,它们都是Python中具有特殊意义的魔法方法名。

类的成员与下划线总结:
_name、_name_、_name__:建议性的私有成员,不要在外部访问。
__name、 __name_ :强制的私有成员,但是你依然可以蛮横地在外部危险访问。
__name__:特殊成员,与私有性质无关,例如__doc__
name_、name__:没有任何特殊性,普通的标识符,但最好不要这么起名。

伪装类方法

  1. @property装饰器——更多有关装饰器的知识

Python内置的@property装饰器可以把类的方法伪装成属性调用的方式。也就是本来是Foo.func()的调用方法,变成Foo.func的方式。在很多场合下,这是一种非常有用的机制。

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
class People:

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

@property
def age(self):
return self.__age

@age.setter
def age(self, age):
if isinstance(age, int):
self.__age = age
else:
raise ValueError

@age.deleter
def age(self):
print("删除年龄数据!")

obj = People("jack", 18)
print(obj.age)
obj.age = 19
print("obj.age: ", obj.age)
del obj.age

---------------------------
打印结果:
18
obj.age: 19
删除年龄数据!

使用方法:

1
2
3
4
obj = People("jack", 18)
a = obj.age # 获取值
obj.age = 19 # 重新赋值
del obj.age # 删除属性

将一个方法伪装成为属性后,就不再使用圆括号的调用方式了。而是类似变量的赋值、获取和删除方法了。当然,每个动作内部的代码细节还是需要你自己根据需求去实现的。

那么如何将一个普通的方法转换为一个“伪装”的属性呢?

  • 首先,在普通方法的基础上添加@property装饰器,例如上面的age()方法。这相当于一个get方法,用于获取值,决定类似"result = obj.age"执行什么代码。该方法仅有一个self参数。
  • 写一个同名的方法,添加@xxx.setter装饰器(xxx表示和上面方法一样的名字),比如例子中的第二个方法。这相当于编写了一个set方法,提供赋值功能,决定类似"obj.age = ...."的语句执行什么代码。
  • 再写一个同名的方法,并添加@xxx.delete装饰器,比如例子中的第三个方法。用于删除功能,决定"del obj.age "这样的语句具体执行什么代码。

简而言之,就是分别将三个方法定义为对同一个属性的获取、修改和删除。还可以定义只读属性,也就是只定义getter方法,不定义setter方法就是一个只读属性。

property()函数

除了使用装饰器的方式将一个方法伪装成属性外,Python内置的builtins模块中的property()函数,为我们提供了第二种设置类属性的手段。

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
class People:

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

def get_age(self):
return self.__age

def set_age(self, age):
if isinstance(age, int):
self.__age = age
else:
raise ValueError

def del_age(self):
print("删除年龄数据!")

# 核心在这句
age = property(get_age, set_age, del_age, "年龄")


obj = People("jack", 18)
print(obj.age)
obj.age = 19
print("obj.age: ", obj.age)
del obj.age

通过语句age = property(get_age, set_age, del_age, "年龄")将一个方法伪装成为属性。其效果和装饰器的方法是一样的。

property()函数的参数:

  • 第一个参数是方法名,调用 实例.属性 时自动执行的方法
  • 第二个参数是方法名,调用 实例.属性 = XXX时自动执行的方法
  • 第三个参数是方法名,调用 del 实例.属性 时自动执行的方法
  • 第四个参数是字符串,调用 实例.属性.__doc__时的描述信息。

特殊成员和魔法方法
Python中有大量类似__doc__这种以双下划线开头和结尾的特殊成员及“魔法方法”,它们有着非常重要的地位和作用,也是Python语言独具特色的语法之一!

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__init__ :      构造函数,在生成对象时调用
__del__ : 析构函数,释放对象时使用
__repr__ : 打印,转换
__setitem__ : 按照索引赋值
__getitem__: 按照索引获取值
__len__: 获得长度
__cmp__: 比较运算
__call__: 调用
__add__: 加运算
__sub__: 减运算
__mul__: 乘运算
__div__: 除运算
__mod__: 求余运算
__pow__: 幂

需要注意的是,这些成员里面有些是方法,调用时要加括号,有些是属性,调用时不需要加括号(废话!)。
具体用法请查看python教程

异常处理

在程序运行过程中,总会遇到各种各样的问题和错误。有些错误是我们编写代码时自己造成的,比如语法错误、调用错误,甚至逻辑错误。还有一些错误,则是不可预料的错误,但是完全有可能发生的,比如文件不存在、磁盘空间不足、网络堵塞、系统错误等等。这些导致程序在运行过程中出现异常中断和退出的错误,我们统称为异常。大多数的异常都不会被程序处理,而是以错误信息的形式展现出来。

异常有很多种类型,Python内置了几十种常见的异常,就在builtins模块内,无需特别导入,直接就可使用。需要注意的是,所有的异常都是异常类,首字母是大写的!

在发生异常的时候,Python会打印出异常信息,信息的前面部分显示了异常发生的上下文环境,并以调用栈的形式显示具体信息。异常类型作为信息的一部分也会被打印出来,例如ZeroDivisionError,NameError 和 TypeError。

1
2
3
4
5
6
7
8
9
10
11
12
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: Can't convert 'int' object to str implicitly

为了保证程序的正常运行,提高程序健壮性和可用性。我们应当尽量考虑全面,将可能出现的异常进行处理,而不是留在那里,任由其发生。

Python内置了一套try…except…finally(else)…的异常处理机制,来帮助我们进行异常处理。其基本语法是:

1
2
3
4
try:
pass
except Exception as ex:
pass

注:在Python3中,原Python2的except Exception , ex的别名方法已经不能使用,逗号被认为是两种异常的分隔符,而不是取别名。

Python的异常机制具有嵌套处理的能力,比如下面的函数f3()调用f2(),f2()调用f1(),虽然是在f1()出错了,但只需要在f3()进行异常捕获,不需要每一层都捕获异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def f1():
return 10/0

def f2():
f1()

def f3():
f2()

f3()
------------------------
Traceback (most recent call last):
File "F:/Python/pycharm/201705/1.py", line 10, in <module>
f3()
File "F:/Python/pycharm/201705/1.py", line 8, in f3
f2()
File "F:/Python/pycharm/201705/1.py", line 5, in f2
f1()
File "F:/Python/pycharm/201705/1.py", line 2, in f1
return 10/0
ZeroDivisionError: division by zero

仅仅需要在调用f3()函数的时候捕获异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
def f1():
return 10/0

def f2():
f1()

def f3():
f2()

try:
f3()
except ZeroDivisionError as e:
print(e)

try…except…语句处理异常的工作机制如下:

  • 首先,执行try子句(在关键字try和关键字except之间的语句)
  • 如果没有异常发生,忽略except子句,try子句执行后结束。
  • 如果在执行try子句的过程中发生了异常,那么try子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的except子句将被执行。
1
2
3
4
5
6
7
8
9
10
11
try:
print("发生异常之前的语句正常执行")
print(1/0)
print("发生异常之后的语句不会被执行")
except ZeroDivisionError as e:
print(e)

#--------------
结果:
发生异常之前的语句正常执行
division by zero
  • 如果程序发生的异常不在你的捕获列表中,那么依然会抛出别的异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
# 未捕获到异常,程序直接报错

s1 = 'hello'
try:
int(s1)
except IndexError as ex: # 本例为非法值异常,而你只捕获索引异常
print(ex)

# -----------
Traceback (most recent call last):
File "F:/Python/pycharm/201705/1.py", line 5, in <module>
int(s1)
ValueError: invalid literal for int() with base 10: 'hello'
  • 如果一个异常没有与任何的except匹配,那么这个异常将会传递给上层的try中。也就是前面说的嵌套处理能力。直到程序最顶端如果还没有被捕获,那么将弹出异常。
1
2
3
4
5
6
7
8
9
10
try:
try:
print("发生异常之前的语句正常执行")
print(1/0)
print("发生异常之后的语句不会被执行")
except ValueError as e:
print(e)

except ZeroDivisionError as e:
print("里层没有抓好,只能辛苦我外层了")
  • 可能包含多个except子句,分别来处理不同的特定的异常。但最多只有一个分支会被执行。所以except子句有排序先后问题,进了一条巷子就不会进别的巷子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
try:
print("发生异常之前的语句正常执行")
print(1/0)
print("发生异常之后的语句不会被执行")
except NameError as e:
print(e)
except ZeroDivisionError as e:
print("我是第一个抓取到除零异常的")
except (ValueError,ZeroDivisionError) as e:
print("我是备胎")

#------------
发生异常之前的语句正常执行
我是第一个抓取到除零异常的
  • 处理程序将只针对对应的try子句中的异常进行处理,不会处理其他try语句中的异常。
  • 一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组。
1
2
except (RuntimeError, TypeError, NameError):
pass
  • 最后一个except子句可以忽略异常的名称,它将被当作通配符使用,也就是说匹配所有异常。
1
2
except:
print("Unexpected error:", sys.exc_info()[0])

看一个综合性的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
import sys

try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise

通用异常:Exception

在Python的异常中,有一个通用异常:Exception,它可以捕获任意异常。

1
2
3
4
5
s1 = 'hello'
try:
int(s1)
except Exception as e:
print('错误')

那么既然有这个什么都能管的异常,其他诸如OSError、ValueError的异常是不是就可以不需要了?当然不是!很多时候程序只会弹出那么几个异常,没有必要针对所有的异常进行捕获,那样的效率会很低。另外,根据不同的异常种类,制定不同的处理措施,用于准确判断错误类型,存储错误日志,都是非常有必要甚至强制的。

finally和else子句

try except语法还有一个可选的else子句,如果使用这个子句,那么必须放在所有的except子句之后。这个子句将在try子句没有发生任何异常的时候执行。

1
2
3
4
5
6
7
8
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()

同样的,还有一个可选的finally子句。无论try执行情况和except异常触发情况,finally子句都会被执行!

1
2
3
4
5
6
7
8
9
try:
print('try...')
r = 10 / int('a')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
finally:
print('finally...')
print('END')

那么,当else和finally同时存在时:

1
2
3
4
5
6
7
8
try:
pass
except:
pass
else:
print("else")
finally:
print("finally")

运行结果:

1
2
else
finally

如果有异常发生:

1
2
3
4
5
6
7
8
try:
1/0
except:
pass
else:
print("else")
finally:
print("finally")

运行结果:

1
finally

主动抛出异常:raise

很多时候,我们需要主动抛出一个异常。Python内置了一个关键字raise,可以主动触发异常。

1
2
3
4
5
6
7
8
9
10
>>> raise
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
raise
RuntimeError: No active exception to reraise
>>> raise NameError("kkk")
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
raise NameError("kkk")
NameError: kkk

raise唯一的一个参数指定了要被抛出的异常的实例,如果什么参数都不给,那么会默认抛出当前异常。

可能有同学会问,为什么要自己主动抛出异常?不嫌多事么?因为有的时候,你需要记录错误信息,然后将异常继续往上层传递,让上层去处理异常,如下:

1
2
3
4
5
6
try:
1/0
except ZeroDivisionError as ex:
print("记录异常日志: ", ex)
print("但是我自己无法处理,只能继续抛出,看看上层能否处理(甩锅)")
raise

有时候,你需要主动弹出异常,作为警告或特殊处理:

1
2
3
4
5
6
7
8
9
10
11
12
sex = int(input("Please input a number: "))

try:
if sex == 1:
print("这是个男人!")
elif sex == 0:
print("这是个女人!")
else:
print("好像有什么不符合常理的事情发生了!!")
raise ValueError("非法的输入")
except ValueError:
print("这是个人妖!")

更多的时候,你需要使用raise抛出你自定义的异常,如下面所述!

自定义异常

Python内置了很多的异常类,并且这些类都是从BaseException类派生的。

下面是一些常见异常类,请把它们记下来!这样你在见到大多数异常的时候都能快速准确的判断异常类型。

异常名 解释
AttributeError 试图访问一个对象没有的属性
IOError 输入/输出异常
ImportError 无法引入模块或包;多是路径问题或名称错误
IndentationError 缩进错误
IndexError 下标索引错误
KeyError 试图访问不存在的键
KeyboardInterrupt Ctrl+C被按下,键盘终止输入
NameError 使用未定义的变量
SyntaxError 语法错误
TypeError 传入对象的类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量
ValueError 传入一个调用者不期望的值,即使值的类型是正确的
OSError 操作系统执行错误

大多数情况下,上面的内置异常已经够用了,但是有时候你还是需要自定义一些异常。自定义异常应该继承Exception类,直接继承或者间接继承都可以,例如:

1
2
3
4
5
6
7
8
9
10
11
12
class MyExcept(Exception):

def __init__(self, msg):
self.message = msg

def __str__(self):
return self.message

try:
raise MyExcept('我的异常!')
except MyExcept as ex:
print(ex) 

异常的名字都以Error结尾,我们在为自定义异常命名的时候也需要遵守这一规范,就跟标准的异常命名一样。

模块与包

在编程语言中,代码块、函数、类、模块,一直到包,逐级封装,层层调用。**在Python中,一个.py文件就是一个模块,模块是比类更高一级的封装。**在其他语言,被导入的模块也通常称为库。

模块可以分为自定义模块、内置模块和第三方模块。自定义模块就是你自己编写的模块,如果你自认水平很高,也可以申请成为Python内置的标准模块之一!如果你在网上发布自己的模块并允许他人使用,那么就变成了第三方模块。内置模块是Python“内置电池”哲学的体现,在安装包里就提供了跨平台的一系列常用库,涉及方方面面。第三方模块的数量非常庞大,有许多非常有名并且影响广泛的模块,比如Django。

使用模块有什么好处?

  • 首先,提高了代码的可维护性。
  • 其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他的模块引用。不要重复造轮子,我们简简单单地使用已经有的模块就好了。
  • 使用模块还可以避免类名、函数名和变量名发生冲突。相同名字的类、函数和变量完全可以分别存在不同的模块中。但是也要注意尽量不要与内置函数名(类名)冲突。

为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package),包是模块的集合,比模块又高一级的封装。没有比包更高级别的封装,但是包可以嵌套包,就像文件目录一样

包名通常为全部小写,避免使用下划线。


要在我们的程序中,使用其它的模块(包、类、函数),就必须先导入对应的模块(包、类、函数)。在Python中,模块(包、类、函数)的导入方式有以下四种:

  • import xx.xx
  • from xx.xx import xx
  • from xx.xx import xx as rename
  • from xx.xx import *

对于xx.xx的说明:

由于一个模块可能会被一个包封装起来,而一个包又可能会被另外一个更大的包封装起来,所以我们在导入的时候,需要提供导入对象的绝对路径,也就是“最顶层的包名.次一级包名.(所有级别的包名).模块名.类名.函数名”。类似文件系统的路径名,只是用圆点分隔的。

有时候,模块名就在搜索路径根目录下,那么可以直接import 模块名,比如Python内置的一些标准模块,os、sys、time等等。

大多数时候,我们不需要直接导入到函数的级别,只需要导入到模块级别或者类的级别,就只需要使用import Django.contrib.auth.models导入models模块,以后使用models.User来引用models模块中的类。

总之,对于xx.xx,你想导入到哪个级别,取决于你的需要,可以灵活调整,没有固定的规则。

  1. import xx.xx

这会将对象(这里的对象指的是包、模块、类或者函数,下同)中的所有内容导入。如果该对象是个模块,那么调用对象内的类、函数或变量时,需要以module.xxx的方式。

比如,被导入的模块Module_a

1
2
3
4
# Module_a.py

def func():
print("this is module A!")

Main.py中导入Module_a

1
2
3
4
5
# Main.py

import module_a

module_a.func() # 调用方法
  1. From xx.xx import xx.xx

从某个对象内导入某个指定的部分到当前命名空间中,不会将整个对象导入。这种方式可以节省写长串导入路径的代码,但要小心名字冲突。

Main.py中导入Module_a

1
2
3
4
5
6
7
# Main.py

from module_a import func

module_a.func() # 错误的调用方式

func() # 这时需要直接调用func
  1. from xx.xx import xx as rename

为了避免命名冲突,在导入的时候,可以给导入的对象重命名。

1
2
3
4
5
6
7
8
9
# Main.py

from module_a import func as f

def func(): ## main模块内部已经有了func函数
print("this is main module!")

func()
f()
  1. from xx.xx import *

将对象内的所有内容全部导入。非常容易发生命名冲突,请慎用!

1
2
3
4
5
6
7
8
# Main.py

from module_a import *

def func():
print("this is main module!")

func() # 从module导入的func被main的func覆盖了

执行结果:this is main module!

模块搜索路径

不管你在程序中执行了多少次import,一个模块只会被导入一次。这样可以防止一遍又一遍地导入模块,节省内存和计算资源。那么,当使用import语句的时候,Python解释器是怎样找到对应的文件的呢?

Python根据sys.path的设置,按顺序搜索模块。

1
2
3
>>> import sys
>>> sys.path
['', 'C:\\Python36\\Lib\\idlelib', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages']

当然,这个设置是可以修改的,就像windows系统环境变量中的path一样,可以自定义。 通过sys.path.append('路径')的方法为sys.path路径列表添加你想要的路径。

1
2
3
4
5
import sys
import os

new_path = os.path.abspath('../')
sys.path.append(new_path)

默认情况下,模块的搜索顺序是这样的:

  1. 当前执行脚本所在目录
  2. Python的安装目录
  3. Python安装目录里的site-packages目录

其实就是“自定义”——>“内置”——>“第三方”模块的查找顺序。任何一步查找到了,就会忽略后面的路径,所以模块的放置位置是有区别的。
总结:千万不要和内置模块或常用第三方模块同名!否则,哪怕你认为自己掌控着一切,也有可能会发生各种意想不到的结果!
只有包含__init__.py文件的目录才会被认作是一个包!__init__.py本身就是一个模块,但是要注意,它的模块名是它所在的包名而不是__init__

一个使用第三方模块——openpyxl操作excel的小例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from openpyxl import Workbook, load_workbook

wb1 = load_workbook("普通学生专业分流成绩-公示.xlsx")
wb2 = load_workbook("2022专业分流班级信息列表.xlsx")
wb3 = Workbook()
sheet = wb3.active
sheet1 = wb1["sheet1"]
sheet2 = wb2["全部"]
count = 1
sheet.append(["排名", "原排名", "姓名", "性别", "班号"])
for row in sheet1.iter_rows(min_row=2, max_row=458, max_col=3):
name = row[2].value
for row2 in sheet2.iter_rows(min_row=356, max_row=459):
if name == row2[2].value:
sheet.append([count, row[0].value, name, row2[3].value, str(row2[5].value)[5]])
count += 1
wb3.save("2022级网安排名.xlsx")

常用标准库

Python奉行“内置电池”的哲学,内置了大量、全面、高效和跨平台的标准库,提供了非常丰富的功能,能够满足大多数常见的需求。并且很多都是用C语言编写的,执行速度也很快。

我们应该尽量多学习这些标准库,理解它们、掌握它们然后使用它们,避免重复造轮子,增强我们的开发能力,提高开发速度。

以下为常用的模块:

  • os
  • sys
  • subprocess
  • random
  • bisect
  • hashlib
  • queue
  • fileinput
  • shutil
  • zipfile
  • tarfile
  • getpass
  • shelve
  • json
  • pickle
  • time
  • datetime
  • timeit
  • logging
  • re

具体用法可查看python常用标准库

那么到此为止关于python(基础篇)的内容就将结束,当然还有很多方面没有涉猎,包括但不限于python与数据库的联合,多进程协程与异步,web开发与网络爬虫等等,碍于篇幅,今后填坑