You are not logged in.

#1 2015-05-06 14:46:25

Awebb
Member
Registered: 2010-05-06
Posts: 6,286

[SOLVED] Python, run expressions from a dictionary, assign variable

I have recently learned, that Python has no case statement, and how we are supposed to either use complex and slow if blocks or (more elegantly) dictionaries. Now this could look like this:

options = {
    0 : null,
    1 : eins,
    2 : zwei
}

def null:
    ....

def eins:
    ....

def null:
    ....

options[int(input())]()

Now I can run the thing and it will allow me to input something, accept 0,1 or 2 and then run the functions associated with the identifier in the dictionary (hence the term associative array).

If I had just stopped there, everything would have been fine, but stupid me had to read more. Now if I do this:

options = {
    0 : lambda x: x+2,
    1 : lambda x: x*2,
    2 : lambda x: x/2
}
options[int(input())](int(input()))

... then I can have my simple case statement without declaring a set of functions, that are probably not used anywhere. Now I want to set a variable from inside the dictionary:

options = {
    0 : onevariable = "is one",
    1 : anothervariable = "is two",
    2 : somevariable = "is three"
}
options[int(input())]()

It tells me, that a NoneType object is not callable. Now I'm a bit puzzled, how can I do this? I didn't have any results with exec() and eval(). I know I probably shouldn't use variables like this and I'm doing it wrong in some way, but it left one question, I cannot answer myself right now:

Why can I use a lambda expression or the name of a function, but no assignments?

Last edited by Awebb (2015-05-07 09:24:46)

Offline

#2 2015-05-06 15:14:27

Raynman
Member
Registered: 2011-10-22
Posts: 1,539

Re: [SOLVED] Python, run expressions from a dictionary, assign variable

Awebb wrote:

Why can I use a lambda expression or the name of a function, but no assignments?

As the error message says, you can't call an assignment. This doesn't make sense (is not legal Python code):

(onevariable = "is one")()

An assignment is used for its side-effect (of binding a variable to a value), not for its value.

You could store code (containing assignments) as strings in the dictonary and use exec, but that's not something I'd recommend (but this looks more like a toy example than a real program anyway).

options = {
    0 : 'onevariable = "is one"',
    1 : 'anothervariable = "is two"',
    2 : 'somevariable = "is three"'
}
exec(options[int(input())])

Last edited by Raynman (2015-05-06 15:17:28)

Offline

#3 2015-05-06 16:44:51

Awebb
Member
Registered: 2010-05-06
Posts: 6,286

Re: [SOLVED] Python, run expressions from a dictionary, assign variable

The problem with exec() is, that exec will not work for mixed cases, e.g. 0 is an assignment and 1 is the name of a function. This is indeed a dummy and not a real program. Let me ask the other way round: What can I use in this fashion? Similar to this problem, I cannot write a lambda with an assignment. What can I use inside a lambda expression?

Offline

#4 2015-05-06 17:42:36

Raynman
Member
Registered: 2011-10-22
Posts: 1,539

Re: [SOLVED] Python, run expressions from a dictionary, assign variable

Awebb wrote:

The problem with exec() is, that exec will not work for mixed cases, e.g. 0 is an assignment and 1 is the name of a function. This is indeed a dummy and not a real program. Let me ask the other way round: What can I use in this fashion?

Still not exactly sure what you want to hear, but exec can definitely work for mixed cases. The string containing code would include the parentheses to call a function and maybe also parameters. You could add

  3 : 'print("hello world")'

to the exec() example. But that's still a rather contrived example.

Generally you want to have the same type of object for each element in the dictionary, but that type depends on the use case: you have shown functions of zero and functions of one argument, with or without a return value, I showed strings (containing Python code). You could also put different types of objects in there and then pick a way to process the chosen value based on certain characteristics (or explicit type tests), such as whether the object is callable or not. But that's likely to get messy pretty quickly.

I get that you're learning, so are you just making these "mixed cases" up, or did you run into this trying to implement something "real"? (that might give us something more concrete to talk about)

Similar to this problem, I cannot write a lambda with an assignment. What can I use inside a lambda expression?

Any decent Python book/tutorial will tell you that a lambda body can only contain a single expression, no statements (such as assignments). It is a bit more limited than I'd like, but they have their uses. The following hack seems to work, though:

(lambda: exec("global z;z=2"))()
print(z) # shows "2"

Last edited by Raynman (2015-05-06 17:44:19)

Offline

#5 2015-05-07 09:01:07

jakobcreutzfeldt
Member
Registered: 2011-05-12
Posts: 1,041

Re: [SOLVED] Python, run expressions from a dictionary, assign variable

You're moving into a territory that is very non-Pythonic.  The general philosophy should be that if you're struggling to figure out the most clever way to do something in Python, you're doing it wrong.  Clarity should be preferred over cleverness.  Sure, you could eventually find a way that works, but it will be so convoluted as not to be worth it.  You don't want to have to look at your code a long time from now and have to re-figure out your thought processes.

Offline

#6 2015-05-07 09:23:26

Awebb
Member
Registered: 2010-05-06
Posts: 6,286

Re: [SOLVED] Python, run expressions from a dictionary, assign variable

To summarize my findings mixed with your input:

1. Python simply has no case/switch statement and any attempt to emulate it only goes so far.
2. My primary use for case statements so far has been option/parameter/argument control (in bash for example). I might want to have a look at getopt, optparse and argparse.
3. Using dictionaries to emulate case statements are meant to reduce performance hits of complex nested if constructs.
4. This would be a great time to brush up all those terms I once learned and seem to have forgotten.
5. Trying to be clever in Python creates a set of problems, that were not there in the beginning.

And, as a bonus:

6. Solving a problem you introduced by trying to be clever reduces the actual work you get done. Be clever, if you know how, get work done if not.

Thanks for the pushes in the right directions.

Offline

#7 2015-05-07 13:47:42

Trent
Member
From: Baltimore, MD (US)
Registered: 2009-04-16
Posts: 990

Re: [SOLVED] Python, run expressions from a dictionary, assign variable

Although you've been deterred from being clever, there is a super-clever way to do this by passing the locals() dict. Don't do this, it's very foolish:

>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> foo = lambda d: d.__setitem__('x', 10)
>>> foo(locals())
>>> x
10

locals().__setitem__('x', 10) sets x to 10 just like an assignment statement. I had to pass the locals() dict in to the function because using locals() within the lambda expression would only affect the scope of the lambda itself, not the outer scope. There is also a globals() function I could have used instead, but then it might not work correctly when called within a function.

If something like this is what you really wanted to do, I'd recommend locals().__setitem__ over using exec(), because of the potential for accidental side effects. But as jacobcreutzfeldt said, it's unPythonic, and you should probably be looking for another, less clever solution.

One thing that comes to mind, which I do when parsing arguments, is to keep a master dict that stores options and flags, rather than making them distinct variables. Not how you might do it in a shell script, but it definitely works better in Python.

Finally, I love this:

Awebb wrote:

Trying to be clever [...] creates a set of problems that were not there in the beginning.

Offline

Board footer

Powered by FluxBB