-1

No, question's not duplicate. It isn't about "How to say a=1 in python"?

And isn't trivial. Please read carefully.

Answer is: it's impossible to create such a one-liner, at least without using dirty hacks. See the accepted answer for details.

And if you don't want to read, but came here for easy karma, please pass by and stop minusing me, cause the accepted answer is very useful. :)


Question:

I'm doing some GUI programming and want to create a very short signal handler that would set local variable to a value in response to a signal from GUI.

def function():
    output = False #this is local variable; I want to change its value by signal handler
    button = PyQt4.QtGui.QPushButton("simple button")
    #next line sets output to True
    button.pressed.connect(lambda:SOME_FUNCTION_I_AM_LOOKING_FOR(output, True))
    #Now output==True

Could you suggest, what to substitute for SOME_FUNCTION_I_AM_LOOKING_FOR? Obviously, you can't just say button.pressed.connect(output=True), cause output=True isn't a function.

To illustrate what I'm looking for here's an analogue: if you want to set an object's attribute, you can use setattr function. Here's an analogue of code above, setting attribute instead of local variable:

def function():
    ...
    #create a dummy UserObject class and make an instance of it to store attributes
    output_object = type('UserObject', (), {})() 
    output_object.it_was_pressed = False #flags that button was pressed
    #when "pressed" signal of button is emitted, "it_was_pressed" attribute of output_object is set to True
    button = PyQt4.QtGui.QPushButton("simple button")
    button.pressed.connect(lambda: setattr(output_object, "it_was_pressed", True) 

That's not what I want to do. What I want to do is create an analogue of this to set a local variable in response to signal:

UPDATE:

I'm asking for one-liner solution: concise and elegant. Of course, I could do this in 3 lines, but I don't want to.

Boris Burkov
  • 13,420
  • 17
  • 74
  • 109
  • Also read: [How do I write a function with output parameters (call by reference)?](http://docs.python.org/2/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference) – Ashwini Chaudhary Feb 09 '14 at 20:06
  • either use a global variable, or pass ```self``` if it is inside a class already. – dashesy Feb 09 '14 at 20:09
  • @delnan not exactly, question is more specifically related to callback functionality, IMO – dashesy Feb 09 '14 at 20:21

2 Answers2

3

There is no reliable way to modify the local environment "programmatically" (i.e. without an explicit assignment).

For example this works in python2:

>>> def test():
...     output = False
...     exec('output=True')
...     print(output)
... 
>>> test()
True
>>> test()
True

However it does not work in python3!

>>> def test():
...     output = False
...     exec('output=True')
...     print(output)
... 
>>> test()
False

Modifying the dictionary returned by locals() might not work, for example:

>>> def test():   #python 2 and 3
...     output = False
...     locals()['output'] = True
...     print(output)
... 
>>> test()
False

It might work on some old versions of python.

exec probably works in python2 because it is a statement, hence the python compiler can determine if the function might need to really use a dict to store its variables and make exec work properly`. It's pretty simple to check this:

>>> def test():
...     exec('pass')
...     locals()['output'] = True
...     print(output)
... 
>>> test()
True
>>> def test():
...     locals()['output'] = True
...     print(output)
... 
>>> test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in test
NameError: global name 'output' is not defined

As you can see if a function contains an exec statement python avoids to optimize local variables and just uses the dict (thus making changes to locals() work properly). When no exec is found changes don't work.

In python3 exec is a function and the python compiler always optimizes local lookup.


So:

  • in python2 you could do that with exec, but it is a statement and cannot be used inside lambdas
  • in python3 you simply cannot do what you want, no matter how hard you try. There is simply no way to modify a local variable without an explicit assignment statement.

Therefore what you ask is, AFAIK, impossible to do in a single expression of one line (at least in any reasonably readable way). The proper way of doing what you want is to use nonlocal in python3:

output = False
def handler():
    nonlocal output
    output = True

button.pressed.connect(handler)

In python2 you don't even have nonlocal so you'll have to use one of the hacks to work around this (like putting in output a one element list etc.).

I don't exclude that there exist a ugly, unreadable and hackish way of writing a one-liner that defines a function (not a lambda!) and that achieves what you want, but I'm pretty sure there is no "concise and elegant" one-liner that does what you want.


Obviously if you don't mind output to be a list you could do the ugly:

output = [False]

button.pressed.connect(lambda: output.__setitem__(0, True))
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • Thank you so much for taking the time to read into the question and to write a whole survey! I'll let the question hang for a while, may be someone suggests an `ugly, unreadable and hackish way` (just for the sake of interest), but will nevertheless accept your answer later. – Boris Burkov Feb 09 '14 at 21:24
0

If you just care about the boolean check, you can rely on some Pythonic idioms:

output = {} # similar to False case

def callback_func(output, val):
    if val:
       output['set']=True
    else:
       output.clear()

If dictionary is empty it will be evaluated as False, otherwise as true.

dashesy
  • 2,596
  • 3
  • 45
  • 61
  • I worry about shortness: your callback function is 5 lines long. I'd accept a 1-line lambda function :) Note that in the second code snippet output is a variable, not an object. I want to set `output = True`, not `output.attribute = True`. More generally, I want a function, analogous to `exec "output=1"`, like exec("a=1"), if exec were a function like `eval`. – Boris Burkov Feb 09 '14 at 20:35