1717 Stimmen

Ersetzungen für switch-Anweisung in Python?

Ich möchte eine Funktion in Python schreiben, die je nach dem Wert eines Eingabeindexes verschiedene feste Werte zurückgibt.

In anderen Sprachen würde ich ein switch o case Anweisung, aber Python scheint nicht über eine switch Aussage. Was sind die empfohlenen Python-Lösungen in diesem Szenario?

77 Stimmen

PEP zum Thema, verfasst von Guido selbst: PEP 3103

28 Stimmen

@chb In diesem PEP erwähnt Guido nicht, dass if/elif-Ketten auch eine klassische Fehlerquelle sind. Es ist ein sehr anfälliges Konstrukt.

15 Stimmen

Was bei allen Lösungen fehlt, ist die Erkennung von doppelte Fallwerte . Als Fail-Fast-Prinzip kann dies ein größerer Verlust sein als die Leistung oder die Fallthrough-Funktion.

117voto

adamh Punkte 1243
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.

70 Stimmen

Dies ist nicht bedrohungssicher. Wenn mehrere Schalter gleichzeitig betätigt werden, nehmen alle Schalter den Wert des letzten Schalters an.

58 Stimmen

Während @francescortiz wahrscheinlich Fadensicherheit meint, ist es auch nicht bedrohungssicher. Es bedroht die Werte der Variablen!

7 Stimmen

Das Problem der Threadsicherheit kann wahrscheinlich durch die Verwendung von thread-lokale Speicherung . Oder es könnte ganz vermieden werden, indem eine Instanz zurückgegeben und diese für die Fallvergleiche verwendet wird.

67voto

John Doe Punkte 3284

Mein Favorit ist ein wirklich schönes Rezept . Es ist das, was den tatsächlichen switch case-Anweisungen am nächsten kommt, vor allem in Features.

class switch(object):
    def __init__(self, value):
        self.value = value
        self.fall = False

    def __iter__(self):
        """Return the match method once, then stop"""
        yield self.match
        raise StopIteration

    def match(self, *args):
        """Indicate whether or not to enter a case suite"""
        if self.fall or not args:
            return True
        elif self.value in args: # changed for v1.5, see below
            self.fall = True
            return True
        else:
            return False

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!"

In einigen Kommentaren wurde darauf hingewiesen, dass eine Kontextmanagerlösung mit with foo as case statt for case in foo könnte sauberer sein, und für große Schalteranweisungen könnte das lineare statt quadratische Verhalten ein netter Zug sein. Ein Teil des Wertes dieser Antwort mit einer for-Schleife ist die Möglichkeit, Unterbrechungen und Fallthroughs zu haben, und wenn wir bereit sind, mit der Wahl unserer Schlüsselwörter ein wenig zu spielen, können wir das auch in einem Kontextmanager erreichen:

class Switch:
    def __init__(self, value):
        self.value = value
        self._entered = False
        self._broken = False
        self._prev = None

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        if self._broken:
            return False

        if not self._entered:
            if values and self.value not in values:
                return False
            self._entered, self._prev = True, values
            return True

        if self._prev is None:
            self._prev = values
            return True

        if self._prev != values:
            self._broken = True
            return False

        if self._prev == values:
            self._prev = None
            return False

    @property
    def default(self):
        return self()

Hier ist ein Beispiel:

# Prints 'bar' then 'baz'.
with Switch(2) as case:
    while case(0):
        print('foo')
    while case(1, 2, 3):
        print('bar')
    while case(4, 5):
        print('baz')
        break
    while case.default:
        print('default')
        break

3 Stimmen

Ich würde ersetzen for case in switch() con with switch() as case ist sinnvoller, da sie nur einmal ausgeführt werden muss.

5 Stimmen

@Skirmantas: Beachten Sie, dass with lässt nicht zu break Die Möglichkeit des Durchfallens ist also nicht mehr gegeben.

5 Stimmen

Entschuldigen Sie, dass ich mich nicht mehr bemüht habe, dies selbst herauszufinden: eine ähnliche Antwort wie oben ist nicht thread-safe. Ist dies?

66voto

Ian Bell Punkte 641
class Switch:
    def __init__(self, value):
        self.value = value

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        return self.value in values

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

13 Stimmen

Der Einsatz von Kontextmanagern ist eine gute kreative Lösung. Ich würde empfehlen, eine kleine Erklärung und vielleicht einen Link zu einigen Informationen über Kontextmanager hinzuzufügen, um diesem Beitrag etwas, nun ja, Kontext zu geben ;)

2 Stimmen

Ich mag if/elif-Ketten nicht besonders, aber dies ist sowohl die kreativste als auch die praktischste aller Lösungen, die ich unter Verwendung der bestehenden Syntax von Python gesehen habe.

2 Stimmen

Das ist wirklich schön. Ein Verbesserungsvorschlag ist das Hinzufügen einer (öffentlichen) value Eigenschaft der Klasse Switch zu, damit Sie die case.value innerhalb der Anweisung.

52voto

Es gibt ein Muster, das ich von 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 immer dann verwenden, wenn Sie ein Token versenden und einen erweiterten Code ausführen müssen. In einem Zustandsautomaten würden Sie state_ Methoden, und Versand an self.state . Dieser Schalter kann sauber erweitert werden, indem Sie von der Basisklasse erben und Ihre eigene Klasse definieren do_ Methoden. Oftmals müssen Sie nicht einmal do_ Methoden in der Basisklasse.

Bearbeiten: Wie genau wird das verwendet?

Im Falle von SMTP erhalten Sie HELO von der Leitung. Der entsprechende Code (aus twisted/mail/smtp.py (modifiziert für unseren Fall) sieht wie folgt 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

Sie erhalten ' HELO foo.bar.com ' (oder Sie bekommen vielleicht 'QUIT' o 'RCPT TO: foo' ). Dies wird tokenisiert in parts als ['HELO', 'foo.bar.com'] . Der eigentliche Name des Methodennachschlagewerks wird aus parts[0] .

(Die ursprüngliche Methode wird auch als state_COMMAND weil es dasselbe Muster zur Implementierung eines Zustandsautomaten verwendet, d. h. getattr(self, 'state_' + self.mode) )

4 Stimmen

Ich sehe den Vorteil dieses Musters gegenüber dem direkten Aufruf der Methoden nicht: SMTP().do_HELO('foo.bar.com') OK, es kann gemeinsamen Code in der lookupMethod geben, aber da dieser auch von der Unterklasse überschrieben werden kann, sehe ich nicht, was man von der Umleitung hat.

1 Stimmen

Sie würden nicht wissen, welche Methode Sie im Voraus aufrufen müssen, d.h. 'HELO' kommt von einer Variablen.

0 Stimmen

Darf ich vorschlagen, einfach: eval('SMTP().do_' + command)('foo.bar.com')

31voto

Lösung zum Ausführen von Funktionen:

result = {
    'case1':     foo1, 
    'case2':     foo2,
    'case3':     foo3,
}.get(option)(parameters_optional)

wobei foo1(), foo2() und foo3() Funktionen sind

Beispiel 1 (mit Parametern):

option = number['type']
result = {
    'number':     value_of_int,  # result = value_of_int(number['value'])
    'text':       value_of_text, # result = value_of_text(number['value'])
    'binary':     value_of_bin,  # result = value_of_bin(number['value'])
}.get(option)(value['value'])

Beispiel 2 (keine Parameter):

option = number['type']
result = {
    'number':     func_for_number, # result = func_for_number()
    'text':       func_for_text,   # result = func_for_text()
    'binary':     func_for_bin,    # result = func_for_bin()
}.get(option)()

2 Stimmen

Ja, zum Beispiel wenn Ihre Variable option=="case2" Ihr Ergebnis=foo2()

0 Stimmen

Und so weiter und so fort.

0 Stimmen

Ja, ich verstehe den Zweck. Aber meine Sorge ist, dass, wenn Sie nur wollen foo2() die foo1() , foo3() y default() Alle Funktionen werden auch ausgeführt, was bedeutet, dass die Dinge lange dauern können.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X