Python学习笔记(三)

第六章 面向对象程序设计

1. 定义与使用

1
2
3
4
5
6
class Car:      # 定义
def info(self):
print('Car.')

car = Car() # 实例化
car.info()

2.1 self 参数

类的所有实例方法都必须至少有一个self参数(未必命名为self),且为第一个形参,代表着对象本身。在类的实例方法中访问实例属性需要以self为前缀,在外部通过对象名调用对象方法时不需要传递此参数。

2.2 类成员与实例成员

这里的成员也可以称为属性。

类属性:在类中所有方法之外定义的数据成员。它属于类型,可通过类名或对象名访问。

实例属性:一般在构造函数__init__()中定义,且定义时以self作为前缀。它属于实例(对象),只能通过对象名访问。

类的方法中可以调用类本身的其他方法,也可以动态地为类和对象增加成员。这是Python的一大特点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Car:
price = 100000 # 类属性
def __init__(self, c):
self.color = c # 实例属性


car_1 = Car('Red')
car_2 = Car('Black')
print(car_1.color, Car.price)
print(car_2.color, Car.price)
Car.price = 200000 # 修改类属性
Car.name = 'Audi' # 增加类属性
car_1.color = 'White' # 修改实例属性
print(car_1.color, Car.price, Car.name)
print(car_2.color, Car.price, Car.name)

def set_speed(self, s):
self.speed = s

car_1.set_speed = types.MethodType(set_speed, Car) # 动态为对象car_1增加成员方法
car_1.set_speed(60) # car_2则没有这个方法
print(car_1.speed)

Python的函数与方法有区别,方法一般指与特定实例绑定的函数,通过对象调用方法时,对象本身被作为一个参数传递过去。普通函数不具此特点。

2.3 私有与共有成员

用两个下划线“__”表示私有属性,推荐只从对象的公有成员方法进行访问。公有属性可在类的内外部进行访问。

以下划线开头:

  • _xxx,单下划线是保护成员,不能用 from … import xxx 访问, 只有类对象和子类对象可以访问
  • __xxx__,系统定义的特殊成员
  • __xxx,私有成员

在IDLE等环境中,“_”表示解释器中最后一次显示的内容或最近一次结果。

2. 方法

私有方法以__开头,只能在属于对象的方法中以self.xxx()调用。公有方法通过对象名直接调用。

私有、公有方法用类名调用时,需要显示为该方法的self参数传递一个对象名,用于明确访问哪个对象的数据成员。

类方法和静态方法可以通过类名和对象名调用,不能访问属于对象的成员,只能访问属于类的成员。一般将cls作为类方法的第一个参数名称。(两者分别使用@classmethod, @staticmethod 来修饰)

3. 属性

使用@propertyproperty() 函数来声明一个属性。下面的代码中,属性可读、可修改、可删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Test:
def __init__ (self, value):
self.__value = value
def __get(self):
return self.__value
def __set(self, v):
self.__value = v
def __del(self):
del self.__value

value = property(__get, __set,__del)

def show(self):
print(self.__value)

t = Test(3)
t.value # 允许读
t.value = 5 # 允许修改对象值
t.show() # 可知属性值被修改

删去三个函数中的某个,属性的读写性质就相应变化了。

4. 特殊方法

构造函数__init__(): 为数据成员设置初值或进行其他必要的初始化工作,创建对象时会被自动调用和执行。可通过为构造函数定义默认值参数来实现类似于其他语言中构造函数重载的目的。没有编写则提供默认。

析构函数__del__():释放对象占用的资源,删除和收回对象空间时自动调用和执行。没有编写则python提供一个默认的析构函数。

方 法 说 明
__new__() 类的静态方法,用于确定是否要创建对象
__add__()、__radd__() 左+、右+
__sub__() -
__mul__() *
__truediv__() /
__floordiv__() //
__mod__() %
__pow__() **
__eq__()、 __ne__()、__lt__()、 __le__()、__gt__()、 __ge__() ==、 !=、<、 <=、>、 >=
__lshift__()、__rshift__() <<、>>
__and__()、__or__()、__invert__()、__xor__() &、|、~、^
__iadd__()、__isub__() +=、-=,很多其他运算符也有与之对应的复合赋值运算符
__pos__() 一元运算符+,正号
__neg__() 一元运算符-,负号
__contains__() 与成员测试运算符in对应

5. 继承机制

派生类可以继承父类的公有成员,但不能继承其私有成员。在派生类中调用基类的方法可以使用super()或通过基类名.方法名()的方式实现。详情见以下代码:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Person(object):  # 必须以object为基类
def __init__(self, name='', age=20, sex='male'):
self.set_name(name)
self.set_age(age)
self.set_sex(sex)

def set_name(self, name):
if not isinstance(name, str):
print('名字类型错误')
return
self.__name = name

def set_age(self, age):
if not isinstance(age, int):
print('年龄类型错误')
return
self.__age = age

def set_sex(self, sex):
if sex != 'male' and sex != 'female':
print('性别错误')
return
self.__sex = sex

def show(self):
print('Name:', self.__name)
print('Age:', self.__age)
print('Sex:', self.__sex)


class Teacher(Person): # 由Person派生
def __init__(self, name='', age=20, sex='male', department='CS'):
super(Teacher, self).__init__(name, age, sex)
# or Person.__init__(self,name, age, sex)
self.__department = None
self.set_department(department)

def set_department(self, department):
if not isinstance(department, str):
print('部门类型错误')
return
self.__department = department

def show(self):
super(Teacher, self).show()
print('Department:', self.__department)


if __name__ == '__main__':
zhang = Person('zhang', 18, 'male')
zhang.show()
li = Teacher('li', 20, 'male', 'English')
li.show()
li.set_age(88)
li.show()

派生类没有定义构造方法时,创建派生类对象会调用父类的构造方法。父类构造方法无法调用派生类中重定义的私有方法,可以调用在派生类中重写的公有方法。

Python支持多继承,如果多个父类中有相同的方法名,而在子类中使用该方法时没有指定父类名,则解释器将从左向右按顺序搜索。例如 Class Son(f1, f2) 则先考虑调用f1类中的方法。

第七章 文件操作

1. 文件对象

open()函数可以以指定模式打开指定文件并创建文件对象,如:

1
fileobj = open(文件名[, 打开方式[, 缓冲区]])

其中,文件名默认寻找当前目录,否则要给出完整路径;打开方式各个模式见下表:

模 式 说 明
r 读模式(常用rt,读取文本时会自动把\r\n转换成\n)
w 写模式(不存在就写,存在就覆盖)
a 追加模式
b 二进制模式(可与其它模式组合使用)
+ 读写模式(可与其它模式组合使用)
w+ 读写(存在则覆盖)
a+ 读写(存在则追加)

缓冲区有两种:数值0表示不缓存,数值1表示缓存(默认),大于1表示缓冲区大小

文件操作结束,一定要关闭文件以保存修改:fileobj.close()

文件对象属性

属性 说明
closed 判断文件是否关闭是则返回True
mode 返回文件的打开模式
name 返回文件的名称

文件对象常用方法

属性 说明
flush() 缓冲区写入文件,不关闭文件
close() 缓冲区写入文件,同时关闭文件,释放文件对象
read([size]) 从文件中读取size个字符的内容作为结果返回,缺省一次读取所有的内容,支持中英文等不同字符
readline() 从文本文件中读取一行内容 作为结果返回 (含换行符)
readlines() 把文本文件中的每行文本作为一个字符串存入列表中,返回该列表 (含换行符)
seek(offset[,whence]) 把文件指针移动到新的位置(指定字节的位置),offset表示相对于whence的位置。whence为0表示从头开始,为1表示从当前位置开始,2表示从文件尾开始计算
tell() 返回指针的当前位置
turncate([size]) 未指定size:删除从当前指针到文件末尾的内容,指定size:保留文件前size个字符,其余的删除
write(s) 把字符串s的内容写入文件
writelines(s) 把字符串列表写入文本文件,默认不添加换行符

2. 示例

2.1 上下文管理with

上下文管理器with可以自动管理资源,任何原因跳出with块都会保证文件被正确关闭,并且可以在代码块结束后自动还原进入该代码块时的现场。例如:

1
2
3
4
5
6
7
8
9
f = open('demo.txt', 'a+')
s = '内容\nhaha'
f.write(s)
f.close()

# 等价于推荐写法:
s = '内容\nhaha'
with open('de.txt', 'a+') as f:
f.write(s)

2.2 tell() 与 seek()

文件完成读写操作后,都会自动移动文件指针,在非UTF-8编码下,这两个函数均是针对于字节来说的(区别于read()函数),可能会出现隔断一个完整汉字而尝试读取的情况,此时会报错。建议始终在UTF-8编码下编程。

2.3 二进制文件

二进制文件无法直接读取,其操作主要有pickle(推荐)、struct、json、marshal等模块。常用pickle:

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

names = ['Jim', 'Tony', 'Tommy', 'Jessica', 'Anna']
grades = [92, 66, 70, 88, 96]
dic = dict(zip(names, grades))
f = open('grade.dat', 'wb')
try:
pickle.dump(dic, f) # 写入,第一个参数支持多种类型
except:
print('写入异常')
finally:
f.close()
f = open('grade.dat', 'rb')
print(pickle.load(f)) # 读取

3. 文件级操作

处理文件路径:os.path模块
命令行读取文件内容:fileinput模块
创建临时文件:tempfile模块
表示和处理文件系统路径:pathlib模块

3.1 os与os.path模块

os模块常用文件操作方法

方 法 说 明
access(path, mode) 按照mode指定的权限访问文件(eg. mode=0o777)
open(path, flags, mode, *,dir_fd) 按照mode指定的权限打开文件,默认权限为读写执行
chmod() 改变文件的访问权限
remove(path) 删除指定的文件
rename(src, dst) 重命名文件或目录,可以实现改名或移动
stat(path) 返回文件的所有属性
fstat(path) 返回打开的文件的所有属性
listdir(path) 返回path目录下的文件和目录列表
startfile(filepath[, operation]) 使用关联的应用程序打开指定文件

os.path模块常用文件操作方法

方法 说明
abspath(path) 返回绝对路径
dirname(p) 返回目录的路径
exists(path) 判断文件是否存在
getatime(filename) 返回文件的最后访问时间
getctime(filename) 返回文件的最后创建时间
getmtime(filename) 返回文件的最后修改时间
getsize(filename) 返回文件的大小
isabs(path) 判断path是否为绝对路径
isdir(path) 判断path是否为目录
isfile(path) 判断path是否为文件
join(path, * paths) 连接两个或多个path
split(path) 对路径进行分割,以列表形式返回
splitext(path) 从路径中分割文件的扩展名
splitdrive(path) 从路径中分割驱动器的名称
walk(top, func, arg) 遍历目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
os.listdir('.')   # 当前目录下所有文件和目录的列表

>>> print([fname for fname in os.listdir(os.getcwd()) if os.path.isfile(fname) and fname.endswith('.py')]) # 打印当前目录下以.py结尾的所有文件名列表

# 批量修改.txt为.html
>>> f_list=[fname for fname in os.listdir(os.getcwd()) if os.path.isfile(fname) and fname.endswith('.txt')]
>>> f_list
['1.txt', '2.txt', '3.txt']
>>> for i in f_list:
... newname = i[:-4]+'.html'
... os.rename(i,newname)
... print(i+'更名为:'+newname)
1.txt更名为:1.html
2.txt更名为:2.html
3.txt更名为:3.html

3.2 shutil模块

import shutil之后用dir(shutil)查看文档

copyfile(path1, path2):复制文件;

make_archive(), unpack_archive():压缩与解压锁

shutil.rmtree():删除文件夹

4. 目录操作

os模块常用目录操作方法与成员

方法 说明
mkdir(path[, mode=0777]) 创建目录
makedirs(path1/path2…, mode=511) 创建多级目录
rmdir(path) 删除目录
rmvedirs(path) 删除多级目录
listdir(path) 返回指定目录下的文件和目录信息
getcwd() 返回当前工作目录
get_exec_path() 返回可执行文件的搜索路径
chdir(path) 把path设为当前的工作目录
walk(top, topdown=True, onerror=None) 遍历目录树,该方法返回一个元组,包含3个元素:所有路径名,所有目录列表,文件列表。参数topdown的默认值是”True”,表示首先返回目录树下的文件,然后在遍历目录树的子目录;值为”False”时,则表示先遍历目录树的子目录,返回子目录下的文件,最后返回根目录下的文件
1
2
3
4
5
6
7
8
# walk() 遍历指定目录下的所有文件及子目录
import os

for root, dirs, files in os.walk(".", topdown=False):
for name in files:
print(os.path.join(root, name))
for name in dirs:
print(os.path.join(root, name))

第八章 异常处理

异常处理不应该代替常规的程序检查如if...else...

可以通过继承Python内置的异常类来自定义异常类,通过继承相同基类而细分相关的异常。

1. 结构

1
2
3
4
5
6
7
8
9
10
11
12
try:
do something
except OneException as alias:
deal with it
except (TwoException,ThreeException): # 仅在多种异常的处理办法相同时才这么写
deal with them
except BaseException: # 笼统地捕获各种异常
deal with it
else:
do something else # 若try语句没有出一异常就执行
finally:
do things # 无论如何都会被运行的代码

尽量避免在finally语句中使用return,且该语句中同样也可能抛出异常

主动抛出异常错误的关键词是raise

主动抛出异常错误的关键词是raise,可以自定义抛出内容,如:

1
2
3
4
5
6
7
8
try:
n = int(input())
if n < 0:
raise -1
except ValueError:
print('xxx')
except:
print('n不能小于0')

一般跳出循环都是用 break,但是只能跳出一层循环,可用异常处理的方式一次性跳出多层循环:

1
2
3
4
5
6
7
try:
for i in range(10):
for j in range(10):
if i+j>6:
raise -1
except: # 跳出两层
print(i, j)

sys.exc_info() 返回程序最终引发异常的位置,但不容易定位真正出错的地方

2. 断言

断言的语法:assert expr [,reason]。当 expr 为 True 时,什么都不做;为 False 时,抛出异常。

断言常常结合 try...except...例如:

1
2
3
4
5
>>> try:
... assert 1==2, 'NOT EQUAL!'
... except AssertionError as reason:
... print('{0}:{1}'.format(reason.__class__.__name__,reason))
AssertionError:NOT EQUAL!

3. 单元测试

Python的unittest 模块提供单元测试的各种类和方法,常用 TestCase类。