4

I am using PyQt and want to create a menu based on a list of strings.

The problem is that when I want to call 'addAction', it requires a call-back function (for each string) that does not take any arguments.

For simple menus, this would be fine: e.g.

menu.addAction("Open", self.open)
menu.addAction("Exit", self.quit)

However, I want to just use a single function and have the 'action string' passed in as an argument.

I am wondering if python can do something like this:

def f(x, y):
    print x + 2*y



# These 2 variables are of type: <type 'function'>
callback1 = f
callback2 = f(x=7, *)
# NB: the line above is not valid python code.
# I'm just illustrating my desired functionality

print callback1(2,5)  # prints 12
print callback2(5)    # prints 17

Here is my code snippet:

def printParam(parameter):
    print "You selected %s" % parameter


# parameters = list of strings

menu_bar = QMenuBar()
menu = menu_bar.addMenu('Select Parameter')
for parameter in parameters:
    # This line will not work because the action does not pass in
    # any arguments to the function
    menu.addAction(parameter, printParam)

Any suggestions greatly appreciated

3
  • "Use a closure". A closure is simply a function that closes over a free variable in the enclosing scope. Commented Jul 13, 2011 at 17:03
  • @pst: No, that'd be reinventing the wheel given the presence of functools.partial. Plus "closure" is a much broader concept ;) Commented Jul 13, 2011 at 17:07
  • @delan :) What do you mean by closure being a broader concept? Commented Jul 13, 2011 at 19:07

5 Answers 5

6

functools.partial() allows you to supply some of the arguments ahead of time. It will allow you to make custom callbacks as you want.

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
Sign up to request clarification or add additional context in comments.

Comments

4

You can use a closure, like this:

def printParam(parameter):
    def callback():
        print "You selected %s" % parameter
    return callback

for parameter in parameters:
    menu.addAction(parameter, printParam(parameter))

2 Comments

As I wrote before, if you just need to apply a function partially, just using functools.partial is shorter and (arguably) simpler. A higher order function like this is a good choice if you need more complex logic though.
Yes, functools.partial does the job here. But there's something beautiful about closures.
2

Yes, it's called partial application. The standard library has a class to apply a function partially, functools.partial. You'd write partial(f, x=7) or partial(f, 7) (since it's a positional argument and you don't need to skip over any positional arguments) in your example.

Comments

1

Use functools.partial to do the following (easier to demonstrate on your simple example):

import functools
callback2=functools.partial(f, 7)
callback3=functools.partial(f, y=5) #if you want to pass `y` instead of `x`

If you always want to pass a particular argument, then as others have pointed out you could use a closure:

def f(x):
    def g(y):
        print x + 2*y
    return g
callback1=f
callback2=f(7)
callback1(2)(5) #prints 12
callback2(5) #prints 17

2 Comments

Note that with this, you have to write callback2(y=5), otherwise you get an error about receiving multiple values for x when you call callback2(5). Easier in this case to write functools.partial(f, 7)
@isbadawi Noted and fixed. Thanks.
0
def my_menu_func(self, string):
   # whatever your function does with that parameter

then for setting up the menu:

x = "Open"
a = eval("lambda : my_menu_func('"+x+"')")
menu.addAction(x, a)

I tried using a lamba directly inside addAction, but that forms a closure containing the variable x, so any subsequent local changes to x would alter the parameter passed in the call to my_menu_func. Doing it with the eval you can iterate over a list of strings and create a whole menu for example by:

for x in List_of_Menu_Items:

do the same thing.

2 Comments

Wasn't my vote but I'd guess it's because of the 'eval' part of your answer.
But without the eval it creates a function that calls my_menu_func with a reference to the local variable x instead of the value "Open". So on subsequent calls, you don't get what you think.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.