学习笔记

0209-对象高级编程-错误测试

Posted on 2022-02-09,9 min read
封面图

使用__slots_

限制实例可添加的属性, 但对当前类中的__slots__对子类无作用:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
>>> 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'
#对子类不起作用
>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999 #不报错

使用@property

@property默认只赋予getter属性, 如需setter需要用到属性名.setter:
@property给一个Screen对象加上widthheight属性,以及一个只读属性resolution

class Screen(object):
    @property
    def width(self):
        return self._width
    @property
    def height(self):
        return self._height
    @property
    def resolution(self):
        return self._width*self._height

    @height.setter
    def height(self, x):
        if isinstance(x, int):
            self._height = x
        else:
            raise ValueError('check your value')
    @width.setter
    def width(self, x):
        if isinstance(x, int):
            self._width= x
        else:
            raise ValueError('check your value')
# 测试:
s = Screen()
s.width = 1024
s.height = 768
print('resolution =', s.resolution)
if s.resolution == 786432:
    print('测试通过!')
else:
    print('测试失败!')

多重继承

或者叫多继承,与java的多重继承区分。
实例方法的调用顺序按照拓扑序列的展开顺序。
类的继承关系通常主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn

定制类

__str__
通常__str__()__repr__()代码都是一样的,所以,有个偷懒的写法:

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__

__iter__
如果一个类想被用于for ... in循环, 就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,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
...
46368
75025

__getitem__

__getattr__
只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。

__call__

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.

__call__()还可以定义参数;
可以把对象看成函数,把函数看成对象,因为两者之间本没有根本的区别;
callable()函数,判断一个对象是否是“可调用”对象。

枚举类

from enum import Enum:

from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

Studentgender属性改造为枚举类型,可以避免使用字符串:

# -*- coding: utf-8 -*-
from enum import Enum, unique
class Gender(Enum):
    Male = 0
    Female = 1
class Student(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = Gender(gender)
# 测试:
bart = Student('Bart', Gender.Male)
if bart.gender == Gender.Male:
    print('测试通过!')
else:
    print('测试失败!')

元类——暂时跳过

网站说以后用不到,以后在再学吧

错误调试与测试

错误处理

try:
    print('try...')
    r = 10 / int('2')
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
else:
    print('no error!')
finally:
    print('finally...')
print('END')

抛出错误

# err_raise.py
class FooError(ValueError):
    pass

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

foo('0')

调试

assert
assert的意思是,表达式n != 0应该是True,否则抛出AssertionError
logging

import logging
logging.basicConfig(level=logging.INFO) #指定记录的级别

s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
#输出
$ python err.py
INFO:root:n = 0
Traceback (most recent call last):
  File "err.py", line 8, in <module>
    print(10 / n)
ZeroDivisionError: division by zero

允许你指定记录信息的级别,有debuginfowarningerror等几个级别,当我们指定level=INFO时,logging.debug不起作用; 指定level=WARNING后,debuginfo不起作用.
pdb
以参数-m pdb启动后,pdb定位到下一步要执行的代码-> s = '0'
输入命令l: (Pdb) l 来查看代码;
n: 可以单步执行代码;
输入命令 p 变量名来查看变量;
q结束调试

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

pdb.set_trace()执行到此行代码进入pdb调试模式.

单元测试

读取实例属性: 实例名.属性 记住.的作用是获取属性;
编写一个Dict类,这个类的行为和dict一致,但是可以通过属性来访问,用起来就像下面这样:

>>> d = Dict(a=1, b=2)
>>> d['a']
1
>>> d.a #通过属性访问
1

mydict.py代码如下:

class Dict(dict):

    def __init__(self, **kw):
        super().__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

测试单元: unittest模块,编写mydict_test.py

import unittest

from mydict import Dict

class TestDict(unittest.TestCase):

    def test_init(self):
        d = Dict(a=1, b='test')
        self.assertEqual(d.a, 1)
        self.assertEqual(d.b, 'test')
        self.assertTrue(isinstance(d, dict))

    def test_key(self):
        d = Dict()
        d['key'] = 'value'
        self.assertEqual(d.key, 'value')

    def test_attr(self):
        d = Dict()
        d.key = 'value'
        self.assertTrue('key' in d) #注意
        self.assertEqual(d['key'], 'value') #注意

    def test_keyerror(self):
        d = Dict()
        with self.assertRaises(KeyError): #注意
            value = d['empty']

    def test_attrerror(self):
        d = Dict()
        with self.assertRaises(AttributeError):
            value = d.empty

运行单元测试:

#在mydict_test.py的最后加上两行代码:
if __name__ == '__main__':
    unittest.main()
#然后当做正常的python脚本运行:
$ python mydict_test.py
#推荐方法:
#通过参数-m unittest直接运行单元测试:
$ python -m unittest mydict_test
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

文档测试

上节课的例子用文档测试:

# mydict2.py
class Dict(dict):
    '''
    Simple dict but also support access as x.y style.

    >>> d1 = Dict()
    >>> d1['x'] = 100
    >>> d1.x
    100
    >>> d1.y = 200
    >>> d1['y']
    200
    >>> d2 = Dict(a=1, b=2, c='3')
    >>> d2.c
    '3'
    >>> d2['empty']
    Traceback (most recent call last):
        ...
    KeyError: 'empty'
    >>> d2.empty
    Traceback (most recent call last):
        ...
    AttributeError: 'Dict' object has no attribute 'empty'
    '''
    def __init__(self, **kw):
        super(Dict, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

if __name__=='__main__':  #确保只在测试环境运行文档测试, 在引用模块不运行.
    import doctest
    doctest.testmod()

下一篇: 0208-函数-对象编程→

loading...