Python学习笔记(八):面向对象编程、错误调试和测试(快速入门篇)

来源:互联网 发布:淘宝代销分账 保証金 编辑:程序博客网 时间:2024/05/16 15:17

看完自强学堂的介绍后,觉得少讲了很多在《Head First Python》和《Python基础教程》中有的东西,于是借廖雪峰的博客进行进一步补充:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000

由于是补充,所以这里仅提及在前面笔记中缺少的东西,不做重复介绍。

面向对象编程

使用–slots–

正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。

>>> class Student(object):    pass>>> s = Student()>>> s.name = 'Michael' # 动态给实例绑定一个属性>>> def set_age(self, age): # 定义一个函数作为实例方法        self.age = age>>> from types import MethodType>>> s.set_age = MethodType(set_age, s, Student) # 给实例绑定一个方法>>> Student.set_score = MethodType(set_score, None, Student)  #给class绑定方法

定义一个特殊的–slots–变量,来限制该class能添加的属性。–slots–定义的属性仅对当前类起作用,对继承的子类是不起作用的

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

使用@property

@property装饰器是负责把一个方法变成属性调用的

class Student(object):    @property   #把一个getter方法变成属性    def birth(self):        return self._birth    @birth.setter   #把一个getter方法变成属性    def birth(self, value):        self._birth = value    @property   #只读    def age(self):        return 2014 - self._birth

多重继承Mixin

在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为Mixin。

为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixin和FlyableMixin。类似的,你还可以定义出肉食动物CarnivorousMixin和植食动物HerbivoresMixin,让某个动物同时拥有好几个Mixin。

class Dog(Mammal, RunnableMixin, CarnivorousMixin):    pass

Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。

定制类

–str–/–repr–打印实例的格式

直接显示变量调用的不是–str–(),而是–repr–(),两者的区别是–str–()返回用户看到的字符串,而–repr–()返回程序开发者看到的字符串,也就是说,–repr–()是为调试服务的。

解决办法是再定义一个–repr–()。但是通常–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

如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个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 # 返回下一个值‘

–getitem–下标存取

class Fib(object):    def __getitem__(self, n):   #仅支持普通和切片,step、负数等都不支持        if isinstance(n, int):  #普通下标            a, b = 1, 1            for x in range(n):                a, b = b, a + b            return a        if isinstance(n, slice):    #切片            start = n.start            stop = n.stop            a, b = 1, 1            L = []            for x in range(stop):                if x >= start:                    L.append(a)                a, b = b, a + b            return L

与之对应的是–setitem–()方法,把对象视作list或dict来对集合赋值。最后,还有一个–delitem–()方法,用于删除某个元素。

–getattr–动态返回属性

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

此外,注意到任意调用如s.abc都会返回None,这是因为定义的–getattr–默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:

class Student(object):    def __getattr__(self, attr):        if attr=='age':            return lambda: 25        raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)

–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()My name is Michael.

–call–()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。

如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。

那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象。通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。

使用元类

type()创建class的方法

type()函数可以查看一个类型或变量的类型,class的类型就是type,实例的类型就是class X。

class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。

type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)…的定义。

>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class

要创建一个class对象,type()函数依次传入3个参数:

  • class的名称;
  • 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
  • class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

正常情况下,我们都用class Xxx…来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。

metaclass

先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

错误、调试和测试

错误处理

Python内置的try…except…finally用来处理错误十分方便。出错时,会分析错误信息并定位错误发生的代码位置才是最关键的。

程序也可以主动抛出错误,让调用者来处理相应的错误。但是,应该在文档中写清楚可能会抛出哪些错误,以及错误产生的原因。

记录错误

Python内置的logging模块可以非常容易地记录错误信息:

import logging

def foo(s):    return 10 / int(s)def bar(s):    return foo(s) * 2def main():    try:        bar('0')    except StandardError, e:        logging.exception(e)main()

程序打印完错误信息后会继续执行,并正常退出。通过配置,logging还可以把错误记录到日志文件里,方便事后排查。

抛出错误(自己定义raise)

如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例。只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError,TypeError),尽量使用Python内置的错误类型。

调试

断言

凡是用print来辅助查看的地方,都可以用断言(assert)来替代。

def foo(s):    n = int(s)    assert n != 0, 'n is zero!'    return 10 / ndef main():    foo('0')

assert的意思是,表达式n != 0应该是True,否则,后面的代码就会出错。如果断言失败,assert语句本身就会抛出AssertionError。

启动Python解释器时可以用-O参数来关闭assert。关闭后,可以把所有的assert语句当成pass来看。

logging

把print替换为logging,和assert比,logging不会抛出错误,而且可以输出到文件

import logginglogging.basicConfig(level=logging.INFO) #设置等级s = '0'n = int(s)logging.info('n = %d' % n)print 10 / n

logging允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。

logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。

pdb

是启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。

以参数-m pdb启动后,pdb定位到下一步要执行的代码。

输入命令l来查看代码。

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

输入命令p 变量名来查看变量。

输入命令q结束调试,退出程序。

pdb.set_trace()

也是用pdb,但是不需要单步执行,我们只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点。

运行代码,程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行。

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

IDE

单元测试

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。

单元测试可以有效地测试某个程序模块的行为,是未来重构代码的信心保证。

单元测试的测试用例要覆盖常用的输入组合、边界条件和异常。

单元测试代码要非常简单,如果测试代码太复杂,那么测试代码本身就可能有bug。

单元测试通过了并不意味着程序就没有bug了,但是不通过程序肯定有bug。

直接附链接吧:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/00140137128705556022982cfd844b38d050add8565dcb9000

文档测试

doctest非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含doctest的注释提取出来。用户看文档的时候,同时也看到了doctest。

def abs(n):    '''    Function to get absolute value of number.    Example:    >>> abs(1)    1    >>> abs(-1)    1    >>> abs(0)    0    '''    return n if n >= 0 else (-n)if __name__=='__main__':    import doctest    doctest.testmod()
0 0
原创粉丝点击