Python学习(三)

学习网址:廖雪峰的Python教程

面型对象高级编程

数据封装,继承和多态只是面向对象编程的3个基础概念

__slots__

定义一个Class后,创建一个class实例后,可以给实例绑定任何属性和方法,动态语言的灵活性,

1
2
class Student(object):
pass

创建实例,绑定一个属性值:

1
2
3
>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
>>> print(s.name)Michael

还可以为其绑定一个方法,

但是,这个方法只对当前实例有效,对其他实例没有效果。

使用 __slots__ 可以为类限定以后添加的属性,

1
2
class Student(object):
slots = ('name', 'age') # 用tuple定义允许绑定的属性名称

这样,除了name和age属性之外的属性,都不可以添加了,会报错

1
2
3
4
5
6
7
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性
'score'Traceback (most recent call last):
File "<stdin>", line 1, in <module>AttributeError:
'Student' object has no attribute 'score'

使用 __slots__ 仅对当前实例有效,对其子类则没有效果,除非在子类中定义 __slots__ 方法

@property

@property就是可以让实例的方法当做属性来用,例如:给Screen对象加上width和height属性,以及一个只读属性的resolution,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Screen(object):
@property #设置getter方法
def width(self):
return self._width
@width.setter #设置setter方法
def width(self,value):
self._width = value
@property #设置getter方法
def height(self):
return self._height
@height.setter #设置setter方法
def height(self,value):
self._height = value
@property #没有setter方法,只读属性,通过计算出来的
def resolution(self):
return self.width*self.height

测试结果:

1
2
3
4
5
s = Screen()
s.width = 1024 #这里就相当于s.set_width
s.height = 768 #这里就相当于s.set_height
print(s.resolution)
786432
多重继承

Python允许使用多重继承,例如:狗是一种会跑的,食肉的,哺乳型动物,就可以继承自定义的

会跑的动物(Runnable),食肉的(Carnivorous),哺乳型(Mammal),动物(Animal)…

1
2
class Dog(Mammal,Animal,Carnivorous):
pass

这种模式叫做MixIn,目的是为了给一个类增加多个功能,在设计类的时候,优先考虑通过多重继承来组合多个MixIn的功能,而不是设计复杂层次的继承关系,

Enum枚举类
1
2
3
4
5
6
7
8
9
10
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

@unique用来检查有没有重复值

访问枚举类型

1
2
3
4
5
6
7
8
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(Weekday(1))
Weekday.Mon

可以根据枚举名称引用常量,也可以用value的值获取枚举常量

元类(metalclass)

动态语言在函数和类的定义过程中,不是编译时定义的 ,而是运行时动态创建的,

当我们需要创建一个实例时,首先要先定义一个类,然后在创建类的实例,所以顺序就是 –>创建类–>创建实例,

但是创建类的时候呢?这是就是先创建metalclass,在创建类,在创建实例,顺序就是–>创建metalclass–>创建类–>创建类的实例

可以理解为,类就是metalclass的实例,

①它可以拦截类的创建,

②对类进行修改

③返回修改后的类

详细解释

错误,调试,测试
错误处理

try…except…finally…错误处理机制

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

当try执行代码块出错时候,后续代码将不执行,而是执行except语句中的代码,提示错误的出现,如果finally语句中有代码,再执行其语句中的代码,

1
2
3
4
try...
result: 5
finally...
END

也可以添加多个except语句块,捕获不同错误,还可以加入else语句块,

1
2
3
4
5
6
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('no error!')

错误类型和继承关系

try…except..还可以跨越多层级调用,可以减少try..except..代码块的书写

1
2
3
4
5
6
7
8
9
10
11
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')

如果错误没有被捕获,会一直向上抛,直到Python解释器捕获,打印错误信息,程序退出,

出现错误不可怕,不知道错误出现在哪里才可怕,要找到错误出现的地方,出错时,会分析错误信息并定位错误发生的代码位置才是最关键的。

通过logging可以方便的记录错误,

1
2
except Exception as e:
logging.exception(e)

抛出错误raise

1
2
3
4
5
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n

只有在必要的时候才使用自定义的错误类,如果可以用Python内置的错误类,尽量使用内置的错误类,

调试

程序调试的方法有:

print()直接打印处错误,还需要手动删除

断言assert,

1
2
3
4
5
6
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')

表达式 n!=0应该是true,否则,后面的代码会出错,如果断言失败,会抛出AssertionError,

启用Python解释器时候,可以启用-0来关闭断言

1
$ python3 -0 err.py

logging,它不会抛出错误,可以输出到文件,可以指定记录信息的级别,debug,info,warning,error等,

pdb,python自带的调试器,

1
2
3
4
# err.py
s = '0'
n = int(s)
print(10 / n)

启动调试器:

1
2
3
$ python3 -m pdb err.py
> /Users/michael/Github/learn-python3/samples/debug/err.py(2)<module>()
-> s = '0'

输入命令n可以单步执行代码,

1
2
3
4
5
6
(Pdb) n
> /Users/michael/Github/learn-python3/samples/debug/err.py(3)<module>()
-> n = int(s)
(Pdb) n
> /Users/michael/Github/learn-python3/samples/debug/err.py(4)<module>()
-> print(10 / n)

输入 p 变量名 可以查看变量

1
2
3
4
(Pdb) p s
'0'
(Pdb) p n
0

输入q 退出调试

pdb.set_trace(),这个方法也是pdb调试的,但不需要单步执行,

1
2
3
4
5
6
# err.py
import pdb
s = '0'
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停
print(10 / n)

只需要在可能出现错误的地方,加上pdb.set_trace(),代码执行到此时,会自动进入pdb调试模式,可以用p查看变量,用c继续执行

1
2
3
4
5
6
7
8
9
10
$ python3 err.py
> /Users/michael/Github/learn-python3/samples/debug/err.py(7)<module>()
-> print(10 / n)
(Pdb) p n
0
(Pdb) c
Traceback (most recent call last):
File "err.py", line 7, in <module>
print(10 / n)
ZeroDivisionError: division by zero

IDE调试,比较好的Python IDE 有PyCharm

单元测试

单元测试时对一个类,一个函数或者一个模块正确性检测的测试工作,就是确保一个程序模块的行为符合我们设计的测试用例,在将来进行修改时候,极大可能保证此模块仍然是正确的

文档测试

Python内置的 文档测试(doctest) 可以抽取文档中的代码直接运行

doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def fact(n):
'''
>>> fact(1)
1
>>> fact(2)
2
>>> fact(-1)
Traceback (most recent call last):
ValueError
'''
if n < 1:
raise ValueError()
if n == 1:
return 1
return n * fact(n - 1)
if__name__ == '__main__':
import doctest
doctest.testmod()

真诚地希望能帮到你!