Why doesn't this monkey patch work for python turtle? - python

So I'm trying to monkey patch the onkey function in turtle so it calls a function with the button hit instead of just calling it with no arguments. I'm using the string "tester" to see if it works, but it looks like the original functions never got changes. Can someone explain what I'm doing wrong?
from threading import Thread
from time import sleep
from turtle import *
def NEWonkeypress(self, fun, key=None):
if fun is None:
if key in self._keys:
self._keys.remove(key)
elif key is not None and key not in self._keys:
self._keys.append(key)
self._onkeypress(fun, key)
def _NEWonkeypress(self, fun, key=None):
if fun is None:
if key is None:
self.cv.unbind("<KeyPress>", None)
else:
self.cv.unbind("<KeyPress-%s>" % key, None)
else:
def eventfun(event):
fun("tester")
if key is None:
self.cv.bind("<KeyPress>", eventfun)
else:
self.cv.bind("<KeyPress-%s>" % key, eventfun)
Turtle.onkeypress = NEWonkeypress
Turtle._onkeypress = _NEWonkeypress
board = Turtle()
screen = board.getscreen()
screen.tracer(0, 0)
temp = Turtle()
def textinput(testing):
print(testing)
def getroomname(option):
global temp
global board
#Box
temp.fillcolor("White")
temp.width(10)
temp.goto(-150, -60)
temp.down()
temp.begin_fill()
for x in range(2):
temp.forward(300)
temp.left(90)
temp.forward(120)
temp.left(90)
temp.end_fill()
temp.up()
temp.goto(0, 100)
screen.update()
#Box
temp.goto(0, -60)
screen.onkeypress(textinput)
listen()
getroomname(0)
mainloop()
(This is just a snippet of the main code, so don't worry about the random square it draws in space)

It's actually simpler than you're making it, we just need to go about it a little differently. When you run the code below, any key you type should get passed through turtle's event system and printed to the console:
from functools import partial
from turtle import Screen, Turtle
def getroomname():
temp.penup()
temp.goto(-150, -60)
temp.pendown()
temp.begin_fill()
for _ in range(2):
temp.forward(300)
temp.left(90)
temp.forward(120)
temp.left(90)
temp.end_fill()
def _onkeypress(self, fun, key=None):
if fun is None:
if key is None:
self.cv.unbind("<KeyPress>", None)
else:
self.cv.unbind("<KeyPress-%s>" % key, None)
else:
def eventfun(event):
fun(event.char)
if key is None:
self.cv.bind("<KeyPress>", eventfun)
else:
self.cv.bind("<KeyPress-%s>" % key, eventfun)
def keyinput(key):
print(key)
screen = Screen()
screen._onkeypress = partial(_onkeypress, screen) # monkey patch (protected access)
temp = Turtle()
temp.speed('fastest')
temp.fillcolor("white")
temp.width(10)
getroomname()
screen.onkeypress(keyinput)
screen.listen()
screen.mainloop()
I simplified your unrelated code. However, you're not using global correctly so I suggest you review a tutorial about that before you run into trouble. Also, you did from turtle import * but then did def textinput(...) where textinput is also method of Python 3 turtle -- so avoid doing from turtle import * to avoid even more trouble.

Related

My program crashes after i hold the w key to move the turtle for too long in the turtle library

The code is as follows:
import turtle
width = 400
length = 300
wn = turtle.Screen()
wn.bgcolor("black")
wn.title("x")
drawer = turtle.Turtle()
drawer.speed(3)
drawer.begin_fill()
drawer.color("blue", "yellow")
def drawern():
drawer.seth(90)
drawer.fd(1)
def drawerw():
drawer.seth(180)
drawer.fd(1)
def drawers():
drawer.seth(270)
drawer.fd(1)
def drawere():
drawer.seth(0)
drawer.fd(1)
wn.onkeypress(drawern, "w")
wn.onkeypress(drawerw, "a")
wn.onkeypress(drawers, "s")
wn.onkeypress(drawere, "d")
wn.listen()
wn.mainloop()
It gives a stack overflow error. Does anyone know why this issue persists? It doesn't happen when i let go of it once in a while.
I believe the stack overflow error is due to repeated assigning of angle to the stack. To prevent this, you can introduce a debounce. We will name our debounce as move.
A debounce, in simple terms, is fail-safe to prevent an event for triggering again and again while keeping the rest of the code running.
Define the variable in global space:
move = False
As for the function:
def drawern():
global move #To let the function know the variable is from global scope
if not move: #The key is pressed first time
drawer.seth(90)
move = True #Sets our debounce to True, it will not activate until the key is released
drawer.fd(1)
wn.onkeypress(drawern, "w")
We need to have another function with an event to reset our debounce:
def reset_db():
global move
move = False #Resets the debounce, referencing the key has been released
wn.onkeyrelease(reset_db, "w") #Connects the event
I have demonstrated for w key here only. You can duplicate it for the rest of the keys too.

Using the turtle package, is it possible to create a function asking for an argument? (And then use it for the .onkeypress method)

I tried myself at making the Pong project using the turtle package on Python.
I know how to make the code work (using 4 different functions taking no arguments, for each paddle and directions), but my question is if and how can I do it with this idea.
Here's the code I'm attempting to make work :
def move_paddle_up(paddle):
y = paddle.ycor()
y += 20
paddle.sety(y)
def move_paddle_down(paddle):
y = paddle.ycor()
y -= 20
paddle.sety(y)
#Keyboard binding movement
screen.listen()
screen.onkeypress(move_paddle_up(left_pad), "w")
screen.onkeypress(move_paddle_down(left_pad), "s")
screen.onkeypress(move_paddle_up(right_pad), "Up")
screen.onkeypress(move_paddle_down(right_pad), "Down")
When the screen is launched, the paddles won't move if I press the associated keys.
With the way of using the 4 different functions, it works.
It's just that I am curious to know how I should call or define the arguments in such a function.
I see then that is it quite more work than just creating 4 functions.
I think you got the wrong take home message. #BehdadAbdollahiMoghadam's use of functools.partial() is spot on, but we can fit it into your existing code with less overhead:
from functools import partial
# ...
def move_paddle_up(paddle):
paddle.sety(paddle.ycor() + 20)
def move_paddle_down(paddle):
paddle.sety(paddle.ycor() - 20)
# Keyboard binding movement
screen.onkeypress(partial(move_paddle_up, left_pad), "w")
screen.onkeypress(partial(move_paddle_down, left_pad), "s")
screen.onkeypress(partial(move_paddle_up, right_pad), "Up")
screen.onkeypress(partial(move_paddle_down, right_pad), "Down")
screen.listen()
# ...
The onkeypress method doesn't allow to pass a function with arguments, however you can use functools.partial for this kind of situations.
You can create a function which handles all type of keyboard presses, and pass that function as a partial function.
Let's create that function first:
def move_paddle(paddle, key_pressed):
def move_paddle_up(paddle):
y = paddle.ycor()
y += 20
paddle.sety(y)
def move_paddle_down(paddle):
y = paddle.ycor()
y -= 20
paddle.sety(y)
if paddle == left_pad:
if key_pressed == "w":
move_paddle_up(paddle)
elif key_pressed == "s":
move_paddle_down(paddle)
elif paddle == right_pad:
if key_pressed == "Up":
move_paddle_up(paddle)
elif key_pressed == "Down":
move_paddle_down(paddle)
Now we can use this function with functools.partial:
import functools
[...]
screen.onkeypress(functools.partial(move_paddle,[left_pad,"w"]), key="w")
screen.onkeypress(functools.partial(move_paddle,[left_pad,"s"]), key="s")
screen.onkeypress(functools.partial(move_paddle,[right_pad,"Up"]), key="Up")
screen.onkeypress(functools.partial(move_paddle,[right_pad,"Down"]), key="Down")
The way functools.partial works:
It takes a function with some arguments (*args, it's actually an unpacked list), and then creates a new function which is the previous function that gets those args as input:
def partial(func, /, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = {**keywords, **fkeywords}
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc

Using screen.onclick() in Turtle with a function that returns multiple values

I am trying to use the coordinates from a user's mouse click as the x and y for another function. When that second function runs, it returns two values (not x and y).
But when I run the onclick method, I get "TypeError: cannot unpack non-iterable NoneType object" before I even click anything.
def on_click(x, y):
t = turtle.Turtle()
screen = turtle.Screen()
t.penup()
t.goto(x, y)
#do stuff part 1
#do stuff part 2
return value1, value2
def main():
t = turtle.Turtle()
screen = turtle.Screen()
value1, value2 = screen.onclick(on_click)
#do other stuff with value1 and value2
main()
Am I using the turtle onclick function incorrectly? Or perhaps setting up my turtle screen wrong?
Else, if I can't return multiple values while using onclick, what would be a work around for getting those two values back into my main function?
Any help would be appreciated!
This syntax indicates you are nor thinking about event handlers correctly:
value1, value2 = screen.onclick(on_click)
The screen.onclick() function doesn't return anything (i.e. None) and your on_click() function can't return anything (i.e. None). You are not calling on_click() at this point in the code, it will be called in the future if/when a click occurs, so returning values doesn't apply. (The caller can't use them.)
You've got some choices: you can put (value1, value2) into a global for some other code to access; you can pass it to another function call. But you can't return it. Structurally, your code might look like:
from turtle import Screen, Turtle
def on_click(x, y):
screen.onclick(None) # disable handler inside handler
turtle.goto(x, y)
# do stuff part 1
# do stuff part 2
some_other_function(value1, value2)
screen.onclick(on_click) # reenable handler
def some_other_function(v1, v2):
# do other stuff with value1 and value2
pass
screen = Screen()
turtle = Turtle()
turtle.penup()
screen.onclick(on_click)
screen.mainloop()

How do I prevent python's turtle module from opening the default window?

I'm currently writing a module (myModule) that can create a tkinter canvas and attach a turtle to it.
That part of the module is done. However, for some reason, turtle still opens another window when it gets attached to the tkinter canvas. I would like to avoid this, but i don't know how, and the turtle documentation is horrible.
Here is the relevant part of what I've made so far:
#myModule
import tkinter as tk
import turtle as tr
import inspect as ins
from functools import partial
_root = None
_canvas = None
_turtle = None
d = None
def openWindow():
global _root
global d
if d == None:
myFrame = sys._getframe()
aboveFrameName = myFrame.f_back.f_back.f_back.f_globals["__name__"] #This doesnt make sense, but it works.
userModule = sys.modules[aboveFrameName]
d = userModule.__dict__
_root = tk.Tk()
def attachCanvas():
global _canvas
if _root == None:
openWindow()
_canvas = tk.Canvas(_root, bd = 0, highlightthickness = 0, width = 500, height = 500)
_canvas.pack()
def attachTurtle():
global _turtle
global _canvas
global _screen
global d
if _canvas == None:
attachCanvas()
_turtle = tr.RawTurtle(_canvas)
for key in tr.__dict__.keys():
obj = None
if key in tr.TNavigator.__dict__.keys(): #Checks if the object also exists in TNavigator
obj = getattr(tr.TNavigator, key)
if hasattr(obj, "__call__") and ("self" in ins.getargspec(obj)[0]): #Checks if the function
uses a self argument
obj = partial(obj, _turtle) #Pass in the turtle we just created automatically
else:
obj = getattr(tr, key)
d[key] = obj #Transfer object reference from myModule to userProgram
return _turtle
def mainloop():
tk.mainloop()
#userProgram
from myModule import *
attachTurtle()
forward(100)
mainloop()
Note: Lets say trM is the turtle module and trI is an instance of RawTurtle.
I have noticed for example that trM.forward(10) is applied on the default screen , and trI.forward(x) is applied on the tkinter screen. Additionally functions such as forward(x) (after having done from turtle import *) are actually calling trM.TNavigator.forward(trI, x).
Turtle has confused me enough for me to code up a recursive object inspector, but i still can't tell what needs to change.
Okay, I found where i went wrong.
The fix is to use:
if elem != "mainloop":
d[elem] = obj
Instead of just d[elem] = obj
Turns out that the default window is created when turtle's mainloop() is called.
In theory, mainloop() in userProgram is the mainloop() in myModule. Unfortunately, because of attachTurtle() (and more specifically d[elem] = obj) this definition gets overwritten with turtle's mainloop(). So the fix is just to prevent attachTurtle() from changing mainloop().
The lesson to be learned is to check what definitions you are creating, in case you're overwriting an important one.
I can't believe I went digging in the source code when the solution was this simple

Python Turtle hold key function

I am attempting to make pong using Turtle, I have most things working, however, I would like to implement the ability to hold a key to move the bumpers up and down, rather than having to tap the key multiple times. These are my functions for movement so far.
def lbump_move_up():
x = lbump.ycor()
x += bumpspeed
if x > 240:
x = 240
lbump.sety(x)
def lbump_move_down():
x = lbump.ycor()
x -= bumpspeed
if x < -240:
x = -240
lbump.sety(x)
def rbump_move_up():
x = rbump.ycor()
x += bumpspeed
if x > 240:
x = 240
rbump.sety(x)
def rbump_move_down():
x = rbump.ycor()
x -= bumpspeed
if x < -240:
x = -240
rbump.sety(x)
turtle.listen()
turtle.onkey(lbump_move_up,'w')
turtle.onkey(rbump_move_up,'Up')
turtle.onkey(lbump_move_down,'s')
turtle.onkey(rbump_move_down,'Down')
turtle.onkey(ball_move,'Return')
I don't have a complete answer to this, as I came here looking for one myself. However, I have some progress on it that someone else could finish...
You can create a new class that calls like a recursive function as follows:
class function2:
def __init__(self,fun,args=None):
self.fun=fun
self.args=args
def begin_loop(self):
self.looping=True
self.loop()
def loop(self):
self.fun(self.args)
if self.looping: self.loop()
def end_loop(self):
self.looping=False
Now to tie it to your example, you can convert your functions into function2s and thus call them onkeypress as follows:
l_up=function2(lbump_move_up)
r_up=function2(rbump_move_up)
l_down=function2(lbump_move_down)
r_down=function2(rbump_move_down)
Wn=turtle.Screen()
Wn.onkeypress(l_up.begin_loop,'w')
Wn.onkeypress(r_up.begin_loop,'Up')
Wn.onkeypress(l_down.begin_loop,'s')
Wn.onkeypress(r_down.begin_loop,'Down')
Wn.onkeyrelease(l_up.end_loop,'w')
Wn.onkeyrelease(r_up.end_loop,'Up')
Wn.onkeyrelease(l_down.end_loop,'s')
Wn.onkeyrelease(r_down.end_loop,'Down')
Wn.listen()
So to the issue: RecursionErrors
If you want your function to cause a small change, then you'll need to hold the button for a long time and that causes RecursionErrors - which at least in this case don't seem to be affected by try/except statements.
Once again, sorry for the incomplete solution, but I feel like you can probably get away with larger movements in pong.
I had the same issue, but found the "onkeypress" function in the Turtle documentation (https://docs.python.org/3/library/turtle.html#turtle.onkeypress), which resolved the issue for me.
turtle.onkeypress(fun, key=None)
"onkeypress", will move the turtle as long as the key is pressed, while "onkey" will move it once when the key is released.
Example:
from turtle import Turtle, Screen
turtle = Turtle("turtle")
turtle.penup()
screen = Screen()
screen.listen()
def move_forward():
turtle.forward(20)
def move_backward():
turtle.backward(20)
screen.onkeypress(move_forward, "w")
screen.onkey(move_backward, "s")
screen.exitonclick()

Categories

Resources