python中是沒有switch語法的,我在練習的時候想使用相似switch的功能,搜索相關內容知道了使用字典能夠完成我想要的步驟。因而,開始動手。python
我使用的是python3,而且在練習使用tkinter模塊寫個小遊戲:乒乓球。測試階段,首先我敲入:canvas
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) 是函數的參數。oop
在測試成功後,我將這種方法移到了測試乒乓球和球拍反彈的文件中,這裏面有乒乓球的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行。這一行的內容我百思不得其解。spa
對於類方法的定義,全部類方法的第一個參數必須是一個self(名稱可變)參量,這個參量默認指向方法說在的類,便於方法內部相關內容的編寫。而在使用方法時,這個self參量是隱藏的,也就是說這個參量並不會對外可見。例如Paddle類中的move2left()函數(56行),在類方法定義中使用move2left()函數時,只須要寫self.move2left(),內部的self參數不須要寫。可是在69行上,我必需要在參數中加入self才行。重溫一下69行:指針
self.movement.get(event.keysym)(self)
這裏的self參數是move()方法中的默認參數self,指向所在的Paddle類,是一個函數指針。爲何要顯示地寫入這個參數呢?code
在字典movement中保存了兩個內容,分別是 move2left 函數和 move2right 函數的指針。那麼在使用 movement.get('Right') 時,至關於返回了 move2right 。那麼,movement.get('Right')() 這個代碼就至關於move2right() 這個函數的調用,最後的括號表示前面的內容是一個函數,不加的話運行確定是過不去的(不能用編譯兒,由於python是解釋性語言)。對於非類方法的函數,在函數定義時形參中沒有設定默認值的參數,在調用這個函數時,必需要在相應的形參位置上傳入實參,若是沒有傳入足夠多的實參,那麼Python解釋器就沒法正確運行這個函數從而報錯。對於類方法中的函數,由於Python解釋器對類的 」特別對待「,因此在調用類方法的時候,解釋器會自動 「跨過」 類方法中第一個默認指向自身類的指針的變量。blog
可是糾結的地方來了。在使用字典方法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!它不是一個類!好吧好吧,報錯吧那就!
結案。