Gibt es ein Python-Äquivalent zum C # -Null-Coalescing-Operator?


301

In C # gibt es einen Null-Koaleszenz-Operator (geschrieben als ??), der eine einfache (kurze) Nullprüfung während der Zuweisung ermöglicht:

string s = null;
var other = s ?? "some default value";

Gibt es ein Python-Äquivalent?

Ich weiß, dass ich tun kann:

s = None
other = s if s else "some default value"

Aber gibt es einen noch kürzeren Weg (wo ich nicht wiederholen muss s)?


14
Der ??Betreiber wird als PEP 505 vorgeschlagen .
200_erfolg

Antworten:


421
other = s or "some default value"

Ok, es muss geklärt werden, wie der orBediener arbeitet. Es ist ein boolescher Operator und funktioniert daher in einem booleschen Kontext. Wenn die Werte nicht boolesch sind, werden sie für den Operator in boolesche Werte konvertiert.

Beachten Sie, dass der orOperator nicht nur Trueoder zurückgibt False. Stattdessen wird der erste Operand zurückgegeben, wenn der erste Operand als wahr ausgewertet wird, und der zweite Operand, wenn der erste Operand als falsch ausgewertet wird.

In diesem Fall wird der Ausdruck x or yzurückgegeben, xwenn er Truebei der Konvertierung in einen Booleschen Wert true ist oder als true ausgewertet wird. Andernfalls wird es zurückgegeben y. In den meisten Fällen dient dies dem gleichen Zweck wie der Null-Koaleszenz-Operator von C♯. Beachten Sie jedoch Folgendes:

42    or "something"    # returns 42
0     or "something"    # returns "something"
None  or "something"    # returns "something"
False or "something"    # returns "something"
""    or "something"    # returns "something"

Wenn Sie Ihre Variable verwenden s, um etwas zu speichern, das entweder auf die Instanz einer Klasse verweist oder None(solange Ihre Klasse keine Mitglieder definiert __nonzero__()und __len__()), ist es sicher, dieselbe Semantik wie der Null-Koaleszenz-Operator zu verwenden.

In der Tat kann es sogar nützlich sein, diese Nebenwirkung von Python zu haben. Da Sie wissen, welche Werte als falsch ausgewertet werden, können Sie damit den Standardwert auslösen, ohne ihn Nonespeziell zu verwenden (z. B. ein Fehlerobjekt).

In einigen Sprachen wird dieses Verhalten als Elvis-Operator bezeichnet .


3
Funktioniert das genauso? Ich meine, wird es kaputt gehen, wenn ses ein gültiger Wert ist, aber nicht wahr? (Ich kenne Python nicht, daher bin ich mir nicht sicher, ob das Konzept der 'Wahrheit' gilt.)
CHA

9
Die Nummer 0 Noneund leere Container (einschließlich Zeichenfolgen) werden zusätzlich zur Konstante als falsch betrachtet False. Fast alles andere gilt als wahr. Ich würde sagen, dass die Hauptgefahr hier darin besteht, dass Sie einen wahren Wert ohne Zeichenfolge erhalten, aber das ist in einigen Programmen kein Problem.
Kindall

25
Wenn Sie diesen anderen verwenden, wird der Standardwert erhalten, wenn s None oder False ist , was möglicherweise nicht der gewünschte Wert ist.
Pafcu

6
Es gibt auch viele obskure Fehler, die dadurch verursacht werden. Zum Beispiel vor Python 3.5 datetime.time(0)war auch falsch!
Antti Haapala

19
Das ist schlecht. Ich empfehle, einen Hinweis zu den Fallstricken hinzuzufügen. Und empfehlen, es nicht zu verwenden.
Mateen Ulhaq

61

Streng,

other = s if s is not None else "default value"

Andernfalls s = Falsewird "default value", was möglicherweise nicht das ist, was beabsichtigt war.

Wenn Sie dies verkürzen möchten, versuchen Sie:

def notNone(s,d):
    if s is None:
        return d
    else:
        return s

other = notNone(s, "default value")

1
Consider x()?.y()?.z()
Nurettin

40

Hier ist eine Funktion, die das erste Argument zurückgibt, das nicht ist None:

def coalesce(*arg):
  return reduce(lambda x, y: x if x is not None else y, arg)

# Prints "banana"
print coalesce(None, "banana", "phone", None)

reduce()Möglicherweise werden alle Argumente unnötig durchlaufen, auch wenn das erste Argument nicht vorhanden Noneist. Sie können also auch diese Version verwenden:

def coalesce(*arg):
  for el in arg:
    if el is not None:
      return el
  return None

23
def coalesce(*arg): return next((a for a in arg if a is not None), None)macht dasselbe wie Ihr letztes Beispiel in einer Zeile.
glglgl

2
Ich verstehe, dass die Leute erklären wollen, ob es sonst Sytnax usw. gibt, aber Coalesce benötigt eine willkürliche Argumentliste, daher sollte dies wirklich die beste Antwort sein.
Eric Twilegar

2
glglgl hat die beste Antwort. Ich habe timeit für ein großes Testarray verwendet und die Implementierung zum Reduzieren ist inakzeptabel langsam, die mehrzeilige Version für / if ist am schnellsten und die nächste Implementierung liegt nur geringfügig zurück. Die nächste Version ist insgesamt die beste, wenn es um Einfachheit und Prägnanz geht.
Ton

3
@glglgl hat interessante Schnipsel. Da Python keinen Pass-by-Namen hat, ist eine solche Verschmelzung leider kein Kurzschluss. Alle Argumente werden ausgewertet, bevor der Code ausgeführt wird.
user1338062

Consider x()?.y()?.z()
Nurettin

5

Mir ist klar, dass dies beantwortet wird, aber es gibt eine andere Option, wenn Sie mit Objekten arbeiten.

Wenn Sie ein Objekt haben, könnte dies sein:

{
   name: {
      first: "John",
      last: "Doe"
   }
}

Sie können verwenden:

obj.get(property_name, value_if_null)

Mögen:

obj.get("name", {}).get("first", "Name is missing") 

Durch Hinzufügen {}als Standardwert wird, wenn "Name" fehlt, ein leeres Objekt zurückgegeben und an den nächsten get übergeben. Dies ähnelt der null-sicheren Navigation in C # obj?.name?.first.


Nicht alle Objekte haben .get, dies funktioniert nur für
diktartige

Ich reiche eine Antwortbearbeitung ein, um sie auch abzudecken getattr().
dgw

geton dict verwendet nicht den Standardparameter, wenn der Wert None ist, sondern den Standardparameter, wenn der Wert nicht vorhanden ist, da sich der Schlüssel nicht im dict befindet. {'a': None}.get('a', 'I do not want None')wird Ihnen trotzdem Noneals Ergebnis geben.
Patrick Mevzek

3

Zusätzlich zu Julianos Antwort zum Verhalten von "oder": Es ist "schnell".

>>> 1 or 5/0
1

Manchmal ist es also eine nützliche Abkürzung für Dinge wie

object = getCachedVersion() or getFromDB()

17
Der Begriff, den Sie suchen, ist "Kurzschlüsse".
jpmc26

1

Zusätzlich zur Antwort von @Bothwells (die ich bevorzuge) für einzelne Werte können Sie den neuen Walross-Operator (seit Python3.8) verwenden, um die Zuordnung von Funktionsrückgabewerten auf Null zu überprüfen:

def test():
    return

a = 2 if (x:= test()) is None else x

Daher muss die testFunktion nicht zweimal bewertet werden (wie in a = 2 if test() is None else test()).


1

Falls Sie mehr als eine Null-Koaleszenzoperation verschachteln müssen, z.

model?.data()?.first()

Dies ist kein leicht zu lösendes Problem or. Es kann auch nicht gelöst werden, .get()was einen Wörterbuchtyp oder ähnliches erfordert (und sowieso nicht verschachtelt werden kann) odergetattr() werden, wenn wenn eine Ausnahme ausgelöst wird, wenn NoneType das Attribut nicht hat.

Der relevante Pip, der erwägt, der Sprache eine Null-Koaleszenz hinzuzufügen, ist PEP 505, und die für das Dokument relevante Diskussion befindet sich im Python-Ideen- Thread.


0

Zu den Antworten von @Hugh Bothwell, @mortehu und @glglgl.

Setup-Datensatz zum Testen

import random

dataset = [random.randint(0,15) if random.random() > .6 else None for i in range(1000)]

Implementierungen definieren

def not_none(x, y=None):
    if x is None:
        return y
    return x

def coalesce1(*arg):
  return reduce(lambda x, y: x if x is not None else y, arg)

def coalesce2(*args):
    return next((i for i in args if i is not None), None)

Testfunktion machen

def test_func(dataset, func):
    default = 1
    for i in dataset:
        func(i, default)

Ergebnisse auf Mac i7 @ 2.7Ghz mit Python 2.7

>>> %timeit test_func(dataset, not_none)
1000 loops, best of 3: 224 µs per loop

>>> %timeit test_func(dataset, coalesce1)
1000 loops, best of 3: 471 µs per loop

>>> %timeit test_func(dataset, coalesce2)
1000 loops, best of 3: 782 µs per loop

Klar die not_none Funktion beantwortet die Frage des OP eindeutig richtig und behandelt das "Falsy" -Problem. Es ist auch am schnellsten und am einfachsten zu lesen. Wenn Sie die Logik an vielen Stellen anwenden, ist dies eindeutig der beste Weg.

Wenn Sie ein Problem haben, bei dem Sie den ersten Wert ungleich Null in einer Iterable finden möchten, ist die Antwort von @ mortehu der richtige Weg. Es ist jedoch eine Lösung für ein anderes Problem als OP, obwohl es diesen Fall teilweise behandeln kann. Es kann kein iterierbares UND ein Standardwert annehmen. Das letzte Argument wäre der zurückgegebene Standardwert, aber dann würden Sie in diesem Fall kein iterables übergeben, und es ist nicht explizit, dass das letzte Argument ein Standardwert ist.

Sie könnten dann unten tun, aber ich würde immer noch not_nullfür den Einzelwert-Anwendungsfall verwenden.

def coalesce(*args, **kwargs):
    default = kwargs.get('default')
    return next((a for a in arg if a is not None), default)

0

Für diejenigen wie mich, die hier gestolpert sind und nach einer praktikablen Lösung für dieses Problem gesucht haben, wenn die Variable undefiniert sein könnte, ist die nächste, die ich bekommen habe:

if 'variablename' in globals() and ((variablename or False) == True):
  print('variable exists and it\'s true')
else:
  print('variable doesn\'t exist, or it\'s false')

Beachten Sie, dass beim Einchecken von Globals eine Zeichenfolge benötigt wird, danach jedoch die tatsächliche Variable beim Überprüfen auf Werte verwendet wird.

Weitere Informationen zur Existenz von Variablen: Wie überprüfe ich, ob eine Variable vorhanden ist?


1
(variablename or False) == Trueist das gleiche wievariablename == True
mic

-2
Python has a get function that its very useful to return a value of an existent key, if the key exist;
if not it will return a default value.

def main():
    names = ['Jack','Maria','Betsy','James','Jack']
    names_repeated = dict()
    default_value = 0

    for find_name in names:
        names_repeated[find_name] = names_repeated.get(find_name, default_value) + 1

Wenn Sie den Namen im Wörterbuch nicht finden können, wird der Standardwert zurückgegeben. Wenn der Name vorhanden ist, wird ein vorhandener Wert mit 1 hinzugefügt.

hoffe das kann helfen


1
Hallo, willkommen bei Stack Overflow. Welche neuen Informationen fügt Ihre Antwort hinzu, die noch nicht durch vorhandene Antworten abgedeckt waren? Siehe @ Craig's Antwort zum Beispiel
Gricey

-6

Die beiden folgenden Funktionen haben sich bei vielen variablen Testfällen als sehr nützlich erwiesen.

def nz(value, none_value, strict=True):
    ''' This function is named after an old VBA function. It returns a default
        value if the passed in value is None. If strict is False it will
        treat an empty string as None as well.

        example:
        x = None
        nz(x,"hello")
        --> "hello"
        nz(x,"")
        --> ""
        y = ""   
        nz(y,"hello")
        --> ""
        nz(y,"hello", False)
        --> "hello" '''

    if value is None and strict:
        return_val = none_value
    elif strict and value is not None:
        return_val = value
    elif not strict and not is_not_null(value):
        return_val = none_value
    else:
        return_val = value
    return return_val 

def is_not_null(value):
    ''' test for None and empty string '''
    return value is not None and len(str(value)) > 0

5
Diese Art von Dingen fügt eine ganze Reihe leicht unterschiedlicher Begriffe hinzu (z. B. "null" und "nz", von denen keine im Kontext von Python etwas bedeutet), die aus anderen Sprachen importiert wurden, sowie Varianten (streng oder nicht streng!). Dies führt nur zu Verwirrung. Explizite "is None" -Prüfungen sollten verwendet werden. Außerdem profitieren Sie nicht von einer Abkürzungssemantik, die Operatoren bei Verwendung eines Funktionsaufrufs ausführen können.
Spookylukey
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.