python: 字典,類與 "switch"


  python中是沒有switch語法的,我在練習的時候想使用類似switch的功能,搜索相關內容知道了使用字典可以完成我想要的步驟。於是,開始動手。

  我使用的是python3,並且在練習使用tkinter模塊寫個小游戲:乒乓球。測試階段,首先我敲入:

from tkinter import *

從而加載tkinter模塊,並使用 * 使得在之后的代碼輸入中可以稍打一些代碼。在這之后,我構想在創建一個canvas類變量,並在上面畫一個矩形,通過左右方向鍵控制矩形移動從而模擬球拍。測試的完整代碼為:

 1 from tkinter import *
 2 
 3 
 4 def move2right(dis):
 5     canvas.move(1, dis, 0)
 6 
 7 
 8 def move2left(dis):
 9     canvas.move(1, -dis, 0)
10 
11 movement = {'Right': move2right, 'Left': move2left}
12 
13 
14 def move(event):
15     movement.get(event.keysym)(8)
16 
17 tk = Tk()
18 canvas = Canvas(tk, width=500, height=500)
19 canvas.pack()
20 canvas.create_rectangle(100, 100, 200, 120)
21 canvas.bind_all('<KeyPress-Right>', move)
22 canvas.bind_all('<KeyPress-Left>', move)
23 
24 tk.mainloop()

以上測試代碼的結果很順利:使用字典變量movement將兩個函數的指針存入字典中。21,22行綁定左右建的觸發事件給函數move(),因為bind_all()方法中傳遞的函數要有一個參數(event),在bind_all()方法內部會將其設置為一個event類,從而存儲觸發的事件內容,因為以上原因,我定義了move()函數來封裝movement.get()方法。15行中,.get()方法可以通過索引key獲取相應的value,event.keysym為bind_all()傳遞給move()函數的event類中的元素,是一個代表相應鍵盤按鍵的字符串(這里有效的是'Right' 和 'left')。15行最后的 (8) 是函數的參數。

  在測試成功后,我將這種方法移到了測試乒乓球和球拍反彈的文件中,這里面有乒乓球的Ball類和球拍Paddle類。先貼上最后可以運行的代碼:

 1 # !/usr/bin/env python3
 2 # -*-coding=utf-8-*-
 3 
 4 from tkinter import *
 5 import time
 6 
 7 
 8 class Ball:
 9     def __init__(self, canvas, color, paddle):
10         self.canvas = canvas
11         self.canvas_height = self.canvas.winfo_height()
12         self.canvas_width = self.canvas.winfo_width()
13         self.paddle = paddle
14         self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
15         self.canvas.move(self.id, 245, 100)
16         self.hit_bottom = False
17         self.x_pixel_of_one_step = 0  # 小球橫向速度
18         self.y_pixel_of_one_step = -3  # 小球縱向速度
19 
20     def hit_paddle(self, pos):
21         paddle_pos = self.canvas.coords(self.paddle.id)
22         if paddle_pos[0] <= (pos[0] + pos[2])/2.0 <= paddle_pos[2]:
23             if paddle_pos[1] <= pos[3] < paddle_pos[3] and self.y_pixel_of_one_step > 0:
24                 return True
25             if paddle_pos[1] < pos[1] <= paddle_pos[3] and self.y_pixel_of_one_step < 0:
26                 return True
27         return False
28 
29     def draw(self):
30         ball_pos = self.canvas.coords(self.id)  # 提取目前小球的位置
31         # 碰撞到上下邊反彈參數設置
32         if ball_pos[1] <= 0 or ball_pos[3] >= self.canvas_height:
33             self.y_pixel_of_one_step = -self.y_pixel_of_one_step
34         # 碰撞到左右邊反彈參數設置
35         if ball_pos[0] <= 0 or ball_pos[3] >= self.canvas_width:
36             self.x_pixel_of_one_step = -self.x_pixel_of_one_step
37         # 碰撞到球拍反彈參數設置
38         if self.hit_paddle(ball_pos):
39             self.y_pixel_of_one_step = -self.y_pixel_of_one_step
40         # 移動小球
41         self.canvas.move(self.id, self.x_pixel_of_one_step, self.y_pixel_of_one_step)
42 
43 
44 class Paddle:
45     def __init__(self, canvas, color):
46         self.canvas = canvas
47         self.canvas_height = canvas.winfo_height()
48         self.canvas_width = canvas.winfo_width()
49         self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color)
50         self.canvas.move(self.id, 250, 250)
51         self.canvas.bind_all('<KeyPress-Right>', self.move)
52         self.canvas.bind_all('<KeyPress-Left>', self.move)
53         self.x_pixel_of_one_step = 0
54         self.y_pixel_of_one_step = 0
55 
56     def move2left(self):
57         paddle_pos = self.canvas.coords(self.id)
58         if paddle_pos[0] >= 0:
59             self.canvas.move(self.id, -5, 0)
60 
61     def move2right(self):
62         paddle_pos = self.canvas.coords(self.id)
63         if paddle_pos[2] <= self.canvas_width:
64             self.canvas.move(self.id, 5, 0)
65 
66     movement = {'Right': move2right, 'Left': move2left}
67 
68     def move(self, event):
69         self.movement.get(event.keysym)(self)
70         # print(self.movement[event.keysym])
71 
72     def draw(self):
73         pass
74 
75 
76 tk = Tk()
77 tk.title('Fuck The Ping-Pang')
78 tk.resizable(0, 0)  # 限制畫布不能伸縮
79 tk.wm_attributes('-topmost', 1)
80 canvas = Canvas(tk,width=500, height=500, bd=0, highlightthickness=0)
81 # canvas = Canvas(tk, width=500, height=500)
82 canvas.pack()
83 tk.update()
84 paddle = Paddle(canvas, 'black')
85 ball = Ball(canvas, 'black', paddle)
86 
87 while True:
88     ball.draw()
89     tk.update_idletasks()
90     tk.update()
91     time.sleep(0.01)

一切正常,除了56~70行。這幾行正是我加入的剛剛所示的方法。我糾結的地方在第69行。這一行的內容我百思不得其解。

  對於類方法的定義,所有類方法的第一個參數必須是一個self(名稱可變)參量,這個參量默認指向方法說在的類,便於方法內部相關內容的編寫。而在使用方法時,這個self參量是隱藏的,也就是說這個參量並不會對外可見。例如Paddle類中的move2left()函數(56行),在類方法定義中使用move2left()函數時,只需要寫self.move2left(),內部的self參數不需要寫。但是在69行上,我必須要在參數中加入self才行。重溫一下69行:

 

self.movement.get(event.keysym)(self)

 

這里的self參數是move()方法中的默認參數self,指向所在的Paddle類,是一個函數指針。為什么要顯示地寫入這個參數呢?

  在字典movement中保存了兩個內容,分別是 move2left 函數和 move2right 函數的指針。那么在使用 movement.get('Right') 時,相當於返回了 move2right 。那么,movement.get('Right')() 這個代碼就相當於move2right() 這個函數的調用,最后的括號表示前面的內容是一個函數,不加的話運行肯定是過不去的(不能用編譯兒,因為python是解釋性語言)。對於非類方法的函數,在函數定義時形參中沒有設定默認值的參數,在調用這個函數時,必須要在相應的形參位置上傳入實參,如果沒有傳入足夠多的實參,那么Python解釋器就無法正確運行這個函數從而報錯。對於類方法中的函數,因為Python解釋器對類的 ”特別對待“,所以在調用類方法的時候,解釋器會自動 “跨過” 類方法中第一個默認指向自身類的指針的變量。

  但是糾結的地方來了。在使用字典方法get()時,get 方法僅僅返回字典中對應鍵(key)的值(value)。在這里返回的是move2left或者move2right函數的地址。解釋器運行到這里並從get()方法中出來后,得到了一個函數指針。然而此時解釋器已經不知道這個指針是否是類中的方法,它只知道這個函數指針所指向的函數需要一個參數self,所以在與后面的括號()結合並解釋成一個函數時,它需要相應個數的實參,所以此時不能忽略那個self參數的輸入。

  問題又來了,python中是隱藏數據類型的。並且在這個例子中,move2left 和 move2right 的self參量都沒有使用,這時我可不可以不傳入self 而傳入別的參數,例如: self.movement.get(event.keysym)(20)。雖然這樣是無意義的,但是與上一段python解釋器的行為並不相沖突。恩,結果倒是沒有懸念:運行出錯。運行出錯,那么原因基本只有一個:在解釋器將get()方法與后面的括號結合起來並解釋為函數時,解釋器將這個函數解釋為非類方法,將其作為普通函數,檢測它的參數傳遞個數與定義個數是否一致。在檢測參數一致后,程序將由函數指針進入這個函數,然后發現這個函數竟然是一個類方法!那么它就再檢查一次向函數傳遞的參數,這時候發現傳遞給函數的參數有一個—— 20,而這個類方法的定義的參數只有一個默認的self。解釋器接受這個事實,並把20賦值給self。因為腳本運行,程序繼續跑(例如之前按的是左鍵,那么跳入的是move2left()方法)。在move2left 方法中使用了self.canvas.move()方法,解釋器這時候發現,self等於20!它不是一個類!好吧好吧,報錯吧那就!

  結案。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM