原文:Learn Python the Hard Way, 5th Edition (Early Release)
譯者:飛龍
協議:CC BY-NC-SA 4.0
練習 30:假如
這是你將要輸入的下一個 Python 腳本,它向你介紹了if
語句。輸入這個代碼,確保它能夠完美運行,然后我們將看看你的練習是否有所收獲。
列表 30.1:ex30.py
1 people = 202 cats = 303 dogs = 15456 if people < cats:7 print("Too many cats! The world is doomed!")89 if people > cats:
10 print("Not many cats! The world is saved!")
11
12 if people < dogs:
13 print("The world is drooled on!")
14
15 if people > dogs:
16 print("The world is dry!")
17
18
19 dogs += 5
20
21 if people >= dogs:
22 print("People are greater than or equal to dogs.")
23
24 if people <= dogs:
25 print("People are less than or equal to dogs.")
26
27
28 if people == dogs:
29 print("People are dogs.")
你應該看到的內容
1 Too many cats! The world is doomed!
2 The world is dry!
3 People are greater than or equal to dogs.
4 People are less than or equal to dogs.
5 People are dogs.
dis()
它
在接下來的幾個練習中,我希望你運行dis()
在你正在學習的一些代碼上,以便更深入地了解它是如何工作的:
1 from dis import dis
2
3 dis('''
4 if people < cats:
5 print("Too many cats! The world is doomed!")
6 ''')
這不是你在編程時通常會做的事情。我只是希望你在這里這樣做,以便為你理解正在發生的事情提供另一種可能的方式。如果dis()
并沒有真正幫助你更好地理解代碼,那么隨意這樣做并忘記它。
要研究這個問題,只需將 Python 代碼放在這個dis()
輸出旁邊,然后嘗試識別與字節碼匹配的 Python 代碼行。
練習題
在這個練習中,試著猜測if
語句是什么以及它的作用是什么。在繼續下一個練習之前,嘗試用自己的話回答這些問題:
-
你認為
if
對下面的代碼有什么影響? -
為什么
if
下面的代碼需要縮進四個空格? -
如果沒有縮進會發生什么?
-
你能否在
if
語句中放入來自練習 28 的其他布爾表達式?試一試。 -
如果你改變
people
、cats
和dogs
的初始值會發生什么?
常見學生問題
+=
是什么意思? 代碼x += 1
與x = x + 1
相同,但輸入更少。你可以稱之為“遞增運算符”。對于-=
和許多其他表達式,你以后會學到的也是一樣。
練習 31:否則和如果
在上一個練習中,你解決了一些if 語句
,然后試圖猜測它們是什么以及它們如何工作。在學習更多之前,我將通過回答你在學習練習中提出的問題來解釋一切。你做了學習練習,對吧?
-
你認為
if
對其下面的代碼有什么影響?if 語句
在代碼中創建了所謂的“分支”。這有點像那些選擇你自己冒險的書,如果你做出一個選擇,就會被要求翻到一頁,如果你選擇另一條路,就會翻到另一頁。if 語句
告訴你的腳本,“如果這個布爾表達式為真,則運行其下的代碼;否則跳過它。” -
為什么
if
下面的代碼需要縮進四個空格?在一行的末尾加上冒號是告訴 Python 你將創建一個新的代碼“塊”,然后縮進四個空格告訴 Python 哪些代碼行在該塊中。這與你在本書的前半部分創建函數時所做的事情完全相同。 -
如果沒有縮進會發生什么?如果沒有縮進,你很可能會產生 Python 錯誤。Python 希望你在以
:
(冒號)結尾的行之后縮進一些東西。 -
你能把練習 28 中的其他布爾表達式放在
if 語句
中嗎?試試看。是的,你可以,而且它們可以盡可能復雜,盡管非常復雜的東西通常是不好的風格。 -
如果更改
people
,cats
和dogs
的初始值會發生什么?因為你正在比較數字,如果更改數字,不同的if 語句
將評估為True
,并且其下的代碼塊將運行。回去放入不同的數字,看看你是否能在腦海中弄清楚哪些代碼塊將運行。
將我的答案與你的答案進行比較,并確保你真正理解代碼“塊”的概念。這對于你做下一個練習很重要,其中你將編寫所有可以使用的if 語句
的部分。
將這個輸入并使其工作。
列表 31.1:ex31.py
1 people = 302 cars = 403 trucks = 15456 if cars > people:7 print("We should take the cars.")8 elif cars < people:9 print("We should not take the cars.")
10 else:
11 print("We can't decide.")
12
13 if trucks > cars:
14 print("That's too many trucks.")
15 elif trucks < cars:
16 print("Maybe we could take the trucks.")
17 else:
18 print("We still can't decide.")
19
20 if people > trucks:
21 print("Alright, let's just take the trucks.")
22 else:
23 print("Fine, let's stay home then.")
你應該看到什么
1 We should take the cars.
2 Maybe we could take the trucks.
3 Alright, let's just take the trucks.
dis()
它
我們現在到了一個dis()
有點太復雜的地步。讓我們只選擇一個代碼塊來學習:
1 from dis import dis23 dis('''4 if cars > people:5 print("We should take the cars.")6 elif cars < people:7 print("We should not take the cars.")8 else:9 print("We can't decide.")
10 ''')
我認為學習這個最好的方法是將 Python 代碼放在dis()
輸出旁邊,嘗試將 Python 代碼的行與其字節碼匹配。如果你能做到這一點,那么你將遠遠領先于許多甚至不知道 Python 有dis()
的 Python 程序員。
如果你搞不清楚,不要擔心。這一切都是關于盡可能推動你的知識,以找到理解 Python 的新方法。
學習練習
-
試著猜猜
elif
和else
在做什么。 -
更改
cars
,people
和trucks
的數字,然后跟蹤每個if 語句
,看看將打印出什么。 -
嘗試一些更復雜的布爾表達式,比如
cars > people or trucks < cars
。 -
在每行上方寫出該行的英文描述。
常見學生問題
如果多個 elif
塊都為 True
會發生什么? Python 從頂部開始運行第一個為True
的塊,因此只會運行第一個。
練習 32:做決策
在這本書的前半部分,你主要只是打印出一些稱為“函數”的東西,但一切基本上都是直線的。你的腳本從頂部開始運行,一直到底部結束。如果你創建了一個函數,你可以稍后運行該函數,但它仍然沒有你真正需要做出決策的分支。現在你有了 if
、else
和 elif
,你可以開始編寫決策性的腳本了。
在上一個腳本中,你列出了一組簡單的測試,詢問一些問題。在這個腳本中,你將詢問用戶問題,并根據他們的答案做出決定。編寫這個腳本,然后多玩一下,弄清楚它的運行方式。
代碼清單 32.1: ex32.py
1 print("""You enter a dark room with two doors.2 Do you go through door #1 or door #2?""")34 door = input("> ")56 if door == "1":7 print("There's a giant bear here eating a cheese cake.")8 print("What do you do?")9 print("1\. Take the cake.")
10 print("2\. Scream at the bear.")
11
12 bear = input("> ")
13
14 if bear == "1":
15 print("The bear eats your face off. Good job!")
16 elif bear == "2":
17 print("The bear eats your legs off. Good job!")
18 else:
19 print(f"Well, doing {bear} is probably better.")
20 print("Bear runs away.")
21
22 elif door == "2":
23 print("You stare into the endless abyss at Cthulhu's retina.")
24 print("1\. Blueberries.")
25 print("2\. Yellow jacket clothespins.")
26 print("3\. Understanding revolvers yelling melodies.")
27
28 insanity = input("> ")
29
30 if insanity == "1" or insanity == "2":
31 print("Your body survives powered by a mind of jello.")
32 print("Good job!")
33 else:
34 print("The insanity rots your eyes into a pool of muck.")
35 print("Good job!")
36
37 else:
38 print("You stumble around and fall on a knife and die. Good job!")
這里的關鍵點是,現在你正在將if-statements
放在if-statements
內部作為可以運行的代碼。這是非常強大的,可以用來創建“嵌套”決策,其中一個分支導致另一個分支。
確保你理解了if
-statements 中嵌套if
-statements 的概念。實際上,做一些練習來真正掌握它。
你應該看到的結果
這是我玩這個小冒險游戲的情況。我表現得不太好。
1 You enter a dark room with two doors.
2 Do you go through door #1 or door #2?
3 > 1
4 There's a giant bear here eating a cheese cake.
5 What do you do?
6 1\. Take the cake.
7 2\. Scream at the bear.
8 > 2
9 The bear eats your legs off. Good job!
dis()
它
這次沒有 *dis()*
It 部分,因為這段代碼太復雜了,難以理解,但如果你感覺幸運的話,可以嘗試一下:
1 from dis import dis23 if door == "1":4 print("1")5 bear = input("> ")6 if bear == "1":7 print("bear 1")8 elif bear == "2":9 print("bear 2")
10 else:
11 print("bear 3")
這將產生大量需要分析的代碼,但盡力而為。過一段時間會變得無聊,但也有助于理解 Python 的工作原理。再次強調,如果這讓你困惑,可以先跳過,以后再嘗試。
練習
-
制作游戲的新部分,并改變人們可以做出的決定。在游戲變得荒謬之前盡可能擴展游戲。
-
編寫一個全新的游戲。也許你不喜歡這個,那就自己創造一個。這是你的電腦;做你想做的事情。
常見學生問題
你能用一系列 if-else
組合替換 elif
嗎? 在某些情況下可以,但這取決于每個 if/else
的編寫方式。這也意味著 Python 將檢查每個 if-else
組合,而不像 if-elif-else
那樣只檢查第一個為假的條件。嘗試創建一些來了解差異。
如何判斷一個數字是否在一系列數字范圍內? 你有兩個選擇:使用 0 < x
< 10
或 1 <= x < 10
—這是經典的表示法—或使用 x in range(1, 10)
。
如果我想在 if-elif-else
塊中增加更多選項怎么辦? 為每個可能的選擇添加更多 elif
塊。
練習 33:循環和列表
現在你應該能夠編寫一些更有趣的程序了。如果你一直在跟進,你應該意識到現在你可以將所有其他學到的東西與if-statements
和布爾表達式結合起來,使你的程序做一些聰明的事情。
然而,程序也需要快速地執行重復的事情。在這個練習中,我們將使用for-loop
來構建和打印各種列表。當你做這個練習時,你會開始明白它們是什么。我現在不會告訴你。你必須自己弄清楚。
在使用for-loop
之前,你需要一種方法來存儲循環的結果。最好的方法是使用lists
。Lists
正是它們的名字所說的:一個按照從頭到尾順序組織的東西的容器。這并不復雜;你只需要學習一種新的語法。首先,這是如何創建lists
的:
1 hairs = ['brown', 'blond', 'red']
2 eyes = ['brown', 'blue', 'green']
3 weights = [1, 2, 3, 4]
你用[
(左括號)開始list
,這個“打開”list
。然后你用逗號分隔每個你想要放入列表的項目,類似于函數參數。最后,用]
(右括號)結束列表以表示它的結束。然后 Python 將獲取這個列表及其所有內容,并將它們分配給變量。
警告!
這就是對于不能編碼的人來說變得棘手的地方。你的大腦被教導世界是平的。還記得在上一個練習中你是如何在if-statements
內部放置if-statements
的嗎?那可能讓你的大腦感到疼痛,因為大多數人不會考慮如何在“嵌套”事物內部放置事物。在編程中,嵌套結構隨處可見。你會發現調用其他函數的函數,這些函數有帶有列表的if-statements
,列表內部還有列表。如果你看到這樣的結構而無法理解,拿出一支鉛筆和紙,逐步手動分解,直到你理解為止。
現在我們將使用一些for-loops
來構建一些列表并將它們打印出來:
列表 33.1:ex33.py
1 the_count = [1, 2, 3, 4, 5]2 fruits = ['apples', 'oranges', 'pears', 'apricots']3 change = [1, 'pennies', 2, 'dimes', 3, 'quarters']45 # this first kind of for-loop goes through a list6 for number in the_count:7 print(f"This is count {number}")89 # same as above
10 for fruit in fruits:
11 print(f"A fruit of type: {fruit}")
12
13 # also we can go through mixed lists too
14 for i in change:
15 print(f"I got {i}")
16
17 # we can also build lists, first start with an empty one
18 elements = []
19
20 # then use the range function to do 0 to 5 counts
21 for i in range(0, 6):
22 print(f"Adding {i} to the list.")
23 # append is a function that lists understand
24 elements.append(i)
25
26 # now we can print them out too
27 for i in elements:
28 print(f"Element was: {i}")
你應該看到的內容
1 This is count 12 This is count 23 This is count 34 This is count 45 This is count 56 A fruit of type: apples7 A fruit of type: oranges8 A fruit of type: pears9 A fruit of type: apricots
10 I got 1
11 I got pennies
12 I got 2
13 I got dimes
14 I got 3
15 I got quarters
16 Adding 0 to the list.
17 Adding 1 to the list.
18 Adding 2 to the list.
19 Adding 3 to the list.
20 Adding 4 to the list.
21 Adding 5 to the list.
22 Element was: 0
23 Element was: 1
24 Element was: 2
25 Element was: 3
26 Element was: 4
27 Element was: 5
dis()
它
這次讓我們簡單點,只看看 Python 如何執行for-loop
:
1 from dis import dis
2
3 dis('''
4 for number in the_count:
5 print(number)
6 ''')
這次我將在這里重現輸出,以便我們可以分析它:
1 0 LOAD_NAME 0 (the_count) # get the count list2 2 GET_ITER # start iteration3 4 FOR_ITER 6 (to 18) # for-loop jump to 184 6 STORE_NAME 1 (number) # create number variable56 8 LOAD_NAME 2 (print) # load print()7 10 LOAD_NAME 1 (number) # load number8 12 CALL_FUNCTION 1 # call print()9 14 POP_TOP # clean stack
10 16 JUMP_ABSOLUTE 2 (to 4) # jump back to FOR_ITER at 4
11
12 18 LOAD_CONST 0 (None) # jump here when FOR_ITER done
13 20 RETURN_VALUE
在FOR_ITER
操作中我們看到了一個新的東西。這個操作通過以下步驟使for-loop
工作:
-
調用
the_count.__next__()
-
如果
the_count
中沒有更多元素,則跳轉到 18 -
如果仍然有元素,則繼續執行
-
STORE_NAME
然后將the_count.__next__()
的結果賦給名為number
的變量
這就是for-loop
實際上所做的一切。它主要是一個單字節代碼FOR_ITER
,結合其他幾個來遍歷列表。
學習練習
-
看看你如何使用了
range
。查閱range
函數以了解它。 -
在第 22 行完全避免了那個
for-loop
,直接將range(0,6)
賦給elements
,你能做到嗎? -
查找關于列表的 Python 文檔并閱讀它們。除了
append
之外,你還可以對列表進行哪些操作?
常見學生問題
如何創建二維(2D)列表? 就像這樣的列表中嵌套列表:[[1,2,3],[4,5,6]]
列表和數組不是一回事嗎? 這取決于語言和實現。在傳統術語中,列表與數組非常不同,因為它們的實現方式不同。在 Ruby 中,它們稱之為“數組”。在 Python 中,它們稱之為“列表”。現在只需稱之為“列表”,因為這是 Python 的稱呼。
為什么 for 循環能夠使用尚未定義的變量? 變量在循環開始時由 for 循環
定義,每次迭代時將其初始化為當前循環元素。
為什么 for i in range(1, 3):
只循環兩次而不是三次? range()
函數只生成從第一個到最后一個的數字,不包括最后一個。因此,在上述情況下它在兩處停止,而不是三處。這實際上是這種循環最常見的方式。
elements.append()
做什么?它簡單地將元素附加到列表的末尾。打開 Python shell 并嘗試用自己創建的列表做幾個示例。每當遇到這樣的情況時,總是嘗試在 Python shell 中進行交互操作。
練習 34:While 循環
現在讓我們用一個新的循環完全震驚你,while-loop
。while-loop
會持續執行其下的代碼塊,只要布爾表達式為True
。
等等,你一直跟上術語了嗎?如果我們寫一行并以:
(冒號)結尾,那告訴 Python 開始一個新的代碼塊?然后我們縮進,這就是新代碼。這一切都是關于構建你的程序,讓 Python 知道你的意圖。如果你沒有理解這個概念,那就回去多做一些關于if
語句、函數和for
循環的工作,直到你理解為止。
后面我們會有一些練習,訓練你的大腦閱讀這些結構,類似于我們如何將布爾表達式烙印在你的大腦中。
回到while-loop
。它們的作用就像一個if
語句的測試,但不同于只運行代碼塊一次,它們會跳回到while
所在的“頂部”,并重復。while
循環會一直運行,直到表達式為False
。
while
循環的問題在于:有時它們不會停止。如果你的意圖只是一直循環直到宇宙的盡頭,那么這很好。否則,你幾乎總是希望你的循環最終會結束。
為了避免這些問題,有一些規則需要遵循:
-
確保你謹慎使用
while
循環。通常for
循環更好。 -
檢查你的
while
語句,并確保布爾測試最終會變為False
。 -
如果有疑問,在
while
循環的頂部和底部打印出你的測試變量,看看它在做什么。
在這個練習中,你將學習while
循環,并在進行以下三個檢查時使用它們:
列表 34.1: ex34.py
1 i = 02 numbers = []34 while i < 6:5 print(f"At the top i is {i}")6 numbers.append(i)78 i = i + 19 print("Numbers now: ", numbers)
10 print(f"At the bottom i is {i}")
11
12
13 print("The numbers: ")
14
15 for num in numbers:
16 print(num)
你應該看到的結果
1 At the top i is 02 Numbers now: [0]3 At the bottom i is 14 At the top i is 15 Numbers now: [0, 1]6 At the bottom i is 27 At the top i is 28 Numbers now: [0, 1, 2]9 At the bottom i is 3
10 At the top i is 3
11 Numbers now: [0, 1, 2, 3]
12 At the bottom i is 4
13 At the top i is 4
14 Numbers now: [0, 1, 2, 3, 4]
15 At the bottom i is 5
16 At the top i is 5
17 Numbers now: [0, 1, 2, 3, 4, 5]
18 At the bottom i is 6
19 The numbers:
20 0
21 1
22 2
23 3
24 4
25 5
dis()
它
在我們代碼之游戲的最終“支線任務”中,你將使用dis()
來分析while-loop
的工作原理:
1 from dis import dis
2
3 dis('''
4 i = 0
5 while i < 6:
6 i = i + 1
7 ''')
你已經看到了大部分這些字節碼,所以現在輪到你去弄清楚這個dis()
輸出與 Python 有什么關系了。記住你可以在文檔的末尾的[dis()](https://docs.python.org/3/library/dis.xhtml#python-bytecode-instructions)
文檔中查找所有的字節碼。祝你好運!
學習練習
-
將這個
while-loop
轉換為一個可以調用的函數,并用一個變量替換測試中的6
(i < 6
)。 -
使用這個函數來重寫腳本以嘗試不同的數字。
-
在函數參數中添加另一個變量,你可以傳入它,以便你可以更改第 8 行的
+ 1
,這樣你就可以改變增量是多少。 -
再次重寫腳本以使用這個函數,看看會有什么影響。
-
重寫它以使用
for-loops
和range
。你還需要在中間保留增量器嗎?如果不去掉它會發生什么?
如果在任何時候你這樣做時出現問題(很可能會),只需按住CTRL
并按下c
(CTRL-c
),程序就會中止。
常見學生問題
for
-循環和**while
-循環有什么區別?for
-循環只能在“集合”上進行迭代(循環)。while
-循環可以進行任何類型的迭代(循環)。然而,while
-循環更難正確使用,通常可以用for
**-循環完成許多任務。
循環很難。我該如何理解它們? 人們不理解循環的主要原因是因為他們無法跟隨代碼的“跳躍”。當循環運行時,它會執行其代碼塊,最后跳回頂部。為了可視化這一點,在循環中到處放置print
語句,打印出 Python 在循環中運行的位置以及這些點上變量的設置。在循環之前、頂部、中間和底部編寫print
行。研究輸出并嘗試理解正在進行的跳躍。
練習 35:分支和函數
你已經學會了if 語句
、函數和列表。現在是時候挑戰你的思維了。把這個輸入進去,看看你能否弄清楚它在做什么:
列表 35.1: ex35.py
1 from sys import exit23 def gold_room():4 print("This room is full of gold. How much do you take?")56 choice = input("> ")7 if "0" in choice or "1" in choice:8 how_much = int(choice)9 else:
10 dead("Man, learn to type a number.")
11
12 if how_much < 50:
13 print("Nice, you're not greedy, you win!")
14 exit(0)
15 else:
16 dead("You greedy bastard!")
17
18
19 def bear_room():
20 print("There is a bear here.")
21 print("The bear has a bunch of honey.")
22 print("The fat bear is in front of another door.")
23 print("How are you going to move the bear?")
24 bear_moved = False
25
26 while True:
27 choice = input("> ")
28
29 if choice == "take honey":
30 dead("The bear looks at you then slaps your face off.")
31 elif choice == "taunt bear" and not bear_moved:
32 print("The bear has moved from the door.")
33 print("You can go through it now.")
34 bear_moved = True
35 elif choice == "taunt bear" and bear_moved:
36 dead("The bear gets pissed off and chews your leg off.")
37 elif choice == "open door" and bear_moved:
38 gold_room()
39 else:
40 print("I got no idea what that means.")
41
42
43 def cthulhu_room():
44 print("Here you see the great evil Cthulhu.")
45 print("He, it, whatever stares at you and you go insane.")
46 print("Do you flee for your life or eat your head?")
47
48 choice = input("> ")
49
50 if "flee" in choice:
51 start()
52 elif "head" in choice:
53 dead("Well that was tasty!")
54 else:
55 cthulhu_room()
56
57
58 def dead(why):
59 print(why, "Good job!")
60 exit(0)
61
62 def start():
63 print("You are in a dark room.")
64 print("There is a door to your right and left.")
65 print("Which one do you take?")
66
67 choice = input("> ")
68
69 if choice == "left":
70 bear_room()
71 elif choice == "right":
72 cthulhu_room()
73 else:
74 dead("You stumble around the room until you starve.")
75
76
77 start()
你應該看到什么
這是我玩游戲的樣子:
1 You are in a dark room.2 There is a door to your right and left.3 Which one do you take?4 > left5 There is a bear here.6 The bear has a bunch of honey.7 The fat bear is in front of another door.8 How are you going to move the bear?9 > taunt bear
10 The bear has moved from the door.
11 You can go through it now.
12 > open door
13 This room is full of gold. How much do you take?
14 > 1000
15 You greedy bastard! Good job!
學習練習
-
繪制游戲地圖以及你如何在其中流動。
-
修復所有錯誤,包括拼寫錯誤。
-
為你不理解的函數寫注釋。
-
添加更多內容到游戲中。你能做些什么來簡化和擴展它?
-
gold_room
有一種奇怪的方式讓你輸入一個數字。這種方式存在哪些錯誤?你能比我寫的更好嗎?看看int()
的工作原理會有提示。
常見學生問題
救命!這個程序怎么運行的!? 當你在理解一段代碼時遇到困難時,只需在每一行上面寫一個英文注釋,解釋該行的作用。保持你的評論簡短并與代碼相似。然后要么畫出代碼的工作原理,要么寫一段描述它的段落。如果你這樣做,你就會理解它。
為什么你寫了 while True
? 這會造成一個無限循環。
exit(0)
的作用是什么? 在許多操作系統上,一個程序可以通過 exit(0)
中止,傳入的數字將指示是否有錯誤。如果你使用 exit(1)
,那么就會有一個錯誤,但 exit(0)
將是一個良好的退出。它與正常的布爾邏輯相反(0==False
)的原因是你可以使用不同的數字來指示不同的錯誤結果。你可以使用 exit(100)
來表示不同的錯誤結果,而不同于 exit(2)
或 exit(1)
。
為什么 input()
有時寫成 input('> ')
? input
的參數是一個字符串,它應該在獲取用戶輸入之前打印作為提示。
練習 36:設計和調試
現在你已經了解了if
語句,我將給你一些關于for
循環和while
循環的規則,這將幫助你避免麻煩。我還會給你一些關于調試的提示,這樣你就可以找出程序中的問題。最后,你將設計一個類似于上一個練習但有些不同的小游戲。
從想法到可運行的代碼
有一個簡單的過程任何人都可以遵循,將你的想法轉化為代碼。這不是唯一的過程,但對許多人來說效果很好。在你開發自己的個人過程之前,使用這個過程。
-
以你理解的任何形式將你的想法表達出來。你是作家嗎?那就寫一篇關于你的想法的文章。你是藝術家或設計師嗎?那就畫出用戶界面。你喜歡圖表嗎?看看序列圖,這是編程中最有用的圖之一。
-
為你的代碼創建一個文件。是的,信不信由你,這是一個重要的步驟,大多數人都會遇到困難。如果你想不出一個名字,就隨便挑一個吧。
-
用簡單的英語(或者你最容易理解的語言)寫下你的想法的描述作為注釋。
-
從頂部開始,將第一個注釋轉換為“偽代碼”,這有點像 Python,但你不用在意語法。
-
將那個“偽代碼”轉換為真正的 Python 代碼,并不斷運行你的文件,直到這段代碼實現了你的注釋所說的。
-
重復這個過程,直到你將所有的注釋轉換為 Python 代碼。
-
退一步,審查你的代碼,然后刪除它。你不必一直這樣做,但如果你養成丟棄第一個版本的習慣,你將獲得兩個好處:
a. 你的第二個版本幾乎總是比第一個版本好。
b. 你向自己確認這不僅僅是愚蠢的運氣。你確實能編寫代碼。這有助于應對冒名頂替綜合癥和增強自信。
讓我們用一個簡單的問題“創建一個簡單的華氏度到攝氏度轉換器”來做一個例子。第一步,我會寫出我對轉換的了解:
C 等于 (F - 32 ) / 1.8。我應該詢問用戶輸入 F,然后打印出 C。
一個非常基本的數學公式是理解問題的簡單方法。第二步,我寫下描述我的代碼應該做什么的注釋:
1 # ask the user for the F
2 # convert it to a float()
3 # C = (F - 32) / 1.8
4 # print C to the user
一旦我有了這個,我會用偽代碼“填空”。我只會做第一行,這樣你就可以完成這個:
1 # ask the user for the F
2 F = input(?)
3
4 # convert it to a float()
5 # C = (F - 32) / 1.8
6 # print C to the user
注意,我故意懶惰,沒有正確地編寫語法,這就是偽代碼的要點。一旦我有了這個,就將其轉換為正確的 Python 代碼:
1 # ask the user for the F
2 F = input("C? ")
3
4 # convert it to a float()
5 # C = (F - 32) / 1.8
6 # print C to the user
運行它! 你應該不斷地運行你的代碼。如果你輸入了超過幾行,只需刪除它們,重新開始。這樣會容易得多。
現在這些行起作用了,我繼續下一個注釋并重復這個過程,直到我將所有的注釋轉換成 Python。當我的腳本最終工作時,我會刪除它并使用我所知道的重新編寫它。也許這一次我直接寫 Python,或者我再次重復這個過程。這樣做會讓我確認自己實際上是可以做到的。這不僅僅是愚蠢的運氣。
這是一個專業的過程嗎?
你可能會認為這個過程不實用或不專業。我認為,當你剛開始時,你需要不同于那些編程時間很長的人所需的工具。我可以坐下來想一個點子然后編碼,但我已經從事專業編程的時間比你活了的時間還長。然而,在我的腦海中,這基本上是我遵循的過程。我只是在腦海中迅速地做這個過程,而你必須在外部練習直到內化。
當我卡住或者在學習一門新語言時,我會使用這個過程。如果我不懂一門語言但知道我想做什么,那么我通常可以寫注釋然后慢慢將其轉換為代碼,這也教會我那種語言。我和你之間唯一的區別是,由于多年的訓練,我做得更快。
關于“X/Y”非問題
一些專業人士聲稱,這個過程會讓學生患上一種奇怪的疾病,稱為“X/Y 問題”。他們將 X/Y 問題描述為“有人想做 X,但只知道如何做 Y,所以他們請求幫助如何做 Y。” X/Y 問題的問題在于它批評了那些簡單學習編程的人,并沒有提出解決方案。對于“X/Y 問題的討厭者”,解決方案似乎是“已經知道答案”,因為如果他們知道如何做 X,他們就不會去煩惱 Y。這種信念的虛偽之處在于所有討厭這種問題的人都經歷過這個階段,提出過這些完全相同的“X/Y”問題。
另一個問題是,他們在責備你的糟糕文檔。經典的例子來自 X/Y 問題的原始描述:
1 <n00b> How can I echo the last three characters in a filename?23 <feline> If they're in a variable: echo ${foo: -3}4 <feline> Why 3 characters? What do you REALLY want?5 <feline> Do you want the extension?67 <n00b> Yes.89 <feline> Then ASK FOR WHAT YOU WANT!
10 <feline> There's no guarantee that every filename will
11 have a three-letter extension,
12 <feline> so blindly grabbing three characters does not
13 solve the problem.
14 <feline> echo ${foo##*.}
首先,這個feline
人實際上在一個專門回答問題的 IRC 頻道里大聲責罵某人提問。“要求你想要的東西!”第二個問題是,他們的解決方案是我——一個有幾十年經驗的 bash 和 Linux 專業人士——每次都要查找的東西。這是 bash 中最糟糕文檔化、最不可用的功能之一。一個初學者如何能預先知道他們應該使用一些復雜的“dollar brace name pound pound asterisk dot brace”操作?如果在線有簡單的文檔解釋如何做這個操作,這個人很可能不會提出這個問題。如果 bash 實際上有一個基本功能來執行這個每個人都需要的非常常見的操作,那將更好。
當涉及“X/Y 問題”時,這實際上只是一個借口,用來責罵初學者是初學者。每個聲稱討厭這個問題的人要么根本不寫代碼,要么絕對在學習編程時確實做過這樣的事情。這就是學習編程的方式。您遇到問題并通過學習如何實現解決方案來摸索解決方案。因此,如果遇到像<feline>
這樣的人,只需忽略他們。他們只是借口找個人發火并感覺自己更優越。
此外,您會注意到在上一個對話中,沒有一個人要求看代碼。如果<n00b>
只是展示了他們的代碼,那么<feline>
就可以推薦更好的方法來解決問題。問題解決了。我是說,假設<feline>
實際上能夠編寫代碼,而不只是在 IRC 中等待著攻擊毫無戒備的初學者提問。
if 語句規則
-
每個
if
語句必須有一個else
。 -
如果
else
部分永遠不應該運行,因為這沒有意義,那么你必須在else
中使用一個 die 函數,打印出錯誤消息并終止程序,就像我們在之前的練習中所做的那樣。這將找到許多錯誤。 -
永遠不要嵌套超過兩層的
if
語句,并始終嘗試將其保持一層。 -
將
if
語句視為段落,其中每個if-elif-else
組合就像一組句子。在其前后放置空行。 -
您的布爾測試應該簡單。如果它們復雜,將它們的計算移到函數中的變量中,并為變量使用一個好的名稱。
如果您遵循這些簡單的規則,您將開始寫出比大多數程序員更好的代碼。回到上一個練習,看看我是否遵循了所有這些規則。如果沒有,請糾正我的錯誤。
警告!
在現實生活中永遠不要成為規則的奴隸。在訓練過程中,您需要遵循這些規則以增強思維能力,但在現實生活中,有時這些規則只是愚蠢的。如果您認為某個規則很愚蠢,請嘗試不使用它。
循環規則
-
僅在需要永久循環時才使用
while
循環,這意味著可能永遠不會用到。這僅適用于 Python;其他語言不同。 -
對于所有其他類型的循環,請使用
for
循環,特別是在需要循環的事物數量是固定或有限的情況下。
調試提示
-
不要使用“調試器”。調試器就像對生病的人進行全身掃描一樣。您不會得到任何具體有用的信息,而會發現許多無用且令人困惑的信息。
-
調試程序的最佳方法是使用
print
打印出程序中變量的值,以查看它們出錯的位置。 -
確保程序的各個部分在編寫時能夠正常工作。不要在嘗試運行之前編寫大量的代碼文件。少寫一點,運行一點,修復一點。
作業
現在編寫一個類似于我在上一個練習中創建的游戲。它可以是你想要的任何類型的游戲,但風格相同。花一周的時間讓它盡可能有趣。在學習練習中,盡可能使用列表、函數和模塊(還記得練習 13 中的那些嗎?),并找到盡可能多的新的 Python 片段來使游戲運行。
在開始編碼之前,你必須為你的游戲繪制一張地圖。在編碼之前,先在紙上創建玩家必須經過的房間、怪物和陷阱。
有了地圖后,嘗試著編寫代碼。如果在地圖中發現問題,那就調整它,使代碼與之匹配。
在軟件開發中,最好的方法是像這樣分成小塊:
-
在一張紙上或一張索引卡上,寫下你需要完成的任務列表,以完成軟件開發。這就是你的待辦事項清單。
-
從你的清單中選擇最容易的任務。
-
在你的源文件中寫下英文注釋,作為你在代碼中如何完成這個任務的指南。
-
在英文注釋下面寫一些代碼。
-
快速運行你的腳本,看看代碼是否有效。
-
保持在寫一些代碼、運行測試并修復直到它有效的循環中工作。
-
將這個任務從你的清單上劃掉,然后選擇下一個最容易的任務并重復。
這個過程將幫助你以一種系統和一致的方式來開發軟件。在工作時,通過刪除你實際不需要的任務并添加你需要的任務來更新你的清單。
練習 37:符號復習
現在是時候復習你所知道的符號和 Python 關鍵字,并嘗試在接下來的幾節課中學習更多。我已經列出了所有重要的 Python 符號和關鍵字。
在這節課中,首先嘗試從記憶中寫出每個關鍵字的作用。接下來,在網上搜索它們,看看它們真正的作用。這可能很困難,因為有些很難搜索,但無論如何都要嘗試。
如果你從記憶中記錯了其中一個,就制作一張正確定義的索引卡,嘗試“糾正”你的記憶。
最后,在一個小的 Python 程序中使用這些中的每一個,或者盡可能多地完成。目標是找出符號的作用,確保你理解正確,如果不正確就糾正,然后使用它來牢記。
關鍵字
數據類型
對于數據類型,寫出每種數據類型的組成部分。例如,對于字符串,寫出如何創建一個字符串。對于數字,寫出一些數字。
字符串轉義序列
對于字符串轉義序列,將它們用在字符串中,確保它們執行你認為的操作。
舊式字符串格式
對于字符串格式也是一樣:在一些字符串中使用它們,以了解它們的作用。
舊版 Python 2 代碼使用這些格式化字符來實現 f-strings 的功能。嘗試它們作為替代方案。
運算符
其中一些可能對你來說很陌生,但無論如何都要查找它們。找出它們的作用,如果你仍然無法弄清楚,就留到以后再看。
大約花一周的時間,但如果你更快完成,那就太好了。重點是嘗試覆蓋所有這些符號,并確保它們牢記在你的腦海中。同樣重要的是找出你不知道的東西,這樣你就可以以后修復它。
閱讀代碼
現在找一些 Python 代碼來閱讀。你應該閱讀任何你能找到的 Python 代碼,并嘗試竊取你發現的想法。你實際上應該有足夠的知識來閱讀,但也許不理解代碼的作用。這節課教你如何應用你學到的東西來理解別人的代碼。
首先,打印出你想要理解的代碼。是的,打印出來,因為你的眼睛和大腦更習慣于閱讀紙張而不是電腦屏幕。確保每次打印幾頁。
其次,瀏覽你的打印輸出,并對以下內容做筆記:
-
函數及其作用。
-
每個變量首次被賦值的地方。
-
程序中不同部分中具有相同名稱的任何變量。這些以后可能會有麻煩。
-
沒有
else
子句的if
語句。它們正確嗎? -
任何可能不會結束的
while
循環。 -
任何你因為任何原因無法理解的代碼部分。
第三,一旦你標記了所有這些,嘗試通過寫注釋來向自己解釋。解釋函數,它們如何被使用,涉及哪些變量以及你可以找出這段代碼的任何內容。
最后,在所有困難的部分,逐行追蹤每個變量的值,逐個函數地。實際上,再做一份打印輸出,并在邊緣寫下你需要“追蹤”的每個變量的值。
一旦你對代碼的功能有了很好的理解,回到電腦上再次閱讀它,看看是否能發現新的東西。繼續找到更多的代碼并這樣做,直到你不再需要打印輸出為止。
學習練習
-
找出“流程圖”是什么,并畫幾個。
-
如果你在閱讀代碼時發現錯誤,請嘗試修復它們,并將更改發送給作者。
-
當你不使用紙張時的另一種技巧是在代碼中用
#
注釋來記錄你的筆記。有時,這些注釋可能成為實際的注釋,幫助下一個人。
常見學生問題
我該如何在網上搜索這些內容? 只需在你想要查找的任何內容前加上“python3”。例如,要查找yield
,搜索python3 yield
。