python(基础篇)笔记
python基础知识
引言
作为我第一门系统学习的解释性脚本语言,先理解解释型语言和编译型语言的区别
编译型源代码–>编译器–>可执行文件 (一次编译,多次运行,效率高,移植性差)
解释型源代码–>解释器
故首先需按安装python解释器,本文以3.10.11
为准
在没有IDE的情况下,我们有两种方法执行python代码:
-
交互式命令行
-
运行文本文件
首先我们需要用dos命令找到安装的python解释器(若将其添加到全局PATH中,可省略)
dir
显示当前路径下的文件夹
cd
转到对应路径(仅限同一盘符)cd …/ 回退一级
D:
转移到对应盘符
tab
补全输入内容
为了便捷,接下来都将以**Pycharm(22.10.3 professional)**作为集成开发环境辅助学习
基本语法
首先补充基本的输入输出函数
ctrl
+左键点击
可查看内置函数的源码
input && print
1 | name = input("请输入姓名:>") |
input默认接收的均为字符串,可用类型转换函数type(s)转换
语法结构
1.语句分隔符
python中通常以换行符
作为语句分隔符
若想在同一行中执行多句语句,也可用;
作为分隔(不推荐,不符合pep8规范)
python代码应符合pep8规范,在pycharm中,绿色波浪线代表违反规范,按Ctrl+Alt+L可一键调整
一行写一条语句,大大提高代码的可读性和规范性
1 | print(1+1) |
注意
:python代码缩进要求严格,允许空行,不可缩进
2.注释
python中用#
单行注释
三引号(单引号或双引号均可)多行注释
1 | import random # 导入获取随机数的random模块 |
变量
特性:
- 多次使用(不会被垃圾回收机制回收)
- 值可修改 (和编译型语言不同,修改时指向变化,原值被回收)
变量名应符合规范
基本数据类型
基本数据类型:
- 整型与浮点型
- bool类型(True、False)
- 字符串
python内置函数type()返回数据类型名
1 | print(type(10)) #<class 'int'> |
内置函数bool,可求变量或表达式的bool值
零值:所有数据类型中有且仅有一个值的bool类型为False 字符串 ""列表 [] 字典 {}
字符串作用:
- 展示文本
- 储存数据
- 传递字节
1 | s1 = "hello a1" # 方式1 |
字符串基本操作
转义符 \
- 给予普通符号特殊功能
- 将具有特殊功能的符号普通化
符号 | 功能 |
---|---|
\" |
双引号 |
\\ |
反斜杠 |
\n |
换行符 |
\t |
制表符 |
\r |
回车符 |
\' |
单引号 |
格式化输出
1 | name = "byf" |
字符串序列操作
python支持负索引,从右(-1)到左依次减一
字符串是不可变的,不能像列表一样修改其中某个数据
索引操作 查询字符 : 字符串[索引]
print(s[0])
切片操作 获取字符串 : 字符串[开始索引:结束索引:步长(默认为1)](顾头不顾尾)
print(s[0:5])
缺省默认从头尾取
步长为2时,隔一个取一个字符,以此类推
步长为负时,从右向左取
一个有趣的应用
1 | # 字符串倒序 |
拼接操作 字符串1+字符串2 || 字符串*int
1 | s1 = "hello " |
计算字符串长度 内置函数len
print(len(s1))
中文算作一个字符
in判断 判断某个成员是否存在(返回bool类型)
1 | s1 = "hello Oracle" |
字符串内置方法
内置函数 | 功能 | 实例 |
---|---|---|
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(",") |
其他方法:
- find 搜索指定字符串,返回索引,没有返回-1
- index 功能同上,但找不到时报错
- count 计算字符串中子串出现的顺序
- replace 将字符串中字串替换为指定字符串
- center 将字符串左右添加填充符到指定长度
- encode 将字符串按指定方式编码
replace 缺省默认全部替换,传入int型可指定替换个数
运算符
计算运算符:+
、-
、*
、/
、%
比较运算符:>
、<
、>=
、<=
、==
、!=
返回类型为bool
赋值运算符:=
、+=
、-=
、/=
、*=
、%=
逻辑运算符:and
、or
、not
python范围判断可以直接写成 18<age<35
成员运算符:in
流程控制语句
多分支语句
注意四个空格的缩进和英文冒号
1 | age = int(input("输入你的年龄:>")) |
多分支
1 | weight = float(input("请输入你的体重(公斤):>")) |
循环语句
while循环
1 | count = 1 |
for循环
对一个序列(如字符串、列表、元组)进行遍历操作
1 | lis = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
break
退出整个循环
continue
退出当前循环
高阶数据类型
列表
[]
内以逗号分隔,按照索引存放各种数据类型
可修改指定索引位置对应的值
列表的操作
追加:数据被追加到尾部
1 | letter = ['a', 'b', 'c', 'd'] |
插入:可插入列表中任一位置(也可用此方法嵌套列表)
1 | letter = ['a', 'b', 'd', 'e'] |
合并:把另一列表的值合并进来
1 | letter1 = ['a', 'b'] |
del删除:直接删除指定位置元素
1 | letter = ['a', 'b', 'c', 'd', 'e'] |
pop弹出:弹出列表最后一个元素并返回(也可指定位置)
1 | letter = ['a', 'b', 'c', 'd', 'e'] |
remove删除:指定元素名删除,如有多个删除从左到右第一个
clear清空:清除列表中所有元素
index查找:返回从左到右第一个指定元素的索引
切片操作和字符串类似
sort排序
1 | letter = [1,2,7,3,8,4,5,9] |
reverse反转
1 | letter = [1,2,7,3,8,4,5,9] |
字典
为满足快速查询,引入key-value的字典类型,以花括号包裹
key必须为不可变的数据类型(如数字、字符串)
1 | dic = { |
字典也可以进行嵌套
增删改查操作
- 增加:
dic["a"] = "error"
直接对新key值赋值即可添加 - 删除:
del dic["x"]
、dic.pop("y")
、dic.clear()
前两个为清除指定key,后一个为清空 - 修改:操作同增加,若key值存在,即会修改value值
- 查找
dic.keys()
返回一个包含字典所有KEY的列表;dic.values()
返回一个包含字典所以value的列表;dic.items()
返回一个包含所有(键、值)元组的列表
循环
for k in dic.keys()
for k,v in dic.items()
for k in dic
(推荐,效率更快)
元组
元组(tuple)是Python中另一种内置的存储有序数据的结构。
元组与列表类似,也是由一系列按特定顺序排列的元素组成,可存储不同类型的数据,
如字符串、数字甚至元组。然而,元组是不可改变的,创建后不能再做任何修改操作。
元组可作为字典的键值,而列表不行
元组由()包裹
删除元组
元组中的元素值是不允许更改的,但可以使用del语句整个删除
1 | data = range(1, 11) |
元组常见操作
元组是不可变序列,虽然不能对单个元素值进行修改,但可以重新赋值以达到更改效果
len(tup)
max(tup)
min(tup)
元组和列表的相互转换
tuple()
函数接收一个列表,返回一个元组
list()
函数接收一个元组,返回一个列表
文件操作
python3在内存中默认用Unicode编码,但存在文件里或网络发送,用utf-8编码
字节类型就是二进制格式,通常用十六进制来表示,编码后转成bytes字节类型,可防止显示乱码
操作文件流程:
- 打开文件
- 读 || 修改
- 保存 && 关闭
打开文件(文本模式)
- r 只读模式
- w 创建模式,若文件已经存在,则覆盖旧文件
- a 追加模式,新数据会写到文件末尾
1 | # w mode |
文件循环遍历
循环遍历时,将文件每一行的数据以列表形式存入line中
1 | f = open("name_list") |
二进制文件操作(图片/视频)
encodin=None,该参数告诉你的解释器当亲要打开文件的编码,若缺省,则用解释器的默认编码,也就是utf-8
图片和视频底层也是二进制,用二进制形式打开
1 | f = open("yuan.png", "rb") |
rb
二进制只读
wb
二进制只写
其他常用功能
seek
把操作文件的光标移动到指定位置
1 | f = open("name_list", mode="r", encoding="UTF-8") |
按字节操作,utf-8编码中中文占三个字节,西欧符号占两个字节,英文和数字占一个字节
写文件时会覆盖光标后的文本
经常搭配tell函数使用,其作用是返回当前光标的位置
flush 把文件从内存buffer里强制刷新到硬盘
truncate 以光标位置截断文件,将光标后数据删除
其他打开方式(混合模式)
w+ 写读:创建一个新文件,可写入文本,也可将写入的内容读出来
r+ 读写:能读能写,但都是些在文件最后,类似追加
a+ 追加读:写入数据均以追加的形式
文件修改
前面提到的文件修改会将当前光标后的数据替换掉,而我们正常需求一般是在光标位置前插入文本数据。要实现这点,需打开文件后,先将数据读到内存中,添加数据后再重新写入硬盘
练习
编写一个全局文本检索替换脚本
要求在命令行中执行python replace.py old_str new_str filename
1 | import sys |
另一种方法:
创建一个新文件,将原文件数据按行读入新文件,并加以替换,最后将原文件删除
函数编程
特性:
- 减少重复代码
- 使程序可扩展
- 使程序易维护
基本语法
1 | def sayhello(name): |
值得注意python中函数形参和实参传入机制与其他编程语言不同,与传入的数据类型(可变 or 不可变有关),可参考Python中变量的本质
python也可使用默认参数调用函数,语法和C++类似,缺省参数定义需在最后。一般按位置关系确定对应关系的参数,但也可指定参数名,指定参数调用时必须放在默认参数之后
非固定参数
若在函数定义时不确定要传入多少参数,可以使用非固定参数
1 | def stu_register(name, age, *args, **kwargs): |
函数返回值
若想在函数外部想获取函数内部产生的值,可以用return语句将值返回
python可同时返回多个值,写成return a,b,c
其值存放在元组中(a, b, c)
函数执行到return就代表函数结束,后面的代码不会执行
全局变量与局部变量
1 | a = 1 # 全局变量 |
global:指定当前变量使用外部的全局变量(直接使用最外层的)
1 | a = 1 |
若有多层嵌套,且仅想使用上一层的变量,可使用nonlocal
关键字声明
递归函数
python语言也支持递归函数
举例一个简单的高斯求和函数
1 | def Gaosi(n): |
常用内置函数
内置函数 | 功能 |
---|---|
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 | class 类名(父类列表): |
类名通常采用驼峰式命名方式,尽量让字面意思体现出类的作用。Python采用多继承机制,一个类可以同时继承多个父类(也叫基类、超类),继承的基类有先后顺序,写在类名后的圆括号里。继承的父类列表可以为空,此时的圆括号可以省略。但在Python3中,即使你采用类似class Student:pass的方法没有显式继承任何父类的定义了一个类,它也默认继承object类。因为,object是Python3中所有类的基类。
下面是一个学生类:
1 | class Student: |
可以通过调用类的实例化方法(有的语言中也叫初始化方法或构造函数)来创建一个类的实例。默认情况下,使用类似obj=Student()的方式就可以生成一个类的实例。但是,通常每个类的实例都会有自己的实例变量,例如这里的name和age,为了在实例化的时候体现实例的不同,Python提供了一个def init(self):的实例化机制。任何一个类中,名字为__init__的方法就是类的实例化方法,具有__init__方法的类在实例化的时候,会自动调用该方法,并传递对应的参数。比如:
1 | li = Student("李四", 24) |
实例变量和类变量
实例变量和类变量
实例变量:
实例变量指的是实例本身拥有的变量。每个实例的变量在内存中都不一样。Student类中__init__方法里的name和age就是两个实例变量。通过实例名加圆点的方式调用实例变量。
我们打印下面四个变量,可以看到每个实例的变量名虽然一样,但他们保存的值却是各自独立的:
1 | print(li.name) |
类变量:
定义在类中,方法之外的变量,称作类变量。类变量是所有实例公有的变量,每一个实例都可以访问、修改类变量。在Student类中,classroom和address两个变量就是类变量。可以通过类名或者实例名加圆点的方式访问类变量,比如:
1 | Student.classroom |
在使用实例变量和类变量的时候一定要注意,使用类似zhang.name访问变量的时候,实例会先在自己的实例变量列表里查找是否有这个实例变量,如果没有,那么它就会去类变量列表里找,如果还没有,弹出异常。
Python动态语言的特点,让我们可以随时给实例添加新的实例变量,给类添加新的类变量和方法。因此,在使用li.classroom = '102’的时候,要么是给已有的实例变量classroom重新赋值,要么就是新建一个li专属的实例变量classroom并赋值为‘102’。看下面的例子:
1 | class Student: # 类的定义体 |
为了防止发生上面的混淆情况,对于类变量,请坚持使用类名.类变量的访问方式,不要用实例去访问类变量。
封装、继承和多态
封装
封装的思想不多赘述,只举具体实例
1 | class Student: |
继承
继承最大的好处是子类获得了父类的全部变量和方法的同时,又可以根据需要进行修改、拓展。其语法结构如下:
1 | class Foo(superA, superB,superC....): |
Python支持多父类的继承机制,所以需要注意圆括号中基类的顺序,若是基类中有相同的方法名,并且在子类使用时未指定,Python会从左至右搜索基类中是否包含该方法。一旦查找到则直接调用,后面不再继续查找。
1 | # 父类定义 |
Python3的继承机制不同于Python2。其核心原则是下面两条,请谨记!
子类在调用某个方法或变量的时候,首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。
根据父类定义中的顺序,以深度优先的方式逐一查找父类!
super()函数
我们都知道,在子类中如果有与父类同名的成员,那就会覆盖掉父类里的成员。那如果你想强制调用父类的成员呢?使用super()函数!这是一个非常重要的函数,最常见的就是通过super调用父类的实例化方法__init__
!
语法:super(子类名, self).方法名()
,需要传入的是子类名和self,调用的是父类里的方法,按父类的方法需要传入参数。
1 | class A: |
多态
先举个例子
1 | class Animal: |
看上去与其他面向对象语言的多态没有区别,实际上,由于Python的动态语言特性,传递给函数show_kind()的参数animal可以是 任何的类型,只要它有一个kind()的方法即可。动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
1 | class Job: |
可能有人会觉得,这些内容很自然啊,没什么不好理解,不觉得多态有什么特殊,Python就是这样啊!
如果你学过JAVA这一类强类型静态语言,就不会这么觉得了,对于JAVA,必须指定函数参数的数据类型,只能传递对应参数类型或其子类型的参数,不能传递其它类型的参数,show_kind()函数只能接收animal、dog、cat和pig类型,而不能接收job类型。就算接收dog、cat和pig类型,也是通过面向对象的多态机制实现的。
成员保护和访问限制
在类似JAVA的语言中,有private关键字,可以将某些变量和方法设为私有,阻止外部访问。但是,Python没有这个机制,Python利用变量和方法名字的变化,实现这一功能。
在Python中,如果要让内部成员不被外部访问,可以在成员的名字前加上两个下划线__,这个成员就变成了一个私有成员(private)。私有成员只能在类的内部访问,外部无法访问。
1 | class People: |
python的有趣特性
以双下划线开头的数据成员是不是一定就无法从外部访问呢?其实也不是!本质上,从内部机制原理讲,外部不能直接访问__age
是因为Python解释器对外把__age
变量改成了_People__age
,也就是_类名__age
(类名前是一个下划线)。因此,投机取巧的话,你可以通过_ People__age
在类的外部访问__age
变量:
1 | obj = People("jack", 18) |
由于Python内部会对双下划线开头的私有成员进行名字变更,所以会出现下面的情况:
1 | class People: |
此时的obj.__name= 'tom'
,相当于给obj实例添加了一个新的实例变量__name
,而不是对原有私有成员__name
的重新赋值。
此外,有些时候,你会看到以一个下划线开头的成员名,比如_name
,这样的数据成员在外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的标识符时,意思就是,“虽然我可以被外部访问,但是,请把我视为私有成员,不要在外部访问我!”。
还有,在Python中,标识符类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊成员,特殊成员不是私有成员,可以直接访问,要注意区别对待。同时请尽量不要给自定义的成员命名__name__
或__iter__
这样的标识,它们都是Python中具有特殊意义的魔法方法名。
类的成员与下划线总结:
_name、_name_、_name__
:建议性的私有成员,不要在外部访问。
__name、 __name_
:强制的私有成员,但是你依然可以蛮横地在外部危险访问。
__name__
:特殊成员,与私有性质无关,例如__doc__
。
name_、name__
:没有任何特殊性,普通的标识符,但最好不要这么起名。
伪装类方法
- @property装饰器——更多有关装饰器的知识
Python内置的@property
装饰器可以把类的方法伪装成属性调用的方式。也就是本来是Foo.func()
的调用方法,变成Foo.func
的方式。在很多场合下,这是一种非常有用的机制。
1 | class People: |
使用方法:
1 | obj = People("jack", 18) |
将一个方法伪装成为属性后,就不再使用圆括号的调用方式了。而是类似变量的赋值、获取和删除方法了。当然,每个动作内部的代码细节还是需要你自己根据需求去实现的。
那么如何将一个普通的方法转换为一个“伪装”的属性呢?
- 首先,在普通方法的基础上添加
@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 | class People: |
通过语句age = property(get_age, set_age, del_age, "年龄")
将一个方法伪装成为属性。其效果和装饰器的方法是一样的。
property()函数的参数:
- 第一个参数是方法名,调用
实例.属性
时自动执行的方法 - 第二个参数是方法名,调用
实例.属性 = XXX
时自动执行的方法 - 第三个参数是方法名,调用
del 实例.属性
时自动执行的方法 - 第四个参数是字符串,调用
实例.属性.__doc__
时的描述信息。
特殊成员和魔法方法
Python中有大量类似__doc__这种以双下划线开头和结尾的特殊成员及“魔法方法”,它们有着非常重要的地位和作用,也是Python语言独具特色的语法之一!
比如:
1 | __init__ : 构造函数,在生成对象时调用 |
需要注意的是,这些成员里面有些是方法,调用时要加括号,有些是属性,调用时不需要加括号(废话!)。
具体用法请查看python教程
异常处理
在程序运行过程中,总会遇到各种各样的问题和错误。有些错误是我们编写代码时自己造成的,比如语法错误、调用错误,甚至逻辑错误。还有一些错误,则是不可预料的错误,但是完全有可能发生的,比如文件不存在、磁盘空间不足、网络堵塞、系统错误等等。这些导致程序在运行过程中出现异常中断和退出的错误,我们统称为异常。大多数的异常都不会被程序处理,而是以错误信息的形式展现出来。
异常有很多种类型,Python内置了几十种常见的异常,就在builtins模块内,无需特别导入,直接就可使用。需要注意的是,所有的异常都是异常类,首字母是大写的!
在发生异常的时候,Python会打印出异常信息,信息的前面部分显示了异常发生的上下文环境,并以调用栈的形式显示具体信息。异常类型作为信息的一部分也会被打印出来,例如ZeroDivisionError,NameError 和 TypeError。
1 | 10 * (1/0) |
为了保证程序的正常运行,提高程序健壮性和可用性。我们应当尽量考虑全面,将可能出现的异常进行处理,而不是留在那里,任由其发生。
Python内置了一套try…except…finally(else)…的异常处理机制,来帮助我们进行异常处理。其基本语法是:
1 | try: |
注:在Python3中,原Python2的except Exception , ex
的别名方法已经不能使用,逗号被认为是两种异常的分隔符,而不是取别名。
Python的异常机制具有嵌套处理的能力,比如下面的函数f3()调用f2(),f2()调用f1(),虽然是在f1()出错了,但只需要在f3()进行异常捕获,不需要每一层都捕获异常。
1 | def f1(): |
仅仅需要在调用f3()函数的时候捕获异常:
1 | def f1(): |
try…except…
语句处理异常的工作机制如下:
- 首先,执行try子句(在关键字try和关键字except之间的语句)
- 如果没有异常发生,忽略except子句,try子句执行后结束。
- 如果在执行try子句的过程中发生了异常,那么try子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的except子句将被执行。
1 | try: |
- 如果程序发生的异常不在你的捕获列表中,那么依然会抛出别的异常。
1 | # 未捕获到异常,程序直接报错 |
- 如果一个异常没有与任何的except匹配,那么这个异常将会传递给上层的try中。也就是前面说的嵌套处理能力。直到程序最顶端如果还没有被捕获,那么将弹出异常。
1 | try: |
- 可能包含多个except子句,分别来处理不同的特定的异常。但最多只有一个分支会被执行。所以except子句有排序先后问题,进了一条巷子就不会进别的巷子。
1 | try: |
- 处理程序将只针对对应的try子句中的异常进行处理,不会处理其他try语句中的异常。
- 一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组。
1 | except (RuntimeError, TypeError, NameError): |
- 最后一个except子句可以忽略异常的名称,它将被当作通配符使用,也就是说匹配所有异常。
1 | except: |
看一个综合性的例子:
1 | import sys |
通用异常:Exception
在Python的异常中,有一个通用异常:Exception
,它可以捕获任意异常。
1 | s1 = 'hello' |
那么既然有这个什么都能管的异常,其他诸如OSError、ValueError的异常是不是就可以不需要了?当然不是!很多时候程序只会弹出那么几个异常,没有必要针对所有的异常进行捕获,那样的效率会很低。另外,根据不同的异常种类,制定不同的处理措施,用于准确判断错误类型,存储错误日志,都是非常有必要甚至强制的。
finally和else子句
try except
语法还有一个可选的else子句,如果使用这个子句,那么必须放在所有的except子句之后。这个子句将在try子句没有发生任何异常的时候执行。
1 | for arg in sys.argv[1:]: |
同样的,还有一个可选的finally子句。无论try执行情况和except异常触发情况,finally子句都会被执行!
1 | try: |
那么,当else和finally同时存在时:
1 | try: |
运行结果:
1 | else |
如果有异常发生:
1 | try: |
运行结果:
1 | finally |
主动抛出异常:raise
很多时候,我们需要主动抛出一个异常。Python内置了一个关键字raise
,可以主动触发异常。
1 | raise |
raise唯一的一个参数指定了要被抛出的异常的实例,如果什么参数都不给,那么会默认抛出当前异常。
可能有同学会问,为什么要自己主动抛出异常?不嫌多事么?因为有的时候,你需要记录错误信息,然后将异常继续往上层传递,让上层去处理异常,如下:
1 | try: |
有时候,你需要主动弹出异常,作为警告或特殊处理:
1 | sex = int(input("Please input a number: ")) |
更多的时候,你需要使用raise抛出你自定义的异常,如下面所述!
自定义异常
Python内置了很多的异常类,并且这些类都是从BaseException类派生的。
下面是一些常见异常类,请把它们记下来!这样你在见到大多数异常的时候都能快速准确的判断异常类型。
异常名 | 解释 |
---|---|
AttributeError | 试图访问一个对象没有的属性 |
IOError | 输入/输出异常 |
ImportError | 无法引入模块或包;多是路径问题或名称错误 |
IndentationError | 缩进错误 |
IndexError | 下标索引错误 |
KeyError | 试图访问不存在的键 |
KeyboardInterrupt | Ctrl+C被按下,键盘终止输入 |
NameError | 使用未定义的变量 |
SyntaxError | 语法错误 |
TypeError | 传入对象的类型与要求的不符合 |
UnboundLocalError | 试图访问一个还未被设置的局部变量 |
ValueError | 传入一个调用者不期望的值,即使值的类型是正确的 |
OSError | 操作系统执行错误 |
大多数情况下,上面的内置异常已经够用了,但是有时候你还是需要自定义一些异常。自定义异常应该继承Exception
类,直接继承或者间接继承都可以,例如:
1 | class MyExcept(Exception): |
异常的名字都以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
,你想导入到哪个级别,取决于你的需要,可以灵活调整,没有固定的规则。
import xx.xx
这会将对象(这里的对象指的是包、模块、类或者函数,下同)中的所有内容导入。如果该对象是个模块,那么调用对象内的类、函数或变量时,需要以module.xxx
的方式。
比如,被导入的模块Module_a
:
1 | # Module_a.py |
在Main.py
中导入Module_a
:
1 | # Main.py |
From xx.xx import xx.xx
从某个对象内导入某个指定的部分到当前命名空间中,不会将整个对象导入。这种方式可以节省写长串导入路径的代码,但要小心名字冲突。
在Main.py
中导入Module_a
:
1 | # Main.py |
from xx.xx import xx as rename
为了避免命名冲突,在导入的时候,可以给导入的对象重命名。
1 | # Main.py |
from xx.xx import *
将对象内的所有内容全部导入。非常容易发生命名冲突,请慎用!
1 | # Main.py |
执行结果:this is main module!
模块搜索路径
不管你在程序中执行了多少次import,一个模块只会被导入一次。这样可以防止一遍又一遍地导入模块,节省内存和计算资源。那么,当使用import语句的时候,Python解释器是怎样找到对应的文件的呢?
Python根据sys.path
的设置,按顺序搜索模块。
1 | import sys |
当然,这个设置是可以修改的,就像windows系统环境变量中的path一样,可以自定义。 通过sys.path.append('路径')
的方法为sys.path
路径列表添加你想要的路径。
1 | import sys |
默认情况下,模块的搜索顺序是这样的:
- 当前执行脚本所在目录
- Python的安装目录
- Python安装目录里的site-packages目录
其实就是“自定义”——>“内置”——>“第三方”模块的查找顺序。任何一步查找到了,就会忽略后面的路径,所以模块的放置位置是有区别的。
总结:千万不要和内置模块或常用第三方模块同名!否则,哪怕你认为自己掌控着一切,也有可能会发生各种意想不到的结果!
只有包含__init__.py
文件的目录才会被认作是一个包!__init__.py
本身就是一个模块,但是要注意,它的模块名是它所在的包名而不是__init__
。
一个使用第三方模块——openpyxl操作excel的小例子
1 | from openpyxl import Workbook, load_workbook |
常用标准库
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开发与网络爬虫等等,碍于篇幅,今后填坑