Python调试、异常、测试

调试

    1.print()

    用print()把可能有问题的变量打印出来,但是用print()最大的坏处是将来还得删掉它,想想程序里到处都是print(),运行结果也会包含很多垃圾信息

    2.断言

    凡是可能有问题的变量,都可以用断言(assert)来替代:

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

def main():
    foo('0')
    assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。

    如果断言失败,assert语句本身就会抛出AssertionError:

Traceback (most recent call last):
  ...
AssertionError: n is zero!

    程序中如果到处充斥着assert,和print()相比也好不到哪去。

    3.logging

    和assert比,logging不会抛出错误,而且可以输出到文件:  

import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
    输出
$ python3 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
    logging允许指定记录信息的级别,有debug,info,warning,error等几个级别,当指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样就可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。

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

    4.pdb

    启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。程序:
# err.py
s = '0'
n = int(s)
print(10 / n)
    以参数-m pdb启动后,pdb定位到下一步要执行的代码-&gt; s = '0'。
$ python -m pdb err.py
> /Users/michael/Github/learn-python3/samples/debug/err.py(2)<module>()
-> s = '0'
    输入命令l 来查看代码, 输入命令n可以单步执行代码;任何时候都可以输入命令p 变量名来查看变量;输入命令q结束调试,退出程序:
(Pdb) l
  1     # err.py
  2  -> s = '0'
  3     n = int(s)
  4     print(10 / n)
(Pdb) n
> /Users/michael/Github/learn-python3/samples/debug/err.py(3)<module>()
-> n = int(s)
(Pdb) p s
'0'
(Pdb) q

    通过pdb在命令行调试的方法理论上是万能的,但太麻烦了。

    5.pdb.set_trace()

只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点:
# err.py
import pdb

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

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

    6.IDE集成开发环境

    目前比较好的Python IDE有PyCharm。虽然用IDE调试起来比较方便,但是最后你会发现,logging才是终极武器。

异常

     Python使用被称为异常 的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继续运行;如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告。
    异常是使用try-except 代码块处理的。try-except 代码块让Python执行指定的操作,同时告诉Python发生异常时怎么办。使用了try-except 代码块时,即便出现异常,程序也将继续运行:显示你编写的友好的错误消息,而不是令用户迷惑的traceback。

1.使用try-except 代码块

    当你认为可能发生了错误时,可编写一个try-except 代码块来处理可能引发的异常。你让Python尝试运行一些代码,并告诉它如果这些代码引发了指定的异常,该怎么办

    处理ZeroDivisionError 异常的try-except 代码块类似于下面这样:

try:
    print(5/0)
except ZeroDivisionError:
    print("You can't divide by zero!")

   1.1try-except-else 代码块

     try-except-else 代码块的工作原理大致如下:Python尝试执行try 代码块中的代码;只有可能引发异常的代码才需要放在try 语句中。有一些仅在try 代码块成功执行时才需要运行的代码;这些代码应放在else 代码块中。except 代码块告诉Python,如果它尝试运行try 代码块中的代码时引发了指定的异常,该怎么办。
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True:
    first_number = input("\nFirst number: ")
    if first_number == 'q':
        break
    second_number = input("Second number: ")
    try:
        answer = int(first_number) / int(second_number)
    except ZeroDivisionError:
        print("You can't divide by 0!")
    else:
        print(answer)
    通过预测可能发生错误的代码,可编写健壮的程序,它们即便面临无效数据或缺少资源,也能继续运行,从而能够抵御无意的用户错误和恶意的攻击

    1.2FileNotFoundError 异常

     FileNotFoundError 异常,这是Python找不到要打开的文件时创建的异常。在这个示例中,这个错误是函数open() 导致的,因此要处理这个错误,必须将try 语句放在包含open() 的代码行之前:
filename = 'alice.txt'

try:
    with open(filename) as f_obj:
        contents = f_obj.read()
except FileNotFoundError:
    msg = "Sorry, the file " + filename + " does not exist."
    print(msg)

    1.3分析文本

    下面来提取童话 Alice in Wonderland 的文本,并尝试计算它包含多少个单词。我们将使用方法split() ,它根据一个字符串创建一个单词列表。下面是对只包含童话名"Alice in Wonderland" 的字符串调用方法split() 的结果:
>>> title = "Alice in Wonderland"
>>> title.split()
['Alice', 'in', 'Wonderland']
    方法split() 以空格为分隔符将字符串分拆成多个部分,并将这些部分都存储到一个列表中。结果是一个包含字符串中所有单词的列表,虽然有些单词可能包含标点。为计算 Alice in Wonderland 包含多少个单词,我们将对整篇小说调用split() ,再计算得到的列表包含多少个元素,从而确定整篇童话大致包含多少个单词:
filename = 'alice.txt' 
try:
    with open(filename) as f_obj:
        contents = f_obj.read()
except FileNotFoundError:
    msg = "Sorry, the file " + filename + " does not exist."
    print(msg)
else:
    # 计算文件大致包含多少个单词
    words = contents.split()
    num_words = len(words)
    print("The file " + filename + " has about " + str(num_words) + " words.")

    1.4使用多个文件

     将这个程序的大部分代码移到一个名为count_words() 的函数中,这样对多本书进行分析时将更容易:
def count_words(filename):
     """计算一个文件大致包含多少个单词"""
    try:
        with open(filename) as f_obj:
            contents = f_obj.read()
    except FileNotFoundError:
        msg = "Sorry, the file " + filename + " does not exist."
        print(msg)
    else:
        # 计算文件大致包含多少个单词
        words = contents.split()
        num_words = len(words)
        print("The file " + filename + " has about " + str(num_words) + " words.")

filename = 'alice.txt'
count_words(filename)
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
    count_words(filename)
The file alice.txt has about 29461 words.
Sorry, the file siddhartha.txt does not exist.
The file moby_dick.txt has about 215136 words.
The file little_women.txt has about 189079 words.
   使用try-except 代码块提供了两个重要的优点:避免让用户看到traceback;让程序能够继续分析能够找到的其他文件。如果不捕获因找不到siddhartha.txt而引发的FileNotFoundError 异常,用户将看到完整的traceback,而程序将在尝试分析 Siddhartha 后停止运行——根本不分析 Moby Dick 和 Little Women 。
    Python有一个pass 语句,可在捕获到异常时什么都不要做:
 def count_words(filename):
      """计算一个文件大致包含多少个单词"""
      try:
          --snip--
      except FileNotFoundError:
❶         pass
      else:
          --snip--
      出现FileNotFoundError 异常时,将执行except 代码块中的代码,但什么都不会发生。这种错误发生时,不会出现traceback,也没有任何输出。
    pass 语句还可以充当占位符,它提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么

测试函数

    使用Python模块unittest 中的工具来测试代码。学习编写测试用例,核实一系列输入都将得到预期的输出。你将看到测试通过了是什么样子,测试未通过又是什么样子,还将知道测试未通过如何有助于改进代码。你将学习如何测试函数和类,并将知道该为项目编写多少个测试。

1单元测试和测试用例

    单元测试 用于核实函数的某个方面没有问题;测试用例 是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。全覆盖式测试 用例包含一整套单元测试,涵盖了各种可能的函数使用方式。对于大型项目,要实现全覆盖可能很难。通常,最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖。

    要学习测试,得有要测试的代码。下面是一个简单的函数,它接受名和姓并返回整洁的姓名:

name_function.py

def get_formatted_name(first, last):
    """Generate a neatly formatted full name."""
    full_name = first + ' ' + last
    return full_name.title()

1能通过的测试

    要为函数编写测试用例,可先导入模块unittest 以及要测试的函数,再创建一个继承unittest.TestCase 的类,并编写一系列方法对函数行为的不同方面进行测试。下面是一个只包含一个方法的测试用例,它检查函数get_formatted_name() 在给定名和姓时能否正确地工作:方法名必须以test_打头,这样它才会在我们运行test_name_function.py时自动运行

test_name_function.py

import unittest
from name_function import get_formatted_name

❶ class NamesTestCase(unittest.TestCase):    #创建类NamesTestCase继承于unittest.TestCase 类,用于包含针对get_formatted_name()的单元测试
      """测试name_function.py"""

      def test_first_last_name(self):
          """能够正确地处理像Janis Joplin这样的姓名吗?"""
❷         formatted_name = get_formatted_name('janis', 'joplin')
          #将formatted_name 的值同字符串'Janis Joplin' 进行比较,如果相等,通过;如果不相等,则不通过
❸         self.assertEqual(formatted_name, 'Janis Joplin')   


  unittest.main()

    unittest 类最有用的方法assertEqual() 之一:一个断言 方法。断言方法用来核实得到的结果是否与期望的结果一致。代码行unittest.main() 让Python运行这个文件中的测试。运行test_name_function.py时,得到的输出如下:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

    第1行的句点表明有一个测试通过了。接下来的一行指出Python运行了一个测试,消耗的时间不到0.001秒。最后的OK 表明该测试用例中的所有单元测试都通过了。

2不能通过的测试

    修改get_formatted_name() ,使其能够处理中间名,但这样做让这个函数无法正确地处理像Janis Joplin只有名和姓的姓名。name_function.py

def get_formatted_name(first, middle, last):
    """生成整洁的姓名"""
    full_name = first + ' ' + middle + ' ' + last
    return full_name.title()
运行程序test_name_function.py时,输出如下:
❶ E    #指出测试用例中有一个单元测试导致了错误
  ======================================================================
❷ ERROR: test_first_last_name (__main__.NamesTestCase)  #NamesTestCase 中的test_first_last_name() 导致了错误
  ----------------------------------------------------------------------
❸ Traceback (most recent call last):
    File "test_name_function.py", line 8, in test_first_last_name
      formatted_name = get_formatted_name('janis', 'joplin')
  TypeError: get_formatted_name() missing 1 required positional argument: 'last'
#标准的traceback,它指出函数调用get_formatted_name('janis', 'joplin') 有问题,缺少一个必不可少的位置实参。
  ----------------------------------------------------------------------
❹ Ran 1 test in 0.000s
❺ FAILED (errors=1) #指出整个测试用例都未通过,因为运行该测试用例时发生了一个错误

测试未通过时怎么办

    测试未通过时,如果你检查的条件没错,测试通过了意味着函数的行为是对的,而测试未通过意味着你编写的新代码有错。因此,测试未通过时,不要修改测试,而应修复导致测试不能通过的代码:检查刚对函数所做的修改,找出导致函数行为不符合预期的修改。

    将中间名设置为可选的,可在函数定义中将形参middle 移到形参列表末尾,并将其默认值指定为一个空字符串。我们还要添加一个if 测试,以便根据是否提供了中间名相应地创建姓名:

name_function.py

def get_formatted_name(first, last, middle=''):
    """生成整洁的姓名"""
    if middle:
        full_name = first + ' ' + middle + ' ' + last
    else:
        full_name = first + ' ' + last
    return full_name.title()
再次运行test_name_function.py:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

添加新测试

确定get_formatted_name() 又能正确地处理简单的姓名后,我们再编写一个测试,用于测试包含中间名的姓名。为此,我们在NamesTestCase 类中再添加一个方法:

import unittest
  from name_function import get_formatted_name

  class NamesTestCase(unittest.TestCase):
      """测试name_function.py """
      def test_first_last_name(self):
          """能够正确地处理像Janis Joplin这样的姓名吗?"""
          formatted_name = get_formatted_name('janis', 'joplin')
          self.assertEqual(formatted_name, 'Janis Joplin')

      def test_first_last_middle_name(self):
          """能够正确地处理像Wolfgang Amadeus Mozart这样的姓名吗?"""
❶         formatted_name = get_formatted_name(
              'wolfgang', 'mozart', 'amadeus')
          self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')

  unittest.main()
再次运行test_name_function.py时,两个测试都通过了:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

测试类

    1各种断言方法

    Python在unittest.TestCase 类中提供了很多断言方法。断言方法检查你认为应该满足的条件是否确实满足。如果该条件确实满足,你对程序行为的假设就得到了确认,你就可以确信其中没有错误。如果你认为应该满足的条件实际上并不满足,Python将引发异常。

    类的测试与函数的测试相似——你所做的大部分工作都是测试类中方法的行为,但存在一些不同之处,下面来编写一个类进行测试。来看一个帮助管理匿名调查的类:

survey.py

class AnonymousSurvey():
      """收集匿名调查问卷的答案"""

❶     def __init__(self, question):
          """存储一个问题,并为存储答案做准备"""
          self.question = question
          self.responses = []

❷     def show_question(self):
          """显示调查问卷"""
          print(question)

❸     def store_response(self, new_response):
          """存储单份调查答卷"""
          self.responses.append(new_response)

❹     def show_results(self):
          """显示收集到的所有答卷"""
          print("Survey results:")
          for response in responses:
              print('- ' + response)
为证明AnonymousSurvey 类能够正确地工作,我们来编写一个使用它的程序:
from survey import AnonymousSurvey

#定义一个问题,并创建一个表示调查的AnonymousSurvey对象
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)

#显示问题并存储答案
my_survey.show_question()
print("Enter 'q' at any time to quit.\n")
while True:
    response = input("Language: ")
    if response == 'q':
        break
    my_survey.store_response(response)

# 显示调查结果
print("\nThank you to everyone who participated in the survey!")
my_survey.show_results()
输出:
What language did you first learn to speak?
Enter 'q' at any time to quit.

Language: English
Language: Spanish
Language: English
Language: q

Thank you to everyone who participated in the survey!
Survey results:
- English
- Spanish
- English

AnonymousSurvey 类可用于进行简单的匿名调查。假设我们将它放在了模块survey 中,并想进行改进:让每位用户都可输入多个答案;编写一个方法,它只列出不同的答案,并指出每个答案出现了多少次;再编写一个类,用于管理非匿名调查。

进行上述修改存在风险,可能会影响AnonymousSurvey 类的当前行为。例如,允许每位用户输入多个答案时,可能不小心修改了处理单个答案的方式。要确认在开发这个模块时没有破坏既有行为,可以编写针对这个类的测试。

test_survey.py

import unittest
from survey import AnonymousSurvey

❶ class TestAnonmyousSurvey(unittest.TestCase):
      """针对AnonymousSurvey类的测试"""

❷     def test_store_single_response(self):
          """测试单个答案会被妥善地存储"""
          question = "What language did you first learn to speak?"
❸         my_survey = AnonymousSurvey(question)
          my_survey.store_response('English')

❹         self.assertIn('English', my_survey.responses)

  unittest.main()
运行test_survey.py时,测试通过了:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

只能收集一个答案的调查用途不大。下面来核实用户提供三个答案时,它们也将被妥善地存储。为此,我们在TestAnonymousSurvey 中再添加一个方法:

 import unittest
 from survey import AnonymousSurvey

  class TestAnonymousSurvey(unittest.TestCase):
      """针对AnonymousSurvey类的测试"""

      def test_store_single_response(self):
          """测试单个答案会被妥善地存储"""
          --snip--

      def test_store_three_responses(self):
          """测试三个答案会被妥善地存储"""
          question = "What language did you first learn to speak?"
          my_survey = AnonymousSurvey(question)
❶         responses = ['English', 'Spanish', 'Mandarin']
          for response in responses:
              my_survey.store_response(response)

❷         for response in responses:
              self.assertIn(response, my_survey.responses)

  unittest.main()
运行test_survey.py时,两个测试(针对单个答案的测试和针对三个答案的测试)都通过了:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

方法setUp()与tearDown()

    可以在单元测试中编写两个特殊的setUp()tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。setUp()tearDown()方法有什么用呢?设想你的测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码.

    unittest.TestCase 类包含方法setUp() ,让我们只需创建一次实例对象,并在每个测试方法中使用它们。如果你在TestCase 类中包含了方法setUp() ,Python将先运行它,再运行各个以test_打头的方法。这样,在你编写的每个测试方法中都可使用在方法setUp() 中创建的对象了。

    使用setUp() 来创建一个调查对象和一组答案,供方法test_store_single_response() 和test_store_three_responses() 使用:

import unittest
  from survey import AnonymousSurvey

  class TestAnonymousSurvey(unittest.TestCase):
      """针对AnonymousSurvey类的测试"""

      def setUp(self):
          """
          创建一个调查对象和一组答案,供使用的测试方法使用
          """
          question = "What language did you first learn to speak?"
❶         self.my_survey = AnonymousSurvey(question)
❷         self.responses = ['English', 'Spanish', 'Mandarin'] #存储这两样东西的变量名包含前缀self,因此可在这个类的任何地方使用

      def test_store_single_response(self):
          """测试单个答案会被妥善地存储"""
          self.my_survey.store_response(self.responses[0])
          self.assertIn(self.responses[0], self.my_survey.responses)

      def test_store_three_responses(self):
          """测试三个答案会被妥善地存储"""
          for response in self.responses:
              self.my_survey.store_response(response)
          for response in self.responses:
              self.assertIn(response, self.my_survey.responses)

  unittest.main()

    再次运行test_survey.py时,这两个测试也都通过了。如果要扩展AnonymousSurvey ,使其允许每位用户输入多个答案,这些测试将很有用。修改代码以接受多个答案后,可运行这些测试,确认存储单个答案或一系列答案的行为未受影响。

    测试自己编写的类时,方法setUp() 让测试方法编写起来更容易:可在setUp() 方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实例并设置其属性,这要容易得多。

注意

  运行测试用例时,每完成一个单元测试,Python都打印一个字符:测试通过时打印一个句点;测试引发错误时打印一个E ;测试导致断言失败时打印一个F。

    参与工作量较大的项目时,你应对自己编写的函数和类的重要行为进行测试。这样你就能够更加确定自己所做的工作不会破坏项目的其他部分,你就能够随心所欲地改进既有代码了。如果不小心破坏了原来的功能,你马上就会知道,从而能够轻松地修复问题。相比于等到不满意的用户报告bug后再采取措施,在测试未通过时采取措施要容易得多。

练习:对Student类编写单元测试,结果发现测试不通过,请修改Student类,让测试通过:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def get_grade(self):
        if self.score >= 80 and self.score <=100:
            return 'A'
        elif self.score >= 60 and self.score<80:
            return 'B'
        elif self.score>=0 and self.score<60:
            return 'C'
        raise ValueError(r"score must be between 0~100 '%s'" % self.score)
class TestStudent(unittest.TestCase):

    def test_80_to_100(self):
        s1 = Student('Bart', 80)
        s2 = Student('Lisa', 100)
        self.assertEqual(s1.get_grade(), 'A')
        self.assertEqual(s2.get_grade(), 'A')

    def test_60_to_80(self):
        s1 = Student('Bart', 60)
        s2 = Student('Lisa', 79)
        self.assertEqual(s1.get_grade(), 'B')
        self.assertEqual(s2.get_grade(), 'B')

    def test_0_to_60(self):
        s1 = Student('Bart', 0)
        s2 = Student('Lisa', 59)
        self.assertEqual(s1.get_grade(), 'C')
        self.assertEqual(s2.get_grade(), 'C')

    def test_invalid(self):
        s1 = Student('Bart', -1)
        s2 = Student('Lisa', 101)
        with self.assertRaises(ValueError):
            s1.get_grade()
        with self.assertRaises(ValueError):
            s2.get_grade()

if __name__ == '__main__':
    unittest.main()
练习
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
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

另一种重要的断言就是期待抛出指定类型的Error,比如通过d['empty']访问不存在的key时,断言会抛出KeyError

with self.assertRaises(KeyError):
    value = d['empty']

而通过d.empty访问不存在的key时,我们期待抛出AttributeError

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

小结:

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

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

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

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

文件测试

Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。

doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用...表示中间一大段烦人的输出。

让我们用doctest来测试上次编写的Dict类:

# 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()
运行 python mydict2.py
$ python mydict2.py
什么输出也没有。这说明我们编写的doctest运行都是正确的。如果程序有问题,比如把 __getattr__() 方法注释掉,再运行就会报错:
$ python mydict2.py
**********************************************************************
File "/Users/michael/Github/learn-python3/samples/debug/mydict2.py", line 10, in __main__.Dict
Failed example:
    d1.x
Exception raised:
    Traceback (most recent call last):
      ...
    AttributeError: 'Dict' object has no attribute 'x'
**********************************************************************
File "/Users/michael/Github/learn-python3/samples/debug/mydict2.py", line 16, in __main__.Dict
Failed example:
    d2.c
Exception raised:
    Traceback (most recent call last):
      ...
    AttributeError: 'Dict' object has no attribute 'c'
**********************************************************************
1 items had failures:
   2 of   9 in __main__.Dict
***Test Failed*** 2 failures.
练习:
def fact(n):
    '''
    Calculate 1*2*...*n

    >>> fact(1)
    1
    >>> fact(10)
    3628800
    >>> fact(-1)
    Traceback (most recent call last):
	...
    ValueError
    '''
    if n < 1:
        raise ValueError()
    if n == 1:
        return 1
    return n * fact(n - 1)
if __name__ == '__main__':
   import doctest
   doctest.testmod()





  • 10
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值