Question

I'm currently writing a small code to move two balls around in a Tkinter GUI and do some other stuff too. I've already written a code that works, but as it uses a lot of global variables I tried to improve it. Below I've pasted the code section relative to my question :

can.coords needs five parameters : the object you wanna 'move', and the new coordinates. The returns from both moveLeft() and addThirty() are two items lists. Of course, the star operator (to unpack a list) doesn't work .

How do I pass the four items from the two returned lists of the functions into the method .coords() ?

PS : I'm new to Python and even to programming.

def moveo (lr, tb):
    global newX, newY
    newX = x+lr
    newY = y+tb
    return newX, newY


def moveLeft ():
    coordins = moveo (-10, 0)
    return coordins

def addThirty (func):
    i = 0
    coordinsNew  = func
    coordinsNew = list(coordinsNew)
    while i < 2:
        coordinsNew[i] = coordinsNew[i]+30
        i += 1
    return coordinsNew

Button(wind, text = 'Left', command=can.coords (oval1,(*moveLeft()),(*addThirty(moveLeft()))))
Was it helpful?

Solution

You can always combine two lists or two tuples into one just with +:

can.coords(oval1, *(moveLeft() + addThirty(moveLeft())))

Even if you've got sequences (or even iterators) of different types, you can always convert them:

can.coords(oval1, *(moveLeft() + tuple(addThirty(moveLeft())))))

However, you really should step back and ask why this needs to be one line in the first place. It scrolls off the right edge of the screen, it requires enough complex parenthesization that you have to think about it to understand it, etc. Why not just do this:

top, left = moveLeft()
bottom, right = addThirty(moveLeft())
can.coords(oval1, top, left, bottom, right)

In a comment, you say:

I can't do this because I want the coordinates to change every time I press the button. So the button needs to : execute both functions to modify the coordinates and pass them to can.coords () in one time.

Just putting it in one line doesn't do that, or even help make that easier. The way you've written it, you're calling can.coords once, and passing the resulting return value as the command. That's not what you want. What you need to pass is a function that does all this stuff.

Which means that you definitely want to split it up into multiple lines. For example:

def update_coords():
    top, left = moveLeft()
    bottom, right = addThirty(moveLeft())
    can.coords(oval1, top, left, bottom, right)
Button(wind, text = 'Left', command=update_coords)

Because the only way to put it in one line would be with an equivalent lambda or partial, which will be even more unreadable than a call; something like:

Button(wind, text = 'Left', command=lambda: can.coords(oval1, *(moveLeft() + addThirty(moveLeft()))))

To explain the difference between passing a function, and calling a function and passing its return value, let's take a much simpler example:

>>> def foo():
...     return 2
>>> print(foo)
<function foo at 0x12345678>
>>> print(foo())
2

Here, it should be pretty clear what the difference is. foo is an expression whose value is the function foo itself. But foo() is an expression whose value is determined by calling foo with no arguments, then using whatever was returned (in this case, 2).

If we make it a little more complicated, it's no different:

>>> def bar(x, y):
...     return x+y
>>> print(bar)
<function bar at 0x12345680>
>>> print(bar(2, 3))
6

So, it's obvious how you can pass around bar itself, or how you can pass the 6 you get back from bar(2, 3)… but what if you want to pass a function that can be called with no arguments and return the same thing that bar(2, 3) would return? Well, you don't have such a thing; you have to create it.

You can do this in two ways: creating a new function:

>>> def new_function():
...     return bar(2, 3)

… or partially evaluating the function:

>>> new_function = partial(bar, 2, 3)

Your case adds a few extra wrinkles: you're starting with a bound method rather than a function, you need to make sure the arguments get evaluated each time the new function is run (because calling moveLeft() twice each time rather than just once is every bit as important as calling can.coords each time), and you've got a bunch of arguments that you get in a complicated way. But none of those wrinkles make things any harder; you just have to look past them:

>>> def new_function():
...     can.coords(oval1, *(moveLeft() + addThirty(moveLeft())))

(The partial would be a lot harder to write, because you have to compose a sequence of functions together just to get the parameters, which you need to partial as well… but whenever partial isn't trivial in Python, don't try to figure it out, just write an explicit function.)

OTHER TIPS

If both functions return the same type (list or tuple) then just do:

can.coords(oval1, *(moveLeft() + addThirty(moveLeft())))

If they return different types (tuple, list, iterator, whatever) do:

args = list(moveLevt())
args.extend(addThirty(moveLeft()))
can.coords(oval1, *args)

Sorry for digging up this topic, but after an interesting answer from a user on another topic, I thought I'll improve the answer to this question. Actually, you can assign a function with arguments to a command as long as it returns a function. In this case, it will avoid you a lot of trouble as you don't need to write a new function for every left right, down etc.

As you see, I can use arguments to the functions I assign to command:

command=move1(0,10)

I've written the code for only one oval, just to show how it works.

from tkinter import *

x1, y1 = 135, 135
x2, y2 = 170, 170



def move1 (x, y):
    def moveo1 ():
        global x1, y1
        x1, y1 = x1+x, y1+y
        can.coords (oval1, x1, y1, x1+30, y1+30)
    return moveo1



##########MAIN############

wind = Tk()
wind.title ("Move Da Ball")

can = Canvas (wind, width = 300, height = 300, bg = "light blue")
can.pack (side = LEFT,padx = 5, pady = 5)


oval1 = can.create_oval(x1,y1,x1+30,y1+30,width=2,fill='orange') #Planet 1
Button(wind, text = 'Left', command=move1(-10,0)).pack(padx = 5, pady = 5)
Button(wind, text = 'Right', command=move1(10,0)).pack(padx = 5, pady = 5)
Button(wind, text = 'Top', command=move1(0,-10)).pack(padx = 5, pady = 5)
Button(wind, text = 'Bottom', command=move1(0,10)).pack(padx = 5, pady = 5)


Button(wind, text = 'Quit', command=wind.destroy).pack(padx = 5, pady = 5)

wind.mainloop()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top