print("Hi, what is your name?")
name = input()
print(f"Hello {name}!")
[x] 给这个 “最基础版本” 添加一个纬度 —— 逻辑判断和条件分支:
print("How are you today?")
feeling = input()
if 'good' in feeling:
print("I'm feeling good too")
else:
print("Sorry to hear that")
[x] debug
在这里用各种输入测试几次之后,我们发现了一个小 bug:如果用户输入 good 或者 I'm good 这种,代码运行是如我们所想,但是如果用户输入的是 Good,程序会不认,而输出 Sorry to hear that,这显然不合理,这个问题源于我们代码中的不严谨,在判断时未考虑大小写问题。修正也很容易,改成下面这样就好了:
print("How are you today?")
feeling = input()
if 'good' in feeling.lower():
print("I'm feeling good too")
else:
print("Sorry to hear that")
[x] 这次,我们引入一点随机性来增加乐趣:
我们使用来自 random 模块中的 choice 函数,从列表 colors 中随机抽选一个元素。
import random
print("What's your favorite color?")
favcolor = input()
colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'purple']
print(f"You like {favcolor.lower()}? My favorite color is {random.choice(colors)}")
使用 Python time 模块中的 sleep() 方法,这个方法让解释器休息指定的秒数;
用一个类变量 wait 来管理程序说每句话之前要停顿的秒数;
[ ] 留下的疑问:为什么把 wait 设置为 类变量,而不是 实例变量?
4.2 Bot 子类
[x] 有了这个父类,我们就可以一股脑把前面尝试的几轮对话都实现为它不同的子类:
# HelloBot
class HelloBot(Bot):
def __init__(self):
self.q = "Hi, what is your name?"
def _think(self, s):
return f"Hello {s}!"
# GreetingBot
class GreetingBot(Bot):
def __init__(self):
self.q = "How are you today?"
def _think(self, s):
if 'good' in s.lower():
return "I'm feeling good too"
else:
return "Sorry to hear that"
# FavoriteColorBot
import random
class FavoriteColorBot(Bot):
def __init__(self):
self.q = "What's your favorite color?"
def _think(self, s):
colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'purple']
return f"You like {s.lower()}? My favorite color is {random.choice(colors)}"
class Garfield:
def _prompt(self, s):
print(s)
print()
def run(self):
bots_list = [HelloBot(), GreetingBot(), FavoriteColorBot()]
self._prompt("This is Garfield dialog system. Let's talk.")
for bot in bots_list:
bot.run()
[x] 初始化一个 Garfield 实例来运行:
garfield = Garfield()
garfield.run()
结果如下:
This is Garfield dialog system. Let's talk.
Hi, what is your name?
zhengfu
Hello zhengfu!
How are you today?
GOOD!
I'm feeling good too
What's your favorite color?
black
You like black? My favorite color is blue
This is Garfield dialog system. Let's talk.
What's your favorite color?
black
You like black? My favorite color is green
Hi, what is your name?
zhengfu
Hello zhengfu!
How are you today?
fine
I'm feeling good too
This is Garfield dialog system. Let's talk.
Hi, what is your name?
zhengfu
Hello zhengfu!
How are you today?
bad
Sorry to hear that
What's your favorite color?
black
You like black? My favorite color is green
from simpleeval import simple_eval
simple_eval("21 + 21")
returns 42.
Expressions can be as complex and convoluted as you want:
simple_eval("21 + 19 / 7 + (8 % 3) ** 9")
returns 535.714285714.
[x] Bot 子类的定义如下:
from simpleeval import simple_eval
class evalBot(Bot):
def __init__(self):
self.q = 'please type in your arithmetic expression: '
def _think(self, s):
return simple_eval(str(s))
from simpleeval import simple_eval
class evalBot(Bot):
def __init__(self):
self.q = 'please type in your arithmetic expression: '
def _think(self, s):
return simple_eval(s)
[x] 思考:我们的执行流程 "print-input-print-end" 已经在 公共父类Bot 中的 run 方法中确定下来。所以,我需要去 Bot 类的 run 方法中更改执行流程。不对,只要在 evalBot 子类中 override run 方法就可以了。
from simpleeval import simple_eval
class evalBot(Bot):
def __init__(self):
self.q = 'please type in your arithmetic expression: \nif you want to exit, please enter "x" "q" "exit" or "quit".'
def _think(self, s):
return simple_eval(s)
def run(self):
sleep(Bot.wait)
print(self._format(self.q))
self.a = input()
while not((self.a == 'x') or (self.a == 'q') or (self.a == 'exit') or (self.a == 'quit')):
sleep(Bot.wait)
print(self._format(self._think(self.a)))
print(self._format(self.q))
self.a = input()
while not ((self.a == 'x') or (self.a == 'q') or (self.a == 'exit') or (self.a == 'quit')): 想表达的逻辑是:当用户输入不为 'x'、'q'、'exit' 或 'quit' 时继续循环执行。这种写法过于冗长,更简洁的写法是:while not(self.a in ['x', 'q', 'exit', 'quit']):
在方框中输入表达式,想要退出的话,输入 'x'、'q'、'exit' 或 'quit',这些内容用户已经知道了,用不着反复提问。故删去 while 循环里的 print(self._format(self.q))。run 函数最终应该写成这样:
def run(self):
sleep(Bot.wait)
print(self._format(self.q))
self.a = input()
while not(self.a in ['x', 'q', 'exit', 'quit']):
sleep(Bot.wait)
print(self._format(self._think(self.a)))
self.a = input()
def run(self):
sleep(Bot.wait)
print(self._format(self.q))
while not(self.a in ['x', 'q', 'exit', 'quit']):
self.a = input()
sleep(Bot.wait)
print(self._format(self._think(self.a)))
self.a = input()
while 的逻辑判断式涉及到用户输入,可用户输入在循环体内,这代码是错的,应该让 while 循环先跑起来,逻辑判断在代码内执行。成为:
def run(self):
sleep(Bot.wait)
print(self._format(self.q))
while True:
self.a = input()
if not(self.a in ['x', 'q', 'exit', 'quit']):
sleep(Bot.wait)
print(self._format(self._think(self.a)))
else:
break
这代码读起来可真别扭,改一下,另外还要注意用户输入大小写问题:
def run(self):
sleep(Bot.wait)
print(self._format(self.q))
while True:
self.a = input()
if self.a.lower() in ['x', 'q', 'exit', 'quit']:
break
sleep(Bot.wait)
print(self._format(self._think(self.a)))
大功告成:
from simpleeval import simple_eval
class evalBot(Bot):
def __init__(self):
self.q = 'please type in your arithmetic expression: \nif you want to exit, please enter "x" "q" "exit" or "quit".'
def _think(self, s):
return simple_eval(s)
def run(self):
sleep(Bot.wait)
print(self._format(self.q))
while True:
self.a = input()
if self.a.lower() in ['x', 'q', 'exit', 'quit']:
break
sleep(Bot.wait)
print(self._format(self._think(self.a)))
from simpleeval import simple_eval
class evalBot(Bot):
def __init__(self, runtype='once'):
super().__init__(runtype)
self.q = 'please type in your arithmetic expression: \nif you want to exit, please enter "x" "q" "exit" or "quit".'
def _think(self, s):
return simple_eval(s)
1. 了解 本章学习目标
2. 理解问题
3. 设计
4. 构建 "这一设计的代码实现"
4.1 公共父类 Bot
print-input-print
流程print()
输出太快了,如果能停顿一下再输出感觉会更像对话。wait
设置为 类变量,而不是 实例变量?4.2 Bot 子类
4.3 对话系统的主类 Garfield
结果如下:
结果如下:
__init__
函数中设置了一个 Bot.wait 变量,除此之外,我们二人代码的其他部分均一致,可是他为什么要这样做?wait
已在公共父类 Bot 中作为 类变量 统一设死为 1s,为了方便以后的调整,对话系统中各个 bot 的聊天延迟wait
在 Garfield 类中再次统一设定,在初始化对话系统时通过修改类变量Bot.wait
的值来实现。所以,最终的代码为:__init__
函数时,传入的参数是wait=1
,而不是wait
?结果如下:
python3 garfield.py
来运行一下。5. 增量式优化
5.1 “增量式优化” 实例1 —— 给程序输出的对话加上颜色,以区分用户输入的内容
[x] 体会责任分离的好处:这个修改只涉及 Bot 父类的 run 方法。
[x] 要给 Python
print()
输出的文字加上颜色和其他格式,可以使用命令行特定的格式串,自己做起来有点复杂,我们用一个第三方的模块termcolor
,首先打开你的命令行界面运行pip3 install termcolor
:5.2 “增量式优化” 实例2 —— 做一个新的 Bot 子类
[x] 了解要求:这个新的 Bot 子类,可以对用户输入的一个四则运算表达式进行计算求值,也就是把对话系统变成一个计算器,提示是:可以借助第三方库来进行表达式求值,比如这个 simpleeval。
[x] 该 Bot 子类定义如下:
pip3 install simpleeval
:结果如下:
6. 作业
run
方法中确定下来。所以,我需要去 Bot 类的run
方法中更改执行流程。不对,只要在 evalBot 子类中 overriderun
方法就可以了。while not ((self.a == 'x') or (self.a == 'q') or (self.a == 'exit') or (self.a == 'quit')):
想表达的逻辑是:当用户输入不为 'x'、'q'、'exit' 或 'quit' 时继续循环执行。这种写法过于冗长,更简洁的写法是:while not(self.a in ['x', 'q', 'exit', 'quit']):
在方框中输入表达式,想要退出的话,输入 'x'、'q'、'exit' 或 'quit',这些内容用户已经知道了,用不着反复提问。故删去 while 循环里的
print(self._format(self.q))
。run
函数最终应该写成这样:“重复” 几乎总是不好的,在以上代码中,
self.a = input()
就出现了两次,应予以精减,留下循环体中的那句,成为:while 的逻辑判断式涉及到用户输入,可用户输入在循环体内,这代码是错的,应该让 while 循环先跑起来,逻辑判断在代码内执行。成为:
这代码读起来可真别扭,改一下,另外还要注意用户输入大小写问题:
大功告成:
evalBot
真正的特性只在self.q
和_think()
方法中体现,而这个run()
方法里面并没有任何和evalBot
的特性有关的内容,完全是通用的,也就是说如果想写个别的循环运行的 bot,完全可以复用这段代码。这强烈地提示我们,这段代码应该成为父类 Bot 的一部分。__init__()
方法中增加了一个参数runtype
,并指定其缺省值为 'once',这意味着以前所写的只运行一次的 bot 们在实例化时的代码不需要修改,但这些子类的__init__()
方法需要修改,对齐父类的定义,并调用父类的__init__()
方法来初始化self.runtype
。比如:[x] 这里有两处疑问:
[x] 1. 子类继承父类之后,父类
__init__
函数中的self.runtype = runtype
为什么没有直接继承?__init__
函数中要使用super().__init__(runtype)
来初始化self.runtype
而不是使用super().__init__()
或者super().__init__('once')
或者super().__init__(runtype='once')
?小结
Logging
2020-03-01 22:33:43 initialize