Pythonischer Weg, um "if x: return x" -Anweisungen zu vermeiden

218

Ich habe eine Methode, die 4 andere Methoden nacheinander aufruft, um nach bestimmten Bedingungen zu suchen, und sofort zurückkehrt (ohne die folgenden zu prüfen), wenn man etwas Wahres zurückgibt.

def check_all_conditions():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

Dies scheint eine Menge Gepäckcode. Anstelle jeder zweizeiligen if-Anweisung würde ich lieber Folgendes tun:

x and return x

Aber das ist ungültiges Python. Vermisse ich hier eine einfache, elegante Lösung? In dieser Situation können diese vier Prüfmethoden übrigens teuer sein, daher möchte ich sie nicht mehrmals aufrufen.

Bernard
quelle
7
Was sind diese x? Sind sie nur wahr / falsch oder sind sie Datenstrukturen, die einige Informationen enthalten, wobei None oder ähnliches als Sonderfall verwendet wird, um das Fehlen von Daten anzuzeigen? Wenn es das letztere ist, sollten Sie mit ziemlicher Sicherheit stattdessen Ausnahmen verwenden.
Nathaniel
13
@gerrit Der oben dargestellte Code ist ein hypothetischer / Pseudocode, der bei der Codeüberprüfung nicht zum Thema gehört. Wenn der Autor des Beitrags möchte, dass sein realer, tatsächlicher Arbeitscode überprüft wird, kann er gerne auf Code Review posten.
Phrancis
4
Warum denkst du x and return xist besser als if x: return x? Letzteres ist weitaus besser lesbar und damit wartbar. Sie sollten sich nicht zu viele Gedanken über die Anzahl der Zeichen oder Zeilen machen. Lesbarkeit zählt. Sie haben sowieso genau die gleiche Anzahl von Nicht-Leerzeichen, und wenn Sie es wirklich müssen, if x: return xfunktionieren sie in nur einer Zeile einwandfrei.
Marcelm
3
Bitte klären Sie, ob Sie sich für die tatsächlichen Werte interessieren oder wirklich nur einen Booleschen Wert zurückgeben müssen. Dies macht einen Unterschied, welche Optionen verfügbar sind und welche die Absicht klarer kommunizieren. Die Benennung legt nahe, dass Sie nur einen Booleschen Wert benötigen. Es macht auch einen Unterschied, ob es wichtig ist, mehrere Aufrufe dieser Funktionen zu vermeiden. Es kann auch wichtig sein, ob die Funktionen einen oder andere Parametersätze annehmen. Ohne diese Klarstellungen fällt diese Frage meiner Meinung nach in eine unklare, zu breite oder auf Meinungen basierende Frage.
jpmc26
7
@ jpmc26 OP spricht explizit von wahrheitsgemäßen Rückgabewerten, und dann gibt sein Code x(im Gegensatz zu bool(x)) zurück. Ich denke, es ist sicher anzunehmen, dass die Funktionen von OP alles zurückgeben können, und er möchte als erstes alles, was wahr ist.
Timgeb

Antworten:

278

Sie könnten eine Schleife verwenden:

conditions = (check_size, check_color, check_tone, check_flavor)
for condition in conditions:
    result = condition()
    if result:
        return result

Dies hat den zusätzlichen Vorteil, dass Sie jetzt die Anzahl der Bedingungen variabel machen können.

Sie können map()+ verwenden filter()(die Python 3-Versionen, verwenden Sie die future_builtinsVersionen in Python 2), um den ersten solchen übereinstimmenden Wert zu erhalten:

try:
    # Python 2
    from future_builtins import map, filter
except ImportError:
    # Python 3
    pass

conditions = (check_size, check_color, check_tone, check_flavor)
return next(filter(None, map(lambda f: f(), conditions)), None)

aber ob dies besser lesbar ist, ist umstritten.

Eine andere Option ist die Verwendung eines Generatorausdrucks:

conditions = (check_size, check_color, check_tone, check_flavor)
checks = (condition() for condition in conditions)
return next((check for check in checks if check), None)
Martijn Pieters
quelle
27
Wenn die Bedingungen wirklich nur Bedingungen sind, dh Boolesche Werte, können Sie in Ihrem ersten Vorschlag auch die integrierte Funktion anyanstelle der Schleife verwenden. return any(condition() for condition in conditions)
4
@Leonhard: anyhat fast die gleiche Implementierung im Inneren. Aber es sieht viel besser aus, bitte
poste
13
Die Lesbarkeit übertrifft fast alle anderen Überlegungen. Sie sagen, Karte / Filter ist "umstritten", ich habe meine Stimme für unbestreitbar hässlich abgegeben. Vielen Dank natürlich, aber wenn jemand in meinem Team eine Karte / einen Filter für diesen Code einfügt, würde ich sie an ein anderes Team übertragen oder sie der Bettpfanne zuweisen.
Kevin J. Rice
15
Ist dieser unlesbare Codeblock wirklich "pythonisch"? Und vor allem die Idee des Reißverschlusses conditionsund arguments? Dies ist meiner Meinung nach viel schlimmer als der ursprüngliche Code, dessen Analyse durch meinen Brainparser etwa 10 Sekunden dauert.
yo
34
"Bevorzugen Sie Python", sagten sie. "Perl ist unlesbar", sagten sie. Und dann passierte das : return next((check for check in checks if check), None).
Jja
393

Alternativ zu Martijns feiner Antwort könnten Sie verketten or. Dies gibt den ersten Wahrheitswert zurück, oder Nonewenn es keinen Wahrheitswert gibt:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor() or None

Demo:

>>> x = [] or 0 or {} or -1 or None
>>> x
-1
>>> x = [] or 0 or {} or '' or None
>>> x is None
True
timgeb
quelle
9
Sicher, aber es wird mühsam, schnell zu lesen, wenn es mehr als ein paar Optionen gibt. Außerdem können Sie mit meinem Ansatz eine variable Anzahl von Bedingungen verwenden.
Martijn Pieters
14
@MartijnPieters, mit dem Sie \jeden Scheck in eine eigene Zeile setzen können.
Caridorc
12
@MartijnPieters Ich habe nie angedeutet, dass meine Antwort besser ist als deine, ich mag deine Antwort auch :)
timgeb
38
@Caridorc: Ich mag \es nicht, die logische Linie zu erweitern. Verwenden Sie stattdessen nach Möglichkeit Klammern. also return (....)mit nach Bedarf eingefügten Zeilenumbrüchen. Trotzdem wird das eine lange logische Linie sein.
Martijn Pieters
47
Ich denke, das ist die bessere Lösung. Das Argument "es wird langweilig [..], wenn es mehr als ein paar Optionen gibt" ist umstritten, da eine einzelne Funktion sowieso keine exorbitante Anzahl von Überprüfungen durchführen sollte. Wenn dies erforderlich ist, sollten die Prüfungen in mehrere Funktionen aufgeteilt werden.
BlueRaja - Danny Pflughoeft
88

Ändere es nicht

Es gibt andere Möglichkeiten, dies zu tun, wie die verschiedenen anderen Antworten zeigen. Keiner ist so klar wie Ihr Originalcode.

Jack Aidley
quelle
39
Ich würde dagegen argumentieren, aber Ihr Vorschlag ist legitim, um geäußert zu werden. Persönlich finde ich meine Augen angespannt, wenn ich versuche, das OP zu lesen, während zum Beispiel die Lösung von timgeb sofort klickt.
Reti43
3
Es ist wirklich eine Ansichtssache. Ich persönlich würde die Zeilenumbrüche danach entfernen :, da ich if x: return xdas für ziemlich gut halte und die Funktion dadurch kompakter aussieht. Aber das kann nur ich sein.
yo '22
2
Es sind nicht nur Sie. Die Verwendung orals Timgeb ist eine richtige und gut verstandene Redewendung. Viele Sprachen haben dies; vielleicht , wenn es heißt orelsees noch klar, aber auch die gute alte or(oder ||in anderen Sprachen) ist gemeint als Alternative verstanden werden , um zu versuchen , wenn die erste „funktioniert nicht.“
Ray Toal
1
@ RayToal: Das Importieren von Redewendungen aus anderen Sprachen ist eine großartige Möglichkeit, Code zu verschleiern.
Jack Aidley
1
Manchmal ja sicher! Es kann auch eine Möglichkeit sein, den Geist zu öffnen und neue und bessere Muster und Paradigmen zu entdecken, die noch niemand zuvor versucht hat. Der Stil entsteht dadurch, dass Menschen neue Dinge ausleihen, teilen und ausprobieren. Funktioniert in beide Richtungen. Wie auch immer, ich habe noch nie die Verwendung von orunpythonisch oder in irgendeiner Weise verschleiert gehört, aber das ist sowieso Ansichtssache - wie es sein sollte.
Ray Toal
83

In praktisch der gleichen Antwort wie Timgeb, aber Sie könnten Klammern für eine schönere Formatierung verwenden:

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )
Wayne Werner
quelle
8
Bitte helfen Sie allen, diese Antwort auf den 1. Platz zu bringen. tue deinen Teil !
Gyom
74

Nach dem Gesetz von Curly können Sie diesen Code lesbarer machen, indem Sie zwei Probleme aufteilen:

  • Welche Dinge überprüfe ich?
  • Hat sich eines als wahr erwiesen?

in zwei Funktionen:

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions():
    for condition in all_conditions():
        if condition:
            return condition
    return None

Dies vermeidet:

  • komplizierte logische Strukturen
  • wirklich lange Schlangen
  • Wiederholung

... unter Beibehaltung eines linearen, leicht lesbaren Flusses.

Abhängig von Ihren besonderen Umständen können Sie wahrscheinlich auch noch bessere Funktionsnamen finden, die die Lesbarkeit verbessern.

Phil Frost
quelle
Ich mag dieses, obwohl Richtig / Falsch in Bedingung / Keine geändert werden sollte, um der Frage zu entsprechen.
Malcolm
2
Das ist mein Favorit! Es kommt auch mit verschiedenen Prüfungen und Argumenten zurecht. Möglicherweise für dieses spezielle Beispiel überarbeitet, aber ein wirklich nützliches Werkzeug für zukünftige Probleme!
rjh
4
Beachten Sie, dass dies return Nonenicht erforderlich ist, da Funktionen Nonestandardmäßig zurückgegeben werden. Es ist jedoch nichts Falsches daran, Noneexplizit zurückzukehren, und ich finde es gut, dass Sie sich dafür entschieden haben.
Timgeb
1
Ich denke, dieser Ansatz wäre besser mit einer lokalen Funktionsdefinition zu implementieren.
Jack Aidley
1
@timgeb "Explizit ist besser als implizit", Zen of Python .
jpmc26
42

Dies ist eine Variante von Martijns erstem Beispiel. Es wird auch der Stil "Sammlung von Callables" verwendet, um Kurzschlüsse zu ermöglichen.

Anstelle einer Schleife können Sie die eingebaute verwenden any.

conditions = (check_size, check_color, check_tone, check_flavor)
return any(condition() for condition in conditions) 

Beachten Sie, dass anyein Boolescher Wert zurückgegeben wird. Wenn Sie also den genauen Rückgabewert der Prüfung benötigen, funktioniert diese Lösung nicht. anynicht unterscheiden zwischen 14, 'red', 'sharp', 'spicy'wird als Rückgabewerte, sie alle als zurückgeschickt werden True.


quelle
Sie können next(itertools.ifilter(None, (c() for c in conditions)))den tatsächlichen Wert ermitteln, ohne ihn in einen Booleschen Wert umzuwandeln.
Kojiro
1
Gibt es anytatsächlich Kurzschluss?
zwol
1
@zwol Ja, versuchen Sie es mit einigen Beispielfunktionen oder sehen Sie docs.python.org/3/library/functions.html
1
Dies ist weniger lesbar als das Verketten der 4 Funktionen mit 'oder' und zahlt sich nur aus, wenn die Anzahl der Bedingungen groß oder dynamisch ist.
rjh
1
@rjh Es ist perfekt lesbar; Es ist nur ein Listenliteral und ein Verständnis. Ich würde es vorziehen, weil meine Augen nach ungefähr dem dritten glasig werdenx = bar(); if x: return x;
Blacklight Shining
27

Haben Sie darüber nachgedacht, if x: return xalles in eine Zeile zu schreiben ?

def check_all_conditions():
    x = check_size()
    if x: return x

    x = check_color()
    if x: return x

    x = check_tone()
    if x: return x

    x = check_flavor()
    if x: return x

    return None

Dies ist nicht weniger repetitiv als das, was Sie hatten, aber IMNSHO liest es sich ziemlich flüssiger.

zwol
quelle
24

Ich bin ziemlich überrascht, dass niemand das eingebaute Gerät erwähnt hat, das anyfür diesen Zweck hergestellt wurde:

def check_all_conditions():
    return any([
        check_size(),
        check_color(),
        check_tone(),
        check_flavor()
    ])

Beachten Sie, dass diese Implementierung zwar wahrscheinlich die klarste ist, jedoch alle Überprüfungen auswertet, auch wenn es sich um die erste handelt True.


Wenn Sie bei der ersten fehlgeschlagenen Prüfung wirklich anhalten müssen, sollten Sie überlegen, mit reducewelcher Methode eine Liste in einen einfachen Wert konvertiert werden soll:

def check_all_conditions():
    checks = [check_size, check_color, check_tone, check_flavor]
    return reduce(lambda a, f: a or f(), checks, False)

reduce(function, iterable[, initializer]): Wenden Sie die Funktion von zwei Argumenten kumulativ von links nach rechts auf die Elemente von iterable an, um das iterable auf einen einzelnen Wert zu reduzieren. Das linke Argument x ist der akkumulierte Wert und das rechte Argument y ist der Aktualisierungswert aus dem iterierbaren Wert. Wenn der optionale Initialisierer vorhanden ist, wird er in der Berechnung vor den Elementen der Iterable platziert

In deinem Fall:

  • lambda a, f: a or f()ist die Funktion, die prüft, ob entweder der Akku aoder die aktuelle Prüfung vorhanden f()ist True. Beachten Sie, dass , wenn aist True, f()werden nicht bewertet.
  • checksenthält Prüffunktionen (der fArtikel aus dem Lambda)
  • False ist der Anfangswert, sonst würde keine Prüfung stattfinden und das Ergebnis wäre immer True

anyund reducesind grundlegende Werkzeuge für die funktionale Programmierung. Ich empfehle Ihnen dringend, diese zu trainieren, und mapdas ist auch großartig!

ngasull
quelle
9
anyfunktioniert nur, wenn die Prüfungen tatsächlich einen booleschen Wert zurückgeben, wörtlich Trueoder False, aber die Frage gibt das nicht an. Sie müssen verwenden reduce, um den tatsächlichen Wert zurückzugeben, der von der Prüfung zurückgegeben wird. Es ist auch leicht genug zu vermeiden, alle Überprüfungen mit anyeinem Generator auszuwerten , z any(c() for c in (check_size, check_color, check_tone, check_flavor)). Wie in Leonhards Antwort
David Z
Ich mag Ihre Erklärung und Verwendung von reduce. Wie @DavidZ glaube ich, dass Ihre Lösung mit anyeinen Generator verwenden sollte, und es muss darauf hingewiesen werden, dass es auf die Rückgabe Trueoder beschränkt ist False.
Timgeb
1
@ DavidZ arbeitet tatsächlich anymit wahrheitsgemäßen Werten: any([1, "abc", False]) == Trueundany(["", 0]) == False
ngasull
3
@ Blint Entschuldigung, ich war nicht klar. Ziel der Frage ist es, das Ergebnis der Prüfung zurückzugeben (und nicht nur anzugeben, ob die Prüfung erfolgreich war oder fehlgeschlagen ist). Ich habe darauf hingewiesen, dass dies anynur funktioniert , wenn tatsächliche boolesche Werte von den Prüffunktionen zurückgegeben werden.
David Z
19

Wenn Sie dieselbe Codestruktur wünschen, können Sie ternäre Anweisungen verwenden!

def check_all_conditions():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

Ich denke, das sieht gut und klar aus, wenn man es sich ansieht.

Demo:

Screenshot davon läuft

Phinet
quelle
7
Was ist mit den kleinen ASCII-Fischen über Ihrem Terminal?
36
@LegoStormtroopr Ich benutze die Fischschale, also dekoriere ich sie mit einem ASCII-Aquarium, um mich glücklich zu machen. :)
Phinet
3
Vielen Dank für den feinen Fisch (und die Farben übrigens, welcher Editor ist das?)
Mathreadler
4
Sie können Fisch auf fishshell.com bekommen , und die Konfigurationsdatei für die ASCII hier pastebin.com/yYVYvVeK , auch der Editor ist erhabener Text.
Phinet
9
x if x else <something>kann nur aufx or <something>
5

Für mich ist die beste Antwort die von @ phil-frost, gefolgt von @ wayne-werner's.

Was ich interessant finde, ist, dass niemand etwas darüber gesagt hat, dass eine Funktion viele verschiedene Datentypen zurückgibt, was es dann zwingend erforderlich macht, den Typ von x selbst zu überprüfen, um weitere Arbeiten durchzuführen.

Also würde ich die Antwort von @ PhilFrost mit der Idee mischen, einen einzigen Typ beizubehalten:

def all_conditions(x):
    yield check_size(x)
    yield check_color(x)
    yield check_tone(x)
    yield check_flavor(x)

def assessed_x(x,func=all_conditions):
    for condition in func(x):
        if condition:
            return x
    return None

Beachten Sie, dass dies xals Argument übergeben wird, aber auch all_conditionsals übergebener Generator für Überprüfungsfunktionen verwendet wird, bei denen alle eine xzu überprüfende Funktion erhalten und Trueoder zurückgeben False. Durch die Verwendung von funcmit all_conditionsals Standardwert, können Sie verwenden assessed_x(x), oder Sie können über einen weiteren personalisierten Generator übergeben func.

Auf diese Weise erhalten Sie, xsobald ein Scheck bestanden wurde, aber es wird immer der gleiche Typ sein.

juandesant
quelle
4

Im Idealfall würde ich die check_ Funktionen neu schreiben , um sie zurückzugeben, Trueoder Falsenicht einen Wert. Ihre Schecks werden dann

if check_size(x):
    return x
#etc

Vorausgesetzt x, Ihre Funktion ist nicht unveränderlich, kann Ihre Funktion sie dennoch ändern (obwohl sie sie nicht neu zuweisen kann) - aber eine aufgerufene Funktion checksollte sie ohnehin nicht wirklich ändern .

RoadieRich
quelle
3

Eine kleine Variation von Martijns erstem Beispiel oben, die das if innerhalb der Schleife vermeidet:

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status
Mathreadler
quelle
Macht es? Sie machen immer noch einen Vergleich. In Ihrer Version werden Sie auch alle Bedingungen unabhängig davon überprüfen und nicht bei der ersten Instanz eines Wahrheitswertes zurückkehren. Abhängig davon, wie teuer diese Funktionen sind, ist dies möglicherweise nicht wünschenswert.
Reti43
4
@ Reti43: Status or c()Überspringt / kurzschließt die Auswertung von Anrufen, c()wenn dies Statuswahr ist, sodass der Code in dieser Antwort nicht mehr Funktionen als den OP-Code aufzurufen scheint. stackoverflow.com/questions/2580136/…
Neil Slater
2
@NeilSlater True. Der einzige Nachteil, den ich sehe, ist, dass der beste Fall jetzt in O (n) ist, weil der Listiterator n-mal nachgeben muss, wenn es vorher O (1) war, wenn die erste Funktion etwas Wahres in O (1) zurückgibt.
Timgeb
1
Ja gute Punkte. Ich muss nur hoffen, dass die Auswertung von c () etwas länger dauert als das Schleifen einer fast leeren Schleife. Das Überprüfen des Geschmacks kann einen ganzen Abend dauern, zumindest wenn es gut ist.
Mathreadler
3

Ich mag @ timgeb's. In der Zwischenzeit möchte ich hinzufügen, dass das Ausdrücken Nonein der returnAnweisung nicht erforderlich ist, da die Sammlung orgetrennter Anweisungen ausgewertet wird und die ersten nicht null, nicht leer, keine keine zurückgegeben werden und wenn keine vorhanden Nonesind, zurückgegeben wird ob es eine gibt Noneoder nicht!

Meine check_all_conditions()Funktion sieht also so aus:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor()

Mit timeitmit habe number=10**7ich mir die Laufzeit einiger Vorschläge angesehen. Zum Vergleich habe ich die random.random()Funktion nur verwendet , um eine Zeichenfolge zurückzugeben oder Nonebasierend auf Zufallszahlen. Hier ist der gesamte Code:

import random
import timeit

def check_size():
    if random.random() < 0.25: return "BIG"

def check_color():
    if random.random() < 0.25: return "RED"

def check_tone():
    if random.random() < 0.25: return "SOFT"

def check_flavor():
    if random.random() < 0.25: return "SWEET"

def check_all_conditions_Bernard():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

def check_all_Martijn_Pieters():
    conditions = (check_size, check_color, check_tone, check_flavor)
    for condition in conditions:
        result = condition()
        if result:
            return result

def check_all_conditions_timgeb():
    return check_size() or check_color() or check_tone() or check_flavor() or None

def check_all_conditions_Reza():
    return check_size() or check_color() or check_tone() or check_flavor()

def check_all_conditions_Phinet():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions_Phil_Frost():
    for condition in all_conditions():
        if condition:
            return condition

def main():
    num = 10000000
    random.seed(20)
    print("Bernard:", timeit.timeit('check_all_conditions_Bernard()', 'from __main__ import check_all_conditions_Bernard', number=num))
    random.seed(20)
    print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()', 'from __main__ import check_all_Martijn_Pieters', number=num))
    random.seed(20)
    print("timgeb:", timeit.timeit('check_all_conditions_timgeb()', 'from __main__ import check_all_conditions_timgeb', number=num))
    random.seed(20)
    print("Reza:", timeit.timeit('check_all_conditions_Reza()', 'from __main__ import check_all_conditions_Reza', number=num))
    random.seed(20)
    print("Phinet:", timeit.timeit('check_all_conditions_Phinet()', 'from __main__ import check_all_conditions_Phinet', number=num))
    random.seed(20)
    print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()', 'from __main__ import check_all_conditions_Phil_Frost', number=num))

if __name__ == '__main__':
    main()

Und hier sind die Ergebnisse:

Bernard: 7.398444877040768
Martijn Pieters: 8.506569201346597
timgeb: 7.244275416364456
Reza: 6.982133448743038
Phinet: 7.925932800076634
Phil Frost: 11.924794811353031
Reza Dodge
quelle
2

Dieser Weg ist etwas außerhalb des Rahmens, aber ich denke, das Endergebnis ist einfach, lesbar und sieht gut aus.

Die Grundidee ist raiseeine Ausnahme, wenn eine der Funktionen als wahr bewertet wird und das Ergebnis zurückgibt. So könnte es aussehen:

def check_conditions():
    try:
        assertFalsey(
            check_size,
            check_color,
            check_tone,
            check_flavor)
    except TruthyException as e:
        return e.trigger
    else:
        return None

Sie benötigen eine assertFalseyFunktion, die eine Ausnahme auslöst, wenn eines der aufgerufenen Funktionsargumente als wahr bewertet wird:

def assertFalsey(*funcs):
    for f in funcs:
        o = f()
        if o:
            raise TruthyException(o)

Das Obige könnte geändert werden, um auch Argumente für die zu bewertenden Funktionen bereitzustellen.

Und natürlich brauchst du das TruthyExceptionselbst. Diese Ausnahme liefert die object, die die Ausnahme ausgelöst hat:

class TruthyException(Exception):
    def __init__(self, obj, *args):
        super().__init__(*args)
        self.trigger = obj

Sie können die ursprüngliche Funktion natürlich in etwas Allgemeineres verwandeln:

def get_truthy_condition(*conditions):
    try:
        assertFalsey(*conditions)
    except TruthyException as e:
        return e.trigger
    else:
        return None

result = get_truthy_condition(check_size, check_color, check_tone, check_flavor)

Dies kann etwas langsamer sein, da Sie sowohl eine ifAnweisung verwenden als auch eine Ausnahme behandeln. Die Ausnahme wird jedoch nur maximal einmal behandelt, sodass der Leistungseinbruch gering sein sollte, es sei denn, Sie erwarten, dass Sie die Prüfung ausführen und Trueviele, viele tausend Male einen Wert erhalten.

Rick unterstützt Monica
quelle
// , Niedlich! Wird es als "Pythonic" angesehen, für solche Dinge die Ausnahmebehandlung zu verwenden?
Nathan Basanese
@ NathanBasanese Sure-Ausnahmen werden ständig für den Kontrollfluss verwendet. StopIterationist ein ziemlich gutes Beispiel: Jedes Mal, wenn Sie ein iterables Element erschöpfen, wird eine Ausnahme ausgelöst. Das, was Sie vermeiden möchten, ist, immer wieder Ausnahmen auszulösen, die teuer werden würden. Aber es einmal zu tun ist nicht.
Rick unterstützt Monica
//, Ah, ich nehme an, Sie beziehen sich auf so etwas wie programmers.stackexchange.com/questions/112463/… . Ich habe für diese Frage und für diese Antwort gestimmt. Python 3-Dokumente hierfür finden Sie hier: docs.python.org/3/library/stdtypes.html#iterator-types , denke ich.
Nathan Basanese
1
Sie möchten eine Allzweckfunktion und eine Ausnahme definieren, um nur ein paar Überprüfungen in einer anderen Funktion durchzuführen? Ich denke das ist ein bisschen viel.
Blacklight Shining
@BacklightShining Ich stimme zu. Ich würde das eigentlich nie selbst machen. Das OP fragte nach Möglichkeiten, um den wiederholten Code zu vermeiden, aber ich denke, was er angefangen hat, ist vollkommen in Ordnung.
Rick unterstützt Monica
2

Die pythonische Methode ist entweder die Verwendung von reduct (wie bereits erwähnt) oder itertools (wie unten gezeigt), aber es scheint mir, dass die einfache Verwendung des Kurzschlusses des orOperators einen klareren Code erzeugt

from itertools import imap, dropwhile

def check_all_conditions():
    conditions = (check_size,\
        check_color,\
        check_tone,\
        check_flavor)
    results_gen = dropwhile(lambda x:not x, imap(lambda check:check(), conditions))
    try:
        return results_gen.next()
    except StopIteration:
        return None
Dmitry Rubanovich
quelle
0

Ich werde hier reinspringen und habe noch nie eine einzige Zeile Python geschrieben, aber ich gehe davon aus, dass sie if x = check_something(): return xgültig ist?

wenn ja:

def check_all_conditions():

    if (x := check_size()): return x
    if (x := check_color()): return x
    if (x := check_tone()): return x
    if (x := check_flavor()): return x

    return None
Richard87
quelle
1
Es ist nicht gültig Python, nein. In Python können Sie den Zuweisungsoperator nicht so verwenden. Kürzlich wurde jedoch ein neuer spezieller Zuweisungsausdruck hinzugefügt, sodass Sie jetzt if ( x := check_size() ) :für denselben Effekt schreiben können.
Jack Aidley
0

Oder verwenden Sie max:

def check_all_conditions():
    return max(check_size(), check_color(), check_tone(), check_flavor()) or None
U10-Vorwärts
quelle
-2

Ich habe in der Vergangenheit einige interessante Implementierungen von Switch / Case-Anweisungen mit Diktaten gesehen, die mich zu dieser Antwort geführt haben. Anhand des von Ihnen angegebenen Beispiels erhalten Sie Folgendes. (Es ist Wahnsinn using_complete_sentences_for_function_names, also check_all_conditionswird umbenannt instatus . Siehe (1))

def status(k = 'a', s = {'a':'b','b':'c','c':'d','d':None}) :
  select = lambda next, test : test if test else next
  d = {'a': lambda : select(s['a'], check_size()  ),
       'b': lambda : select(s['b'], check_color() ),
       'c': lambda : select(s['c'], check_tone()  ),
       'd': lambda : select(s['d'], check_flavor())}
  while k in d : k = d[k]()
  return k

Die Auswahlfunktion macht das Aufrufen jedes einzelnen überflüssig check_FUNCTION zweimalige überflüssig, dh Sie vermeiden dies, check_FUNCTION() if check_FUNCTION() else nextindem Sie eine weitere Funktionsebene hinzufügen. Dies ist nützlich für lang laufende Funktionen. Die Lambdas im Diktat verzögern die Ausführung ihrer Werte bis zur while-Schleife.

Als Bonus können Sie die Ausführungsreihenfolge ändern und sogar einige der Tests durch Ändern überspringen k undsk='c',s={'c':'b','b':None} die Anzahl der Tests z. B. reduzieren und die ursprüngliche Verarbeitungsreihenfolge umkehren.

Das timeit Kollegen feilschen möglicherweise über die Kosten für das Hinzufügen einer oder zweier zusätzlicher Ebenen zum Stapel und die Kosten für das Nachschlagen des Diktats, aber Sie scheinen sich mehr um die Schönheit des Codes zu kümmern.

Alternativ könnte eine einfachere Implementierung die folgende sein:

def status(k=check_size) :
  select = lambda next, test : test if test else next
  d = {check_size  : lambda : select(check_color,  check_size()  ),
       check_color : lambda : select(check_tone,   check_color() ),
       check_tone  : lambda : select(check_flavor, check_tone()  ),
       check_flavor: lambda : select(None,         check_flavor())}
  while k in d : k = d[k]()
  return k
  1. Ich meine dies nicht mit pep8, sondern mit der Verwendung eines kurzen beschreibenden Wortes anstelle eines Satzes. Zugegeben, das OP folgt möglicherweise einer Codierungskonvention, arbeitet mit einer vorhandenen Codebasis oder kümmert sich nicht um knappe Begriffe in ihrer Codebasis.
Carel
quelle
1
Manchmal sind die Leute wirklich verrückt nach ihrer Benennung, wenn ein Wort ausreicht. Verwenden Sie den Code des OP als ein Beispiel ist es unwahrscheinlich , dass er Funktionen aufgerufen hätte , check_no/some/even/prime/every_third/fancy_conditionsaber nur diese eine Funktion so , warum es nicht nennen statusoder , wenn man darauf besteht check_status. Die Verwendung _all_ist überflüssig, er gewährleistet nicht die Integrität des Universums. Bei der Benennung sollten sicherlich konsistente Schlüsselwörter verwendet werden, wobei der Namensabstand nach Möglichkeit genutzt wird. Lange Sätze dienen am besten als Dokumentzeichenfolgen. Man braucht selten mehr als 8-10 Zeichen, um etwas kurz und bündig zu beschreiben.
Carel
1
Ich bin ein Fan von langen Funktionsnamen, weil ich möchte, dass übergeordnete Funktionen sich selbst dokumentieren. Ist aber check_all_conditionsein schlechter Name, weil nicht alle Bedingungen überprüft werden, ob eine wahr ist. Ich würde so etwas benutzen matches_any_condition.
John Hazen
Das ist ein interessanter Takt. Ich versuche, die Anzahl der Buchstaben zu minimieren, auf die ich später Tippfehler machen werde :) Es scheint, als hätte ich eine Menge Meinungen in meine Lösung gesteckt, als ich wirklich versuchte, einen hilfreichen Hinweis zu geben. Sollte dies herausgeschnitten werden?
Carel
2
Dies scheint viel zu hackig, insbesondere angesichts der anderen Lösungen zu dieser Frage. Was OP versucht, ist überhaupt nicht kompliziert; Die Lösung sollte einfach genug sein, um den Halbschlaf zu verstehen. Und ich habe keine Ahnung, was hier los ist.
Blacklight Shining
Ich strebte nach Flexibilität. Die Antwort wurde geändert, um eine weniger "hackige" Variante aufzunehmen
Carel