Calling same function by different objects by pressing different key in python

Question:

I have created two turtles say ‘tur1’ and ‘tur2’ in python using turtle module. I have created event listener on the screen. I have created a function ‘move_fwd(turte_name)’ which moves the turtle forward by 20 steps.

I want that if key ‘w’ is pressed then this method shoud be called by ‘tur1’ and tur1 should move and if key ‘i’ is pressed then this function should be called by ‘tur2’ turtle and it should move.

I dont know how to pass argument ‘turtle_name’ while this method is called by event_listener.

from turtle import Turtle, Screen

screen = Screen()
tur1 = Turtle()
tur1.shape("turtle")
tur1.shape("turtle")
tur1.penup()
tur1.setposition(x=-200, y=-100)

tur2 = Turtle()
tur2.shape("turtle")
tur2.penup()
tur2.setposition(x=200, y=100)


def move_fwd(turtle_name):
    turtle_name.forward(20)


screen.listen()

screen.onkey(move_fwd, "w") # how to tell that this is to be called for tur1
screen.onkey(move_fwd, "i") # how to tell that this is to be called for tur2

screen.exitonclick()

I have some idea how to do this in javascript by adding addEventListener ..


document.addEventListener("keypress", function(event) {

  makeSound(event.key);

  buttonAnimation(event.key);
  
  });


function makeSound(key) {

  switch (key) {
    case "w":
      var tom1 = new Audio("sounds/tom-1.mp3");
      tom1.play();
      break;

    case "a":
      var tom2 = new Audio("sounds/tom-2.mp3");
      tom2.play();
      break;

    case "s":
      var tom3 = new Audio('sounds/tom-3.mp3');
      tom3.play();
      break;


But no idea about how to do in python.

Thanks for your valuable time.

Asked By: Akash Singh

||

Answers:

You just need to call two different functions:

def move_fwd1():
    tur1.forward(20)
def move_fwd2():
    tur2.forward(20)


...

screen.onkey(move_fwd1, "w") # how to tell that this is to be called for tur1
screen.onkey(move_fwd2, "i") # how to tell that this is to be called for tur2

Answered By: quamrana

Use a lambda

screen.onkey(lambda: move_fwd(tur1), "w")
screen.onkey(lambda: move_fwd(tur2), "i")
Answered By: Barmar

This answer was triggered by the comment of Barmar explaining the advantages of using the lambda construct:

@Claudio This method generalizes better than defining a named function for every turtle.

The trouble I anticipate here is that the parameter to the inner function of the lambda construct are passed at parsing time (and not at runtime depending on the current value assigned to the parameter variable). This can result in hard to debug errors and lead to confusion. There are many questions here on stackoverflow because of this potential confusion.

I suggest to generalize using another approach which allows definition of any number of turtles and their properties using Python lists which then are used to generate the code required to provide the desired functionality. And yes, the code below uses different function names for different keyboard keys, but at the same time allows also to modify the move() function which is used for all the turtles.

from turtle import Screen, Turtle
screen = Screen()

Turtles         =   [Turtle(),  Turtle()    ]
StartPositions  =   [(200,-100),(200,100)   ]
Names           =   ["turti_1", "turti_2"   ]
Keys            =   ["w",       "i"         ]
Colors          =   ["red",     "green"     ]

# =========================================================

def move(turtle):
    print(turtle.name)
    turtle.forward(20)
    
for turtleNo, turtle in enumerate(Turtles):
    turtle.shape("turtle")
    turtle.penup()
    turtle.setposition(StartPositions[turtleNo])
    turtle.name=Names[turtleNo]
    turtle.color(Colors[turtleNo])
    exec(f'def onKey_{Keys[turtleNo]}(): move(Turtles[{turtleNo}])')
    exec(f'screen.onkey(onKey_{Keys[turtleNo]}, "{Keys[turtleNo]}")')
    
screen.listen()
screen.exitonclick()

There ARE cases where exec() makes sense and is helpful to achieve generalization making it possible to use string variables for variable names or parts of variable names and at the same time using them also just as strings.

Extend the list in the code above to:

Turtles         =   [Turtle()   ,   Turtle()    ,   Turtle()    ,   Turtle()    ,   Turtle()        ]
StartPositions  =   [(200,-100) ,   (200,100)   ,   (200,-50)   ,   (200,50)    ,   (200,0)     ]
Names           =   ["turti_1"  ,   "turti_2"   ,   "turti_3"   ,   "turti_4"   ,   "turti_5"   ]
Keys            =   ["w"        ,   "i"         ,   "s"         ,   "k"         ,   "b"         ]
Colors          =   ["red"      ,   "green"     ,   "blue"      ,   "cyan"      ,   "magenta"   ]

and enjoy the five Turtle race …

Answered By: Claudio

The actual issue you are facing here coming from javascript programming is that the Python turtle.py module, probably for the sake of simplicity or just for no good reason at all, does not pass the key which triggered the keyboard event handling function to this function like it is usually the standard in all keyboard input handling supporting modules.
And it is not an issue of Python as such. It is specific to the turtle.py module only.

I suggest you read the answer by ggorlen to another question stating:

Preamble: turtle is a mess and not nearly as beginner-friendly as it could be. There are a ton of inconsistencies in the API and it breaks almost every rule of the Zen of Python. It’s probably the way it is because it was written a long time ago and hasn’t been changed much since. So don’t be too surprised when parts of the turtle API or internals are inexplicably weird or inconsistent or an IDE autocomplete gets confused.

You can experience it yourself patching the turtle module as done in the code below changing only one line (in order to make the pressed key available as parameter) in the turtle.py module to achieve the functionality you for good reason expect to be there:

part of turtle.py file where one line need to be changed

# The PATCH: 
from turtle import TurtleScreenBase
def _onkeyrelease(self, fun, key):
    """Bind fun to key-release event of key.
    Canvas must have focus. See method listen
    """
    if fun is None:
        self.cv.unbind("<KeyRelease-%s>" % key, None)
    else:
        def eventfun(event):
            fun(key) # <<<=== CHANGED ===>>> was:  fun()
        self.cv.bind("<KeyRelease-%s>" % key, eventfun)
TurtleScreenBase._onkeyrelease = _onkeyrelease

# The script utilizing the PATCH:
from turtle import Turtle, Screen

screen = Screen()
tur1 = Turtle()
tur1.shape("turtle")
tur1.shape("turtle")
tur1.penup()
tur1.setposition(x=-200, y=-100)

tur2 = Turtle()
tur2.shape("turtle")
tur2.penup()
tur2.setposition(x=200, y=100)

def onKey(key):
    if key=="i": 
        print("  i pressed. Moving tur1 .")
        tur1.forward(20)
    if key=="w":
        print("  w pressed. Moving tur2 .")
        tur2.forward(20)


screen.listen()

screen.onkey(onKey, "w") # how to tell that this is to be called for tur1
screen.onkey(onKey, "i") # how to tell that this is to be called for tur2

screen.exitonclick()
Answered By: Claudio