python输入print跳到documentation-习题 48: 更复杂的用户输入
習(xí)題 48: 更復(fù)雜的用戶輸入?
你的游戲可能一路跑得很爽,不過你處理用戶輸入的方式肯定讓你不勝其煩了。每一個(gè)房間都需要一套自己的語句,而且只有用戶完全輸入正確后才能執(zhí)行。你需要一個(gè)設(shè)備,它可以允許用戶以各種方式輸入語匯。例如下面的機(jī)種表述都應(yīng)該被支持才對(duì):
open door
open the door
go THROUGH the door
punch bear
Punch The Bear in the FACE
也就是說,如果用戶的輸入和常用英語很接近也應(yīng)該是可以的,而你的游戲要識(shí)別出它們的意思。為了達(dá)到這個(gè)目的,我們將寫一個(gè)模組專門做這件事情。這個(gè)模組里邊會(huì)有若干個(gè)類,它們互相配合,接受用戶輸入,并且將用戶輸入轉(zhuǎn)換成你的游戲可以識(shí)別的命令。
英語的簡單格式是這個(gè)樣子的:
單詞由空格隔開。
句子由單詞組成。
語法控制句子的含義。
所以最好的開始方式是先搞定如何得到用戶輸入的詞匯,并且判斷出它們是什么。
我們的游戲語匯?
我在游戲里創(chuàng)建了下面這些語匯:
表示方向: north, south, east, west, down, up, left, right, back.
動(dòng)詞: go, stop, kill, eat.
修飾詞: the, in, of, from, at, it
名詞: door, bear, princess, cabinet.
數(shù)詞: 由 0-9 構(gòu)成的數(shù)字。
說到名詞,我們會(huì)碰到一個(gè)小問題,那就是不一樣的房間會(huì)用到不一樣的一組名詞,不過讓我們先挑一小組出來寫程序,以后再做改進(jìn)把。
如何斷句?
我們已經(jīng)有了詞匯表,為了分析句子的意思,接下來我們需要找到一個(gè)斷句的方法。我們對(duì)于句子的定義是“空格隔開的單詞”,所以只要這樣就可以了:
stuff = raw_input("> ")
words = stuff.split()
目前做到這樣就可以了,不過這招在相當(dāng)一段時(shí)間內(nèi)都不會(huì)有問題。
語匯元組?
一旦我們知道了如何將句子轉(zhuǎn)化成詞匯列表,剩下的就是逐一檢查這些詞匯,看它們是什么類型。為了達(dá)到這個(gè)目的,我們將用到一個(gè)非常好使的 Python 數(shù)據(jù)結(jié)構(gòu),叫做”元組(tuple)”。元組其實(shí)就是一個(gè)不能修改的列表。創(chuàng)建它的方法和創(chuàng)建列表差不多,成員之間需要用逗號(hào)隔開,不過方括號(hào)要換成圓括號(hào) () :
first_word = ("direction", "north")
second_word = ("verb", "go")
sentence = [first_word, second_word]
這樣我們就創(chuàng)建了一個(gè) (TYPE, WORD) 組,讓你識(shí)別出單詞,并且對(duì)它執(zhí)行指令。
這只是一個(gè)例子,不過最后做出來的樣子也差不多。你接受用戶輸入,用 split
將其分隔成單詞列表,然后分析這些單詞,識(shí)別它們的類型,最后重新組成一個(gè)句子。
掃描輸入?
現(xiàn)在你要寫的是詞匯掃描器。這個(gè)掃描器會(huì)將用戶的輸入字符串當(dāng)做參數(shù),然后返回由多個(gè) (TOKEN, WORD) 組成的一個(gè)列表,這個(gè)列表實(shí)現(xiàn)類似句子的功能。如果一個(gè)單詞不在預(yù)定的詞匯表中,那它返回時(shí) WORD 應(yīng)該還在,但 TOKEN 應(yīng)該設(shè)置成一個(gè)專門的錯(cuò)誤標(biāo)記。這個(gè)錯(cuò)誤標(biāo)記將告訴用戶哪里出錯(cuò)了。
有趣的地方來了。我不會(huì)告訴你這些該怎樣做,但我會(huì)寫一個(gè)“單元測試(unit test)”,而你要把掃描器寫出來,并保證單元測試能夠正常通過。
“異常”和數(shù)字?
有一件小事情我會(huì)先幫幫你,那就是數(shù)字轉(zhuǎn)換。為了做到這一點(diǎn),我們會(huì)作一點(diǎn)弊,使用“異常(exceptions)”來做。“異常”指的是你運(yùn)行某個(gè)函數(shù)時(shí)得到的錯(cuò)誤。你的函數(shù)在碰到錯(cuò)誤時(shí),就會(huì)“提出(raise)”一個(gè)“異常”,然后你就要去處理(handle)這個(gè)異常。假如你在Python 里寫了這些東西:
~/projects/simplegame $ python
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>int("hell")
Traceback (most recent call last):
File "", line 1, in
ValueError: invalid literal for int() with base 10: "hell"
>>
這個(gè) ValueError 就是 int() 函數(shù)拋出的一個(gè)異常。因?yàn)槟憬o int() 的參數(shù)不是一個(gè)數(shù)字。 int() 函數(shù)其實(shí)也可以返回一個(gè)值來告訴你它碰到了錯(cuò)誤,不過由于它只能返回整數(shù)值,所以很難做到這一點(diǎn)。它不能返回 -1,因?yàn)檫@也是一個(gè)數(shù)字。
int() 沒有糾結(jié)在它“究竟應(yīng)該返回什么”上面,而是提出了一個(gè)叫做 ValueError
的異常,然后你只要處理這個(gè)異常就可以了。
處理異常的方法是使用 try 和 except 這兩個(gè)關(guān)鍵字:
def convert_number(s):
try:
return int(s)
except ValueError:
return None
你把要試著運(yùn)行的代碼放到 try 的區(qū)段里,再將出錯(cuò)后要運(yùn)行的代碼放到 except
區(qū)段里。在這里,我們要試著調(diào)用 int() 去處理某個(gè)可能是數(shù)字的東西,如果中間出了錯(cuò),我們就抓到這個(gè)錯(cuò)誤,然后返回 None。
在你寫的掃描器里面,你應(yīng)該使用這個(gè)函數(shù)來測試某個(gè)東西是不是數(shù)字。做完這個(gè)檢查,你就可以聲明這個(gè)單詞是一個(gè)錯(cuò)誤單詞了。
你應(yīng)該測試的東西?
這里是你應(yīng)該使用的測試文件 tests/lexicon_tests.py :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46from nose.tools import *
from ex48 import lexicon
def test_directions():
assert_equal(lexicon.scan("north"), [("direction", "north")])
result = lexicon.scan("north south east")
assert_equal(result, [("direction", "north"),
("direction", "south"),
("direction", "east")])
def test_verbs():
assert_equal(lexicon.scan("go"), [("verb", "go")])
result = lexicon.scan("go kill eat")
assert_equal(result, [("verb", "go"),
("verb", "kill"),
("verb", "eat")])
def test_stops():
assert_equal(lexicon.scan("the"), [("stop", "the")])
result = lexicon.scan("the in of")
assert_equal(result, [("stop", "the"),
("stop", "in"),
("stop", "of")])
def test_nouns():
assert_equal(lexicon.scan("bear"), [("noun", "bear")])
result = lexicon.scan("bear princess")
assert_equal(result, [("noun", "bear"),
("noun", "princess")])
def test_numbers():
assert_equal(lexicon.scan("1234"), [("number", 1234)])
result = lexicon.scan("3 91234")
assert_equal(result, [("number", 3),
("number", 91234)])
def test_errors():
assert_equal(lexicon.scan("ASDFADFASDF"), [("error", "ASDFADFASDF")])
result = lexicon.scan("bear IAS princess")
assert_equal(result, [("noun", "bear"),
("error", "IAS"),
("noun", "princess")])
記住你要使用你的項(xiàng)目骨架來創(chuàng)建新項(xiàng)目,將這個(gè)測試用例寫下來(不許復(fù)制粘貼!),然后編寫你的掃描器,直至所有的測試都能通過。注意細(xì)節(jié)并確認(rèn)結(jié)果一切工作良好。
設(shè)計(jì)的技巧?
集中一次實(shí)現(xiàn)一個(gè)測試項(xiàng)目,盡量保持項(xiàng)目簡單,只要把你的 lexicon.py 詞匯表中所有的單詞放那里就可以了。不要修改輸入的單詞表,不過你需要?jiǎng)?chuàng)建自己的新列表,里邊包含你的語匯元組。另外,記得使用 in 關(guān)鍵字來檢查這些語匯列表,以確認(rèn)某個(gè)單詞是否在你的語匯表中。
加分習(xí)題?
改進(jìn)單元測試,讓它覆蓋到更多的語匯。
向語匯列表添加更多的語匯,并且更新單元測試代碼。
讓你的掃描器能夠識(shí)別任意大小寫的詞匯。更新你的單元測試以確認(rèn)其功能。
找出另外一種轉(zhuǎn)換為數(shù)字的方法。
我的解決方案用了 37 行代碼,你的是更長還是更短呢?
總結(jié)
以上是生活随笔為你收集整理的python输入print跳到documentation-习题 48: 更复杂的用户输入的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果iPhone 14 Pro能使用无边
- 下一篇: 独一无二的网名524个