multiple - python switch()




Ersatz für switch-Anweisung in Python? (20)

Definieren:

def switch1(value, options):
  if value in options:
    options[value]()

erlaubt es Ihnen, eine recht einfache Syntax zu verwenden, wobei die Fälle in einer Karte gebündelt sind:

def sample1(x):
  local = 'betty'
  switch1(x, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye," + local),
      print("!")),
    })

Ich versuchte immer wieder, Switch so zu definieren, dass ich das "Lambda:" loswerden konnte, gab aber auf. Die Definition anpassen:

def switch(value, *maps):
  options = {}
  for m in maps:
    options.update(m)
  if value in options:
    options[value]()
  elif None in options:
    options[None]()

Erlaubt mir, mehrere Fälle demselben Code zuzuordnen und eine Standardoption anzugeben:

def sample(x):
  switch(x, {
    _: lambda: print("other") 
    for _ in 'cdef'
    }, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye,"),
      print("!")),
    None: lambda: print("I dunno")
    })

Jeder replizierte Fall muss in einem eigenen Wörterbuch sein; switch () konsolidiert die Wörterbücher, bevor der Wert nachgeschlagen wird. Es ist immer noch hässlicher, als ich es möchte, aber es hat die grundlegende Effizienz, einen Hash-Lookup auf den Ausdruck zu verwenden, anstatt eine Schleife durch alle Schlüssel.

Ich möchte eine Funktion in Python schreiben, die verschiedene feste Werte basierend auf dem Wert eines Eingabeindex zurückgibt.

In anderen Sprachen würde ich eine switch oder case Anweisung verwenden, aber Python scheint keine switch Anweisung zu haben. Was sind die empfohlenen Python-Lösungen in diesem Szenario?


Die Lösungen, die ich verwende:

Eine Kombination aus zwei der hier veröffentlichten Lösungen, die relativ einfach zu lesen sind und Standardwerte unterstützen.

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}.get(whatToUse, lambda x: x - 22)(value)

woher

.get('c', lambda x: x - 22)(23)

sucht im Diktat nach "lambda x: x - 2" und verwendet es mit x=23

.get('xxx', lambda x: x - 22)(44)

findet es nicht im dict und verwendet den Standard "lambda x: x - 22" mit x=44 .


Erweiterung der Idee "dict as switch". Wenn Sie einen Standardwert für Ihren Switch verwenden möchten:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'

Es gibt ein Muster, das ich aus Twisted Python-Code gelernt habe.

class SMTP:
    def lookupMethod(self, command):
        return getattr(self, 'do_' + command.upper(), None)
    def do_HELO(self, rest):
        return 'Howdy ' + rest
    def do_QUIT(self, rest):
        return 'Bye'

SMTP().lookupMethod('HELO')('foo.bar.com') # => 'Howdy foo.bar.com'
SMTP().lookupMethod('QUIT')('') # => 'Bye'

Sie können es jederzeit verwenden, wenn Sie ein Token versenden und einen erweiterten Code ausführen müssen. In einer Zustandsmaschine hätten Sie die Methoden state_ und dispatch auf self.state . Dieser Schalter kann sauber erweitert werden, indem er von der Basisklasse erbt und eigene do_ Methoden definiert. Oftmals haben Sie nicht einmal do_ Methoden in der Basisklasse.

Edit: Wie genau wird das verwendet?

Bei SMTP erhalten Sie HELO von der Leitung. Der entsprechende Code (aus twisted/mail/smtp.py , modifiziert für unseren Fall) sieht so aus

class SMTP:
    # ...

    def do_UNKNOWN(self, rest):
        raise NotImplementedError, 'received unknown command'

    def state_COMMAND(self, line):
        line = line.strip()
        parts = line.split(None, 1)
        if parts:
            method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
            if len(parts) == 2:
                return method(parts[1])
            else:
                return method('')
        else:
            raise SyntaxError, 'bad syntax'

SMTP().state_COMMAND('   HELO   foo.bar.com  ') # => Howdy foo.bar.com

Du ' HELO foo.bar.com ' (oder du 'QUIT' oder 'RCPT TO: foo' ). Dies wird in parts wie ['HELO', 'foo.bar.com'] . Der tatsächliche Methodennachschlagname wird aus parts[0] .

(Die ursprüngliche Methode wird auch state_COMMAND , weil sie dasselbe Muster verwendet, um eine Zustandsmaschine zu implementieren, dh getattr(self, 'state_' + self.mode) )


Ich habe dafür eine (relativ) flexible und wiederverwendbare Lösung geschaffen. Es kann bei GitHub als dieser Kern gefunden werden . Wenn das Ergebnis der Switch-Funktion aufrufbar ist, wird es automatisch aufgerufen.


Ich habe die einfache Antwort, die ich gesucht habe, nirgendwo auf der Google-Suche gefunden. Aber ich habe es trotzdem herausgefunden. Es ist wirklich ziemlich einfach. Beschlossen, es zu veröffentlichen und vielleicht ein paar weniger Kratzer auf jemandes anderen Kopf zu verhindern. Der Schlüssel ist einfach "in" und Tupel. Hier ist das Switch-Statement-Verhalten mit Fall-Through, einschließlich RANDOM-Fall-Through.

l = ['Dog', 'Cat', 'Bird', 'Bigfoot',
     'Dragonfly', 'Snake', 'Bat', 'Loch Ness Monster']

for x in l:
    if x in ('Dog', 'Cat'):
        x += " has four legs"
    elif x in ('Bat', 'Bird', 'Dragonfly'):
        x += " has wings."
    elif x in ('Snake',):
        x += " has a forked tongue."
    else:
        x += " is a big mystery by default."
    print(x)

print()

for x in range(10):
    if x in (0, 1):
        x = "Values 0 and 1 caught here."
    elif x in (2,):
        x = "Value 2 caught here."
    elif x in (3, 7, 8):
        x = "Values 3, 7, 8 caught here."
    elif x in (4, 6):
        x = "Values 4 and 6 caught here"
    else:
        x = "Values 5 and 9 caught in default."
    print(x)

Bietet:

Dog has four legs
Cat has four legs
Bird has wings.
Bigfoot is a big mystery by default.
Dragonfly has wings.
Snake has a forked tongue.
Bat has wings.
Loch Ness Monster is a big mystery by default.

Values 0 and 1 caught here.
Values 0 and 1 caught here.
Value 2 caught here.
Values 3, 7, 8 caught here.
Values 4 and 6 caught here
Values 5 and 9 caught in default.
Values 4 and 6 caught here
Values 3, 7, 8 caught here.
Values 3, 7, 8 caught here.
Values 5 and 9 caught in default.

Ich habe festgestellt, dass eine gemeinsame Switch-Struktur:

switch ...parameter...
case p1: v1; break;
case p2: v2; break;
default: v3;

kann in Python wie folgt ausgedrückt werden:

(lambda x: v1 if p1(x) else v2 if p2(x) else v3)

oder besser formatiert:

(lambda x:
     v1 if p1(x) else
     v2 if p2(x) else
     v3)

Anstatt eine Aussage zu sein, ist die Python-Version ein Ausdruck, der einen Wert ergibt.


Ich mochte Mark Bies 'Antwort

Da die x Variable zweimal verwendet werden muss, habe ich die Lambda-Funktionen auf parameterlos geändert.

Ich muss mit results[value](value) laufen

In [2]: result = {
    ...:   'a': lambda x: 'A',
    ...:   'b': lambda x: 'B',
    ...:   'c': lambda x: 'C'
    ...: }
    ...: result['a']('a')
    ...: 
Out[2]: 'A'

In [3]: result = {
    ...:   'a': lambda : 'A',
    ...:   'b': lambda : 'B',
    ...:   'c': lambda : 'C',
    ...:   None: lambda : 'Nothing else matters'

    ...: }
    ...: result['a']()
    ...: 
Out[3]: 'A'

Edit: Ich habe festgestellt, dass ich None mit Wörterbüchern verwenden kann. Also würde dies den switch ; case else emulieren switch ; case else switch ; case else


Mein Favorit ist ein wirklich schönes recipe . Du wirst es wirklich mögen. Es ist das, was ich am ehesten bei tatsächlichen Switch-Case-Anweisungen gesehen habe, besonders in Features.

Hier ist ein Beispiel:

# The following example is pretty much the exact use-case of a dictionary,
# but is included for its simplicity. Note that you can include statements
# in each suite.
v = 'ten'
for case in switch(v):
    if case('one'):
        print 1
        break
    if case('two'):
        print 2
        break
    if case('ten'):
        print 10
        break
    if case('eleven'):
        print 11
        break
    if case(): # default, could also just omit condition or 'if True'
        print "something else!"
        # No need to break here, it'll stop anyway

# break is used here to look as much like the real thing as possible, but
# elif is generally just as good and more concise.

# Empty suites are considered syntax errors, so intentional fall-throughs
# should contain 'pass'
c = 'z'
for case in switch(c):
    if case('a'): pass # only necessary if the rest of the suite is empty
    if case('b'): pass
    # ...
    if case('y'): pass
    if case('z'):
        print "c is lowercase!"
        break
    if case('A'): pass
    # ...
    if case('Z'):
        print "c is uppercase!"
        break
    if case(): # default
        print "I dunno what c was!"

# As suggested by Pierre Quentel, you can even expand upon the
# functionality of the classic 'case' statement by matching multiple
# cases in a single shot. This greatly benefits operations such as the
# uppercase/lowercase example above:
import string
c = 'A'
for case in switch(c):
    if case(*string.lowercase): # note the * for unpacking as arguments
        print "c is lowercase!"
        break
    if case(*string.uppercase):
        print "c is uppercase!"
        break
    if case('!', '?', '.'): # normal argument passing style also applies
        print "c is a sentence terminator!"
        break
    if case(): # default
        print "I dunno what c was!"

Mein Lieblings-Python-Rezept für Switch / Case ist:

choices = {'a': 1, 'b': 2}
result = choices.get(key, 'default')

Kurz und einfach für einfache Szenarien.

Vergleichen Sie mit mehr als 11 Zeilen C-Code:

// C Language version of a simple 'switch/case'.
switch( key ) 
{
    case 'a' :
        result = 1;
        break;
    case 'b' :
        result = 2;
        break;
    default :
        result = -1;
}

Sie können sogar mehrere Variablen mithilfe von Tupeln zuweisen:

choices = {'a': (1, 2, 3), 'b': (4, 5, 6)}
(result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))


Wenn Sie einen komplizierten Fallblock haben, können Sie eine Funktionsverzeichnis-Suchtabelle verwenden ...

Wenn Sie dies noch nicht getan haben, ist es eine gute Idee, in Ihren Debugger zu gehen und genau zu sehen, wie das Wörterbuch nach jeder Funktion sucht.

HINWEIS: Verwenden Sie nicht "()" in der Case / Dictionary-Suche oder es ruft jede Ihrer Funktionen auf, wenn der Dictionary / Case-Block erstellt wird. Denken Sie daran, weil Sie jede Funktion nur einmal mit einem Hash-Lookup aufrufen möchten.

def first_case():
    print "first"

def second_case():
    print "second"

def third_case():
    print "third"

mycase = {
'first': first_case, #do not use ()
'second': second_case, #do not use ()
'third': third_case #do not use ()
}
myfunc = mycase['first']
myfunc()

Zusätzlich zu den Dictionary-Methoden (die ich wirklich mag, BTW), können Sie auch if-elif-else verwenden, um die switch / case / default-Funktionalität zu erhalten:

if x == 'a':
    # Do the thing
elif x == 'b':
    # Do the other thing
if x in 'bc':
    # Fall-through by not using elif, but now the default case includes case 'a'!
elif x in 'xyz':
    # Do yet another thing
else:
    # Do the default

Das ist natürlich nicht identisch mit switch / case - Sie können nicht so leicht durchfallen wie die Pause; Aussage, aber Sie können einen komplizierteren Test haben. Seine Formatierung ist schöner als eine Reihe von verschachtelten ifs, obwohl es funktional näher ist.


Expanding on Greg Hewgill's answer - We can encapsulate the dictionary-solution using a decorator:

def case(callable):
    """switch-case decorator"""
    class case_class(object):
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs

        def do_call(self):
            return callable(*self.args, **self.kwargs)

return case_class

def switch(key, cases, default=None):
    """switch-statement"""
    ret = None
    try:
        ret = case[key].do_call()
    except KeyError:
        if default:
            ret = default.do_call()
    finally:
        return ret

This can then be used with the @case -decorator

@case
def case_1(arg1):
    print 'case_1: ', arg1

@case
def case_2(arg1, arg2):
    print 'case_2'
    return arg1, arg2

@case
def default_case(arg1, arg2, arg3):
    print 'default_case: ', arg1, arg2, arg3

ret = switch(somearg, {
    1: case_1('somestring'),
    2: case_2(13, 42)
}, default_case(123, 'astring', 3.14))

print ret

The good news are that this has already been done in NeoPySwitch -module. Simply install using pip:

pip install NeoPySwitch

I was quite confused after reading the answer, but this cleared it all up:

def numbers_to_strings(argument):
    switcher = {
        0: "zero",
        1: "one",
        2: "two",
    }
    return switcher.get(argument, "nothing")

This code is analogous to:

function(argument){
    switch(argument) {
        case 0:
            return "zero";
        case 1:
            return "one";
        case 2:
            return "two";
        default:
            return "nothing";
    }
}

Check the Source for more about dictionary mapping to functions.


I would just use if/elif/else statements. I think that it's good enough to replace the switch statement.


Inspired by this awesome answer . Requires no other code. Not tested. Realized that fall through does not work properly.

for case in [expression]:
    if case == 1:
        do_stuff()
        # Fall through

    if case in range(2, 5):
        do_other_stuff()
        break

    do_default()

Just mapping some a key to some code is not really and issue as most people have shown using the dict. The real trick is trying to emulate the whole drop through and break thing. I don't think I've ever written a case statement where I used that "feature". Here's a go at drop through.

def case(list): reduce(lambda b, f: (b | f[0], {False:(lambda:None),True:f[1]}[b | f[0]]())[0], list, False)

case([
    (False, lambda:print(5)),
    (True, lambda:print(4))
])

I was really imagining it as a single statement. I hope you'll pardon the silly formatting.

reduce(
    initializer=False,
    function=(lambda b, f:
        ( b | f[0]
        , { False: (lambda:None)
          , True : f[1]
          }[b | f[0]]()
        )[0]
    ),
    iterable=[
        (False, lambda:print(5)),
        (True, lambda:print(4))
    ]
)

I hope that's valid python. It should give you drop through. of course the boolean checks could be expressions and if you wanted them to be evaluated lazily you could wrap them all in a lambda. I wouldn't be to hard to make it accept after executing some of the items in the list either. Just make the tuple (bool, bool, function) where the second bool indicates whether or not to break or drop through.


class Switch:
    def __init__(self, value): self._val = value
    def __enter__(self): return self
    def __exit__(self, type, value, traceback): return False # Allows traceback to occur
    def __call__(self, *mconds): return self._val in mconds

from datetime import datetime
with Switch(datetime.today().weekday()) as case:
    if case(0):
        # Basic usage of switch
        print("I hate mondays so much.")
        # Note there is no break needed here
    elif case(1,2):
        # This switch also supports multiple conditions (in one line)
        print("When is the weekend going to be here?")
    elif case(3,4): print("The weekend is near.")
    else:
        # Default would occur here
        print("Let's go have fun!") # Didn't use case for example purposes

class switch(object):
    value = None
    def __new__(class_, value):
        class_.value = value
        return True

def case(*args):
    return any((arg == switch.value for arg in args))

Verwendung:

while switch(n):
    if case(0):
        print "You typed zero."
        break
    if case(1, 4, 9):
        print "n is a perfect square."
        break
    if case(2):
        print "n is an even number."
    if case(2, 3, 5, 7):
        print "n is a prime number."
        break
    if case(6, 8):
        print "n is an even number."
        break
    print "Only single-digit numbers are allowed."
    break

Tests:

n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.




python