views:

560

answers:

6

Alright, so my title sucked. An example works better:

input = 'check yahoo.com'

I want to parse input, using the first word as the "command", and the rest of the string as a parameter. Here's the simple version of how my non-Pythonic mind is coding it:

if len(input) > 0:
    a = input.split(' ')
    if a[0] == 'check':
        if len(a) > 1:
            do_check(a[1])
    elif a[0] == 'search':
        if len(a) > 1:
            do_search(a[1])

I like Python because it makes normally complicated things into rather simple things. I'm not too experienced with it, and I am fairly sure there's a much better way to do these things... some way more pythonic. I've seen some examples of people replacing switch statements with dicts and lambda functions, while other people simply recommended if..else nests.

+30  A: 
dispatch = {
  'check': do_check,
  'search': do_search,
}
cmd, _, arg = input.partition(' ')
if cmd in dispatch:
    dispatch[cmd](arg)
else:
    do_default(cmd, arg)
MizardX
Instead of do_default there, you may want to either do an if a[0] in dispatch: and pass the a[0] to the default, or simply replace do_default with lambda arg: do_default(a[0], arg) -- that way you get the command passed.
Cody Brocious
You could replace the last four lines with: dispatch.get(cmd, do_default)(arg)
John Fouhy
@John: That way do_default won't get the cmd.
Paolo Bergantino
so replace last 4 lines with: dispatch.get(cmd, lambda a:do_default(cmd, a))(arg) And now do_default receives 'cmd'.
Abgan
@Abgan: I've considered that, but I think an explicit if-statement looks better than a lambda. (or functools.partial for that matter)
MizardX
+1: simple If; Lambdas are never helpful.
S.Lott
@MizardX: That looks really nice. Can you explain what the underscore does in the fifth line?
theycallmemorty
It's a normal variable. It's usually used to indicate that you don't care about the value. In this case it's the delimiter itself, ' '.
MizardX
Re: the underscore, just be careful when gettext is involved as a common practice is to import gettext() as _()
Wayne Koorts
A: 

If you're looking for a one liner 'pythonic' approach to this you can use this:


def do_check(x): print 'checking for:', x
def do_search(x): print 'searching for:', x

input = 'check yahoo.com'
{'check': do_check}.get(input.split()[0], do_search)(input.split()[1])
# checking for: yahoo.com

input = 'search google.com'
{'check': do_check}.get(input.split()[0], do_search)(input.split()[1])
# searching for: google.com

input = 'foo bar.com'
{'check': do_check}.get(input.split()[0], do_search)(input.split()[1])
# searching for: bar.com
adam
Well, it certainly doesn't simplify Tom's example, but it takes his 8 lines of code and does the same thing in just one line.
adam
on second glace, he used an elif. I had initially thought he used an else. I need to revise my code. Sorry for the confusion.
adam
dude this is so wrong. your dictionary only has "check", as if one would need a dictionary to choose between "check" and "search" because an "if" statement is not sufficient!
hasen j
This is way too complicated. If I see this code I would certainly pull my hair out. It's just an "if elif" construct for god sake.
miya
hey, it's not that bad! You can extend the dictionary with as many commands as you like, leaving one default (probably not do_search, but some error handler) in {}.get() - and it's still more compact than if-elif-elif-....-else construct.
Abgan
+3  A: 

This lets you avoid giving each command name twice; function names are used almost directly as command names.

class CommandFunctions:
    def c_check(self, arg):
        print "checking", arg

    def c_search(self, arg):
        print "searching for", arg

    def c_compare(self, arg1, arg2):
        print "comparing", arg1, "with", arg2

    def execute(self, line):
        words = line.split(' ')
        fn = getattr(self, 'c_' + words[0], None)
        if fn is None:
            import sys
            sys.stderr.write('error: no such command "%s"\n' % words[0])
            return
        fn(*words[1:])

cf = CommandFunctions()
import sys
for line in sys.stdin:
    cf.execute(line.strip())
rob mayoff
This can even be better by using an error handling method as the default value for gettr (instead of None), so you don't have to check for unknown commmands explicitly
Ber
I thought about doing that, but the called function doesn't get words[0] so it would not be able to print the unknown command in the error message.
rob mayoff
+4  A: 

I am fairly sure there's a much better way to do these things... some way more pythonic.

Not really. You code is simple, clear, obvious and English-like.

I've seen some examples of people replacing switch statements with dicts and lambda functions,

Yes, you've seen them and they're not clear, obvious or English-like. They exist because some people like to wring their hands over the switch statement.

while other people simply recommended if..else nests.

Correct. They work. They're simple, clear, ...

Your code is good. Leave it alone. Move on.

S.Lott
A: 

Disregard, I just realized that my answer was similar to one of the other answers - and apparently there's no delete key :)

John C
A: 

Variation on @MizardX's answer:

from collections import defaultdict

dispatch = defaultdict(do_default, check=do_check, search=do_search)
cmd, _, arg = input.partition(' ')
dispatch[cmd](arg)
J.F. Sebastian