1

I want to set colors of an object, and don't want to create 10 functions for every color. So, I just want to declare the colors and create 10 buttons and one function. Error message is:

<lambda>() missing 1 required positional argument: 'green'

The code:

from tkinter import *

green=["#5bd575","#55c76d"]
#different colors should follow here

root=Tk()
Btn=Button(text="Trigger lambda", command=lambda green: printfunction(green))
Btn.pack()

def printfunction(colorset):
    print(colorset)

It does not need to be a lambda function, the question is just, How can I call the printfunction with an argument by clicking the button?

Jakub M.
  • 32,471
  • 48
  • 110
  • 179
user2366975
  • 4,350
  • 9
  • 47
  • 87
  • I found a way, but it is not totally convenient to me: command=self.start_set_color function, which calls a function for each color (2 lines) that calls a second, general function "self.set_color" (that gets the color as parameter. but still more than one function – user2366975 May 19 '13 at 11:53
  • found a solution, similar to the first idea with lambda: command=lambda:set_color("green") for the green-button. lambda just has to be called without the parameter. feeling kind of stupid. – user2366975 May 19 '13 at 12:08

2 Answers2

2

The command callable doesn't take any arguments. If you want to pass the green list into printfunction, just omit the argument, the lambda doesn't need it:

Btn=Button(text="Trigger lambda", command=lambda: printfunction(green))

Now green inside the lambda refers to the global.

If all you wanted to do was to call printfunction with a pre-defined argument, you could use the functools.partial() function; you pass it the function to be called plus any arguments that need to be passed in, and when it's return value is called, it'll do exactly that; call the function with the arguments you specified:

from functools import partial

Btn=Button(text="Trigger lambda", command=partial(printfunction, green))
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Alternatively, you can use `partial(printfunction, green)` – Jakub M. May 19 '13 at 11:13
  • 1
    @JakubM.: I fear that explaining `functools.partial()` is going to be a conceptual step too far. – Martijn Pieters May 19 '13 at 11:15
  • `functools.partial()` is the right tool for the job here - no point reinventing the wheel. I think if you get `lambda`, you'll get `partial()`. – Gareth Latty May 19 '13 at 11:20
  • 3
    Oh you guys and your arm twisting. `partial()` is indeed the right tool, it is more performant than a lambda. It is the right tool when you know what you are doing, but when you are still grasping the concepts of callables and Tk buttons having to learn about partials too is extra cognitive weight that a new learner could do without. But, on your heads be it. :-P – Martijn Pieters May 19 '13 at 11:25
  • Well spoken, Martijn. ;) There's another thing that can come and bite you with `partial()`. Quoting from the documentation: "Also, partial objects defined in classes behave like static methods and do not transform into bound methods during instance attribute look-up." – Ulrich Eckhardt May 19 '13 at 11:46
  • `lambda` have their own pitfalls, e.g. `>>> lambdas = [] >>> for x in 0,1: lambdas.append(lambda: x+1) ... >>> for func in lambdas: print(func()) ... 2 2 `. This does not happen using `partial`. Using `lambda` when you actually want `partial` is a conceptual mistake. – Bakuriu May 19 '13 at 11:53
  • 1
    @Bakuriu: That is nothing special about lambdas, that happens to functions too. Partials have no scoped namespace themselves and are thus not affected, sure, but you are not defining a function or lambda there. What you see is a misunderstanding about when a name in a new scope is looked up. `lambdas.append(lambda x=x: x+1)` will print `1` then `2`. – Martijn Pieters May 19 '13 at 11:57
  • @MartijnPieters Yes, I know. But it happened more than once to see people not aware of this pitfall about function definition. It usually happens more with `lambda`s since it's harder to see a `def` inside a loop. – Bakuriu May 19 '13 at 12:03
  • @Bakuriu: I know it's a common pitfall, [I answered questions about it before](http://stackoverflow.com/questions/12423614/local-variables-in-python-nested-functions/12423750#12423750); but don't make this out to be a disadvantage of lambdas. – Martijn Pieters May 19 '13 at 12:03
0

Python is incredibly dynamic, you can modify classes after their definition or create local functions. Example:

class X:
    # generic print function
    def print_color(self, color):
        pass

# dictionary with colors to support
COLORS = {'green': ["#5bd575","#55c76d"]}

# attach various print functions to class X
for name, value in COLORS.items():
    def print_color_x(self, value=value):
        self.print_color(value)
    setattr(X, 'print_color_{}'.format(name), print_color_x)

Note the value=value default parameter. This one is necessary to bind the value within each iteration. Without it, the lookup of value would take place when the function is called, giving you errors about a missing global value or picks up a random one that it happens to find there, but not the one you want. Using functools.partial() could be cleaner, if it allowed creating proper memberfunctions and not just staticfunctions. Note that the example pure Python implementation mentioned in the documentation allows creating memberfunctions, so using that as a replacement is an option.

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
  • ( : well, I upvoted your solution because it is simple and easy, but I wouldn't spare the upcoming Pythonista another relatively simple option where you learn a bit more about Python. Next step: Overriding __getitem__ to generate the colour-printing methods on the fly. – Ulrich Eckhardt May 19 '13 at 12:11
  • hm interesting idea. in some other peace of code i noticed a strange behaviour where values had been picked randomly, looks like the value=value thing is the way to go there. – user2366975 May 19 '13 at 23:14