文章目录
面向对象高级编程
高级特性:多重继承、定制类、元类等概念。
一、slots
__slots__变量:限制该 class 实例能添加的属性。
class Student(object):
__slots__ = ('name', 'age')
s = Student()
s.name = 'Michael'
s.age = 25
s.score = 99
print(s.name)
print(s.age)
print(s.score)
==》AttributeError 的错误
AttributeError: 'Student' object has no attribute 'score'
注意:__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。除非在子类中也定义 __slots__
,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
class GraduateStudent(Student):
pass
g = GraduateStudent()
g.score = 9999
print(g.score) ## 9999
二、@property装饰器
负责把一个方法变成属性调用的。
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
s.score = 60
print(s.score)
s.score = 999
print(s.score)
输出结果
60
Traceback (most recent call last):
File "E:/codes/python/basic/6.py", line 169, in <module>
s.score = 999
File "E:/codes/python/basic/6.py", line 163, in score
raise ValueError('score must between 0 ~ 100!')
ValueError: score must between 0 ~ 100!
把一个 getter 方法变成属性,只需要加上@property 就可以了,此时, @property 本身又创建了另一个装饰器@score.setter,负责把一个 setter 方法变成属性赋值。
三、多重继承——MixIn
MixIn 的目:给一个类增加多个功能。
在设计类的时候,要优先考虑通过多重继承来组合多个 MixIn 的功能,而不是设计多层次的复杂的继承关系。
class Animal(object):
pass
class RunnableMixIn(object):
def run(self):
print('Running...')
class Mammal(Animal):
pass
## Dog为多继承,继承于 Mammal 和 RunnableMixIn
class Dog(Mammal, RunnableMixIn):
pass
四、定制类——__xxx__
Python 的 class 中还有许多这样有特殊用途的函数,可以帮
助我们定制类。
1、__str__()
与 __repr__()
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
print(Student('Michael'))
结果对比:
- 未自定义
__str__()
的结果
<__main__.Student object at 0x000001994F615FD0>
- 自定义
__str__()
的结果
Student object (name: Michael)
在交互的模式下,直接输出 s,打印出来的 <__main__.Student object at 0x109afb310>
也并不好看。
直接显示变量调用的是 __repr__()
而非 __str__()
。
==》
区别:__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。
==》解决办法:再定义一个__repr__()
2、__iter__()
__iter__()
:用于 for…in 循环,该函数返回一个迭代对象。
然后,Python 的 for 循环就不断用该迭代对象的 __next__()
方法获取循环的下一个值,直到遇到 StopIteration 错误时退出循环。
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration
return self.a
for n in Fib():
print(n)
输出结果
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
3、__getitem__()
可以按照下标取出元素 ==》__getitem__()
方法
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
f = Fib()
print(f[0])
print(f[1])
print(f[100])
输出结果
1
1
573147844013817084101
==》切片操作
原因:__getitem__()
传入的参数可能是一个 int,也可能是一个切片对象 slice,所以要做判断。
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n 是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n 是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
f = Fib()
print(f[0:5])
print(f[:10])
输出结果
[1, 1, 2, 3, 5]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
分析:没有对 step 参数、负数等做处理。
==》将对象看出 dict
==》__getitem__()
的参数可能作 key 的 object。其对应的 __setitem__()
方法:将对象看作 list 或 dict 来对集合赋值;__delitem__()
方法:删除某个元素。
4、__getattr__()
为了避免由于类的方法或属性不在造成的 AttributeError 错误,使用 __getattr__()
,动态返回一个属性。
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr == 'score':
return 99
elif attr == 'age':
return lambda:25
# 若均不匹配,则抛出 AttributeError 的错误
raise AttributeError('\'Student\'object has no attribute \'%s\'' % attr)
==》当调用不存在的属性时,比如 score, Python 解释器会试图调用__getattr__(self, ‘score’)来尝试获得属性,从而可以返回score的值99。
常约定 class 只响应特定的几个属性,否则抛出 AttributeError 的错误。
5、__call__()
对实例进行直接调用。
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self, *args, **kwargs):
print('My name is %s' % self.name)
s = Student('Michael')
print(s())
# My name is Michael
判断一个对象是否能被调用,若可被调用,则该对象是一个 Callable 对象——callable()
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False
五、枚举类——Enum类
from enum import Enum
# Month类型的枚举类 定义
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
print(Month.Jun)
# 枚举所有成员,value属性为int常量,默认从1开始计数
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
精确控制枚举类型 ==》Enum的派生类
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
## 枚举类型的访问
day1 = Weekday.Mon
print(day1)
print(Weekday.Tue) # 用成员名称引用枚举常量
print(Weekday['Tue'])
print(Weekday.Tue.value)
print(day1 == Weekday.Mon)
print(day1 == Weekday.Tue)
print(Weekday(1)) # 根据 value 的值获得枚举常量
print(day1 == Weekday(1))
# print(Weekday(7))
for name, member in Weekday.__members__.items():
print(name, '=>', member)
既可以用成员名称引用枚举常量,又可以直接根据 value 的值获得枚举常量。
六、使用元类
1、type()
动态语言的函数和类的定义在运行时动态创建的。
type()函数既可以返回一个对象的类型,又可以创建出新的类型。
。。。。。
七、错误、调试和测试
1、错误处理
处理机制:try…except…finally…(可以多个except)
如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即 except 语句块,执行完 except 后,如果有 finally 语句块,则执行 finally 语句块,至此,执行完毕。
若没有错误,执行完try部分,不执行except部分而执行finally部分。
try:
print('try...')
r = 10 / int('a')
print('result: ', r)
except ValueError as e:
print('ValueError: ', e)
except ZeroDivisionError as e:
print('ZeroDivisionError: ', e)
finally:
print('finally...')
print('END')
输出结果
try...
ValueError: invalid literal for int() with base 10: 'a'
finally...
END
Python 所有的错误都是从 BaseException 类派生的,常见的错误类型和继承关系看这里:
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
2、调用堆栈
解读错误信息是定位错误的关键。我们从上往下可以看到整个错误的调用函数链。
3、记录错误
import logging
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
logging.exception(e)
main()
print('END')
4、抛出错误
5、调试
- print()可能错误的变量值
- 断言(assert):assert 表达式, ‘输出语句’==》表达式为真,继续执行,否则抛出 AssertionError错误并输出后面的输出语句。
- python -0 文件.py ==》关闭assert
- logging输出到文件;
- 调试器 pdb,可以单步调试
6、单元测试
用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
==》确保一个程序模块的行为符合我们设计的测试用例。
测试用例:
- 输入正数;
- 输入负数;
- 输入0;
- 输入非数值类型