__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捕获的,它的类型就是写上去的ValueError
和ZeroDivisionError
,把这个错误复制给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。