标签:
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!它不是一个类!好吧好吧,报错吧那就!
结案。
标签:
原文地址:http://www.cnblogs.com/bolgofzjr/p/4883611.html