初学Python 10 Python中的定制类、错误与调试

__slots__ 变量。
如果想要限制实例的属性,可以在定义类时加上这个变量,这样就限制了class能添加的属性。

>>> class stu(object):
    __slots__=('name','age')
>>> chen=stu()
>>> chen.age=11
>>> chen.age
11
>>> chen.name='chenyu'
>>> chen.name
'chenyu'
>>> chen.score=111
Traceback (most recent call last):
  File "<pyshell#83>", line 1, in <module>
    chen.score=111
AttributeError: 'stu' object has no attribute 'score'

注意,__slots__ 对属性的限制,仅对当前类的直接实例起作用,对该类的子类时不起作用的,如果要给子类也加上限制,就要在定义子类时另加__slots__,这样子类的限制就是该子类的限制加上其父类的限制。

__str__ 类和__repr__
__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'))
Student object (name: Michael)

__iter__
通过__iter__() 方法,可以把一个类像list、tuple那样运用于for循环,该方法返回一个迭代对象,而后Python的for循环就会不断调用该迭代对象的__next__() 方法获取循环的下一个值,直到遇到StopIteration() 时退出循环。

#打印斐波那契额数列
>>> class fib(object):
    def __init__(self):
        self.a,self.b=0,1
    def __iter__(self):
        return self
    def __next__(self):
        self.a,self.b=self.b,self.a+self.b
        if(self.a>10000):
            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

__getitem__

__call__
定义了这个方法以后,就可以直接对实例进行调用。

>>> class student(object):
    def __init__(self,name):
        self.name=name
    def __call__(self):
        print('my name is %s' % self.name)
>>> chen=student('chenyuting')
>>> chen
<__main__.student object at 0x05A22AB0>
>>> chen()
my name is chenyuting

如果把对象看成函数,而函数本身可以在运行期间动态创建出来,又因为类的实例都是在运行期间创建出来的,这就模糊了对象和函数的边界。
能被调用的对象是一个Callable 对象

>>> callable(chen)
True
>>> callable(student)
True
>>> callable([1,2,3])
False
>>> callable(abs)
True

枚举类Enum
定义枚举类1.导入enum模块,2.用class关键字,继承自Enum

>>> from enum import Enum
>>> class color (Enum):
    red=1
    green=2
    blue=3
>>> color(1)
<color.red: 1>
>>> color.red
<color.red: 1>

枚举类中的每一项,是成员name——值value的对应。成员名不允许重复,重复就会出错。而且不同成员如果有相同的值,第二个出现的名称会被视为别名,这在枚举时只会枚举第一个出现的名称,跳过之后的别名。如果要把别名成员也遍历出来,要用枚举的一个特殊属性__members__

from enum import Enum
class Color(Enum):
    red = 1
    orange = 2
    yellow = 3
    green = 4
    red_alias = 1
for color in Color.__members__.items():
    print(color)

如果要避免定义了相同值的成员,可以使用先导入unique 模块,然后使用装饰器@unique

from enum import Enum, unique
@unique
class Color(Enum):
    red = 1
    red_alias = 1

这样如果定义了值相同的成员,就会提示错误。ValueError:balabala
枚举类中的成员可以通过成员名、成员值、成员、迭代器来访问和遍历。
枚举类中的成员也可以进行比较,但是不能够比较大小。

>>> color.red is color(1)
True
>>> color.green ==color.blue
False
>>> color.red<color.blue
Traceback (most recent call last):
  File "<pyshell#31>", line 1, in <module>
    color.red<color.blue
TypeError: '<' not supported between instances of 'color' and 'color'

type()可以查看一个变量的类型,也可以用来创建class。用type()创建的类和用class直接创建的类是一样的,因为Python的解释器遇到class,也是调用type()来创建的class。
使用方法:
type()函数要依次传入3个参数:1.class的名称 2.继承的父类的集合,用tuple()传入,注意tuple的单元素写法 3.class的方法的名称与函数的绑定。

>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.

metaclass 元类,可以控制类的创建行为。
通常的做法是:定义类——创建实例
而用这个方法就可以:定义metaclass——创建类
所以可以把类看作是metaclass的“实例”。
这是Python中比较难理解、难使用的“魔术代码”,

try except finally 错误处理机制
当认为有些代码可能会出错,就用try来运行这段代码,如果出错,就会跳转至except处执行,执行完后,如果还有finally语句块,就执行。
不管有没有发生错误,finally是可选的,一旦有,它必然会执行。而try和except只会执行其中的一个。

>>> try:
    print('try...')
    r = 10 / 0
    print('result:', r)
except ZeroDivisionError as e:
    print('except:', e)
finally:
    print('finally...')
try...
except: division by zero
finally...

通常错误类型会有多种,所以except语句块可以有多个。try except finally 的逻辑就类似于if elif else 可以在最后一个except后面加上一个else语句,如果一个错误都没有,就会执行该语句。

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...')

错误类型是由except捕获的,它的类型就是写上去的ValueErrorZeroDivisionError ,把这个错误复制给e,然后输出e。

Python的错误也是class,所有的错误类型都继承自BaseExeption ,所以except不但捕获该类型的错误,而且把它的子类都捕获了。

try:
    foo()
except ValueError as e:
    print('ValueError')
except UnicodeError as e:
    print('UnicodeError')

因为第二个except的错误是第一个except的错误的子类,所以不管有没有这个错误,第二个except都不会执行。因为错误的时候,会直接在第一个except中被捕获。
常见的错误类型和继承关系
使用try except 捕获错误的好处是,可以跨越多层次捕获,所以如果有函数嵌套,多级调用,不需要在每一个可能出错的地方都写一个try except,只要在合适的层次写上,就行了。
比如main函数调用了func函数,在func中发生了错误,只要在main中写了try except,就能捕获到错误。

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

def foo(s):
    return 10 / int(s)
def bar(s):
    return foo(s) * 2
def main():
    bar('0')
main()
Traceback (most recent call last):
  File "err.py", line 11, in <module>
    main()
  File "err.py", line 9, in main
    bar('0')
  File "err.py", line 6, in bar
    return foo(s) * 2
  File "err.py", line 3, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero

这就是错误的追踪信息,即错的的调用栈信息。

使用logging 模块可以在捕获到错误后记录到错误日志里,并且程序可以继续执行。

因为错误是class,所以捕获的一个错误也就是该class的一个实例。
可以用raise 抛出错误。既可以使用Python内置的已有的错误类型,也可以我们自己定义错误类型,尽量使用Python内置的,只在必要的时候才自己定义错误类型。
可以在except中使用raise来抛出另一种错误,但绝不能是另一种毫不相干的错误,这只能是相关的,或更加详细的错误描述。得是合理的逻辑转换。

调试:
1.print()
调试的方法可以是直接把可能有问题的变量都打印出来,不过以后还得删除或注释掉,比较麻烦。
2.assert()断言,凡是用print来辅助查看的地方,都可以换成assert。

>>> def fun(s):
    n=int(s)
    assert n!=0,'n is zero'
    return 10/n
>>> fun(2)
5.0
>>> fun(0)
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    fun(0)
  File "<pyshell#4>", line 3, in fun
    assert n!=0,'n is zero'
AssertionError: n is zero

assert的意思是,断言它后面的表达式是True的,否则就抛出AssertionError和表达式后面的辅助描述信息。
assert相比print的好处是,在启动Python解释器时,可以使用-0 参数来关闭assert,这样,它们就相当于pass语句。

3.logging
把print的地方都换成logging也是一种方式。,相比assert,logging不会抛出错误,但它可以输出到文件。
需要先引入logging模块,再加一行配置信息,就能看到输出了。

inport logging 
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)

这样logging.info()就可以输出一段文本,可以用level指定错误的级别,有debug info warning error 等几个级别。以此可以控制输出指定级别的错误信息。

4.调试器pdb
让程序单步运行,且在任何时候都可以输入p 变量名来查看变量,输入命令q结束调试,退出程序。
如果程序很长,就会很麻烦。
5.pdb.set_trace()
需要先引入pdb模块,在可能出错的地方,设置断点pdb.set_trace()

import pdb
s = '0'
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停
print(10 / n)

程序运行到断点处会进入pdb调试环境,用命令p可以查看变量或者命令c继续运行。
这笔单步调试效率高,但也好不到哪儿去。

6.使用带有调试功能的IDE,
Visual Studio Code vs

PyCharm
Ecilpse加上pydev插件也可以

用IDE调试起来比较方便,配合logging使用效果更好。

测试驱动开发:TDD:Test-Driven Development
单元测试:用来对一个模块、函数、类进行正确性检验的测试工作。
比如把测试abs()函数所有可能用到的数据放到一个测试模块里,就是一个完整的单元测试。这样如果以后修改了函数,还是用这个测试模块,可以确保修改后的函数与原函数行为一致。
单元测试的测试用例要覆盖所有可能的输入组合、边界、异常。
通过了单元测试并不意味着就没有bug了,但是通不过就以一定有bug。
setUp与tearDown
可以在单元测试中编写两个特殊的setUp()和tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。
Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试.
doctest非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含doctest的注释提取出来。用户看文档的时候,同时也看到了doctest。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值