破解书中代码的 Pickle 模块 "Conceptual Programming with Python"

Pickle module that breaks the code from the book "Conceptual Programming with Python"

我正在使用“使用 Python 进行概念编程”一书中的示例。

代码旨在解决的问题的一些介绍:

示例:创建知识库

作为面向对象编程的第二个例子,我们将实现一个简单的猜谜游戏!但是这个游戏将能够从经验中学习,也就是说,你将能够在玩的过程中教授程序。对于这个例子,我们将创建一个动物知识库。用户会想到一种动物,而计算机将不得不通过向您询问有关该动物的(明智的)问题来弄清楚它是哪种动物;答案要么是,要么不是。如果它没有猜对动物,程序会问你什么是一个明智的问题,以便下次能够找到正确的解决方案!

例1:计算机只知道如何区分鸟或猫,看它是否有4条腿。即初始知识库只有这两种动物和一个问题。

再一次,这遵循树结构!根据用户的回答是或否,计算机会提出不同的问题,或提供答案!

例2:你教电脑一道新题。

现在 pickle 片段:

文件总是需要先打开;然后操作它们(例如将它们的内容加载到数据结构中),最后在它们不再使用时关闭它们。因此,我们将尝试打开一个名为 animal.kb 的文件,我们将在其中保存树。我们第一次打开文件时,它是空的,所以我们将创建我们以前的知识库。为此,我们将使用 try-except 结构。为什么?每当我们尝试打开一个不存在的文件时,这将创建一个异常 FileNotFoundError。我们可以简单地捕捉它并“手动”创建知识库。然后我们让用户玩,我们不断更新知识库kb。程序结束时,当用户不想玩了,我们将kb中包含的信息‘dump’到文件“animal.kb”。

第二次尝试打开“animal.kb”知识库后出现错误:

```

Do you want to play? y
    Traceback (most recent call last):
      File "...\ConceptualPython02\knowledgeBase.py", line 71, in <module>
        kb = kb.play()
    AttributeError: 'NoneType' object has no attribute 'play'
    
    Process finished with exit code 1

```

那是有问题的代码:


    import pickle


    class Knowledge:
        pass
    
    
    class Question(Knowledge):
        def __init__(self, text, if_yes, if_no):
            self.text, self.if_yes, self.if_no = text, if_yes, if_no
    
        def play(self):
            if ask(self.text):
                self.if_yes = self.if_yes.play()
            else:
                self.if_no = self.if_no.play()
                return self
    
    
    class Answer(Knowledge):
        def __init__(self, text):
            self.text = text
    
        def play(self):
            if ask("Were you thinking of a {} ? ".format(self.text)):
                print("I knew it!")
                return self
            # here we got it right, # so we simply return the
            # Answer node as it is.
            else:
                newanimal = input("What animal were\ "
                                  "you thinking of? ")
                newquestion = input("What is a question "
                                    "to distinguish between {} and {} ?"
                                    .format(self.text, newanimal))
                # but in case we didn't know the animal
                # we need to modify the node adding # the appropriate question and# what to do
                # ifyes and if no
                if ask("For {} , what should be the answer? ".format(newanimal)):
                    return Question(newquestion,
                                    Answer(newanimal), self)
                else:
                    return Question(newquestion,
                                    self, Answer(newanimal))
    
    
    def ask(q):
        while True:
            ans = input(q + " ")
            if ans == "y":
                return True
            elif ans == "n":
                return False
            else:
                print("Please answer y or n!")
    
    
    try:
        file = open("animal.kb", "rb")
        kb = pickle.load(file)
        file.close()
    
    except FileNotFoundError:
        kb = Question("Does it have 4 legs?", Question("Does it bark?",
                                                       Answer("dog"), Answer("cat")), Answer("bird"))
    while True:
        if not ask("Do you want to play?"):
            break
        kb = kb.play()
    
    file = open("animal.kb", "wb")
    pickle.dump(kb, file)
    file.close()

当然,它也没有像应该的那样缓存关于动物的新问题。

play 应该总是 return 一个 Knowledge 实例(或继承自它)。但是当在 Question.play 中调用 ask returns True 时,你不会 return 任何东西:

        def play(self):
            if ask(self.text):
                self.if_yes = self.if_yes.play()
            else:
                self.if_no = self.if_no.play()
                return self

所以改变 return self 的缩进,所以它总是被执行。