Czy istnieje odpowiednik w języku Python operatora zerowego koalescencji w języku C #?

301

W języku C # występuje operator koalescencji zerowej (zapisany jako ??), który umożliwia łatwe (krótkie) sprawdzanie wartości zerowej podczas przypisywania:

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

Czy istnieje odpowiednik Pythona?

Wiem, że mogę:

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

Ale czy jest jeszcze krótsza droga (gdzie nie muszę powtarzać s)?

Klaus Byskov Pedersen
źródło
14
??Operator zaproponowano PEP 505 .
200_success,

Odpowiedzi:

421
other = s or "some default value"

Ok, należy wyjaśnić, jak ordziała operator. Jest to operator logiczny, więc działa w kontekście logicznym. Jeśli wartości nie są logiczne, są one konwertowane na logiczne dla celów operatora.

Pamiętaj, że oroperator nie zwraca tylko Truelub False. Zamiast tego zwraca pierwszy operand, jeśli pierwszy operand ma wartość true, i zwraca drugi operand, jeśli pierwszy operand ma wartość false.

W takim przypadku wyrażenie x or yzwraca wartość, xjeśli jest, Truelub zwraca wartość true po przekonwertowaniu na wartość logiczną. W przeciwnym razie zwraca y. W większości przypadków będzie to służyć temu samemu celowi operatora zerowego koalescencji C♯, ale należy pamiętać:

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

Jeśli użyjesz swojej zmiennej sdo przechowywania czegoś, co jest albo odwołaniem do instancji klasy lub None(o ile twoja klasa nie definiuje członków __nonzero__()i __len__()), możesz bezpiecznie stosować tę samą semantykę, co operator zerowania.

W rzeczywistości przydatne może być wywołanie tego efektu ubocznego Pythona. Ponieważ wiesz, które wartości mają wartość false, możesz użyć tego do wyzwolenia wartości domyślnej bez Nonespecjalnego użycia (na przykład obiekt błędu).

W niektórych językach takie zachowanie jest nazywane operatorem Elvisa .

Juliano
źródło
3
Czy to zadziała tak samo? Mam na myśli, czy sto się zepsuje, jeśli jest prawidłową wartością, ale nie jest prawdą? (Nie znam Pythona, więc nie jestem pewien, czy ma zastosowanie koncepcja „prawdy”.)
cHao
9
Liczba 0 Nonei puste kontenery (w tym ciągi) są uważane za fałsz, oprócz stałej False. Większość wszystkiego jest uważana za prawdę. Powiedziałbym, że głównym niebezpieczeństwem byłoby uzyskanie prawdziwej, ale nie łańcuchowej wartości, ale nie będzie to problemem w niektórych programach.
uprzejmie
25
Użycie tego drugiego spowoduje uzyskanie wartości domyślnej, jeśli s to Brak lub Fałsz , co może nie być tym, czego potrzeba.
pafcu
6
Powoduje to również wiele niejasnych błędów. Na przykład przed Pythonem 3.5 datetime.time(0)był także falsy!
Antti Haapala,
19
To jest złe. Polecam dodanie zawiadomienia o jego pułapkach. I zalecając, aby go nie używać.
Mateen Ulhaq
61

Rygorystycznie,

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

W przeciwnym razie s = Falsestanie się "default value", co może nie być tym, co było zamierzone.

Jeśli chcesz to skrócić, spróbuj:

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

other = notNone(s, "default value")
Hugh Bothwell
źródło
1
Consider x()?.y()?.z()
nurettin
40

Oto funkcja, która zwróci pierwszy argument, który nie jest 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()może niepotrzebnie powtarzać wszystkie argumenty, nawet jeśli pierwszy argument nie jest None, więc możesz również użyć tej wersji:

def coalesce(*arg):
  for el in arg:
    if el is not None:
      return el
  return None
mortehu
źródło
23
def coalesce(*arg): return next((a for a in arg if a is not None), None)robi to samo co twój ostatni przykład w jednej linii.
glglgl
2
Rozumiem, że ludzie chcą wyjaśnić, czy inaczej sytnax itp., Ale łączenie zajmuje dowolną listę argumentów, więc to naprawdę powinna być najlepsza odpowiedź.
Eric Twilegar,
2
glglgl ma najlepszą odpowiedź. Użyłem timeit na dużej tablicy testowej, a implementacja redukcji jest niedopuszczalnie wolna, wersja wieloliniowa dla / jeśli wersja jest najszybsza, a kolejna implementacja jest nieco opóźniona. Następna wersja jest najlepsza ogólnie biorąc pod uwagę prostotę i zwięzłość.
glina
3
@glglgl ma interesujący fragment. Niestety, ponieważ Python nie ma imienia i nazwiska, takie połączenie nie powoduje zwarcia; wszystkie argumenty są sprawdzane przed uruchomieniem kodu.
user1338062,
Consider x()?.y()?.z()
nurettin
5

Zdaję sobie sprawę, że na to odpowiedź, ale istnieje inna opcja, gdy masz do czynienia z przedmiotami.

Jeśli masz obiekt, który może być:

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

Możesz użyć:

obj.get(property_name, value_if_null)

Lubić:

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

Dodając {}jako wartość domyślną, jeśli brakuje „name”, pusty obiekt jest zwracany i przekazywany do następnego pobrania. Jest to podobne do nawigacji null-safe-w C #, co byłoby jak obj?.name?.first.

Craig
źródło
Nie wszystkie obiekty mają .get, działa to tylko w przypadku obiektów podobnych do
dykt
Przesyłam również odpowiedź do zmiany getattr().
dgw
geton dict nie używa parametru domyślnego, jeśli wartością jest None, ale używa parametru domyślnego, jeśli wartość nie istnieje, ponieważ klucza nie ma w dict. {'a': None}.get('a', 'I do not want None')nadal da ci Nonew rezultacie.
Patrick Mevzek
3

Oprócz odpowiedzi Juliano na temat zachowania „lub”: jest „szybki”

>>> 1 or 5/0
1

Czasami może to być przydatny skrót do takich rzeczy

object = getCachedVersion() or getFromDB()
Orca
źródło
17
Termin, którego szukasz, to „zwarcie”.
jpmc26
1

Dodatkowo do odpowiedzi @Bothwells (którą wolę) dla pojedynczych wartości, w celu sprawdzenia zerowego przypisania wartości zwracanych przez funkcję, możesz użyć nowego operatora morsa (od python3.8):

def test():
    return

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

Dlatego testfunkcja nie musi być oceniana dwa razy (jak w a = 2 if test() is None else test())

Henhuy
źródło
1

W przypadku potrzeby zagnieżdżenia więcej niż jednej zerowej operacji koalescencji, takiej jak:

model?.data()?.first()

Nie jest to problem łatwy do rozwiązania or. Nie można go również rozwiązać za .get()pomocą typu słownika lub podobnego (i tak nie można go zagnieżdżić) lubgetattr() który zgłosi wyjątek, gdy NoneType nie ma atrybutu.

Odpowiednim pipem rozważającym dodanie koalescencji zerowej do języka jest PEP 505, a dyskusja dotycząca dokumentu znajduje się w wątku python-ideas .

nurettin
źródło
0

Odnośnie odpowiedzi @Hugh Bothwell, @mortehu i @glglgl.

Skonfiguruj zestaw danych do testowania

import random

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

Zdefiniuj implementacje

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)

Włącz funkcję testową

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

Wyniki na komputerze Mac i7 @ 2.7 Ghz przy użyciu języka 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

Oczywiście not_none funkcja poprawnie odpowiada na pytanie OP i radzi sobie z problemem „falsy”. Jest to również najszybszy i najłatwiejszy do odczytania. Jeśli zastosujesz logikę w wielu miejscach, jest to zdecydowanie najlepsza droga.

Jeśli masz problem, w którym chcesz znaleźć pierwszą wartość inną niż null w iterowalnym, odpowiedź @ mortehu jest dobrym rozwiązaniem. Ale jest to rozwiązanie innego problemu niż OP, chociaż może częściowo poradzić sobie z tą sprawą. Nie może przyjąć iterowalnej ORAZ wartości domyślnej. Ostatnim argumentem byłaby zwrócona wartość domyślna, ale w takim przypadku nie przekazano by się iterowalnego, a także nie jest jasne, że ostatni argument jest wartością domyślną.

Możesz wtedy zrobić poniżej, ale nadal używałbym not_nulldla przypadku użycia pojedynczej wartości.

def coalesce(*args, **kwargs):
    default = kwargs.get('default')
    return next((a for a in arg if a is not None), default)
debo
źródło
0

Dla takich jak ja, którzy natknęli się tutaj, szukając realnego rozwiązania tego problemu, kiedy zmienna może być niezdefiniowana, najbliższe, jakie mam, to:

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')

Zwróć uwagę, że podczas sprawdzania wartości globalnych potrzebny jest ciąg, ale później podczas sprawdzania wartości używana jest rzeczywista zmienna.

Więcej informacji o istnieniu zmiennych: Jak sprawdzić, czy zmienna istnieje?

Itaca
źródło
1
(variablename or False) == Truejest taki sam jakvariablename == 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

jeśli nie możesz znaleźć nazwy w słowniku, zwróci wartość domyślną, jeśli nazwa istnieje, doda dowolną istniejącą wartość z 1.

mam nadzieję, że to może pomóc

Angelo
źródło
1
Cześć, witamy w Stack Overflow. Jakie nowe informacje dodaje twoja odpowiedź, które nie zostały jeszcze uwzględnione w istniejących odpowiedziach? Zobacz na przykład odpowiedź @ Craiga
Gricey,
-6

Dwie poniższe funkcje okazały się bardzo przydatne w przypadku wielu zmiennych przypadków testowych.

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
Mike Stabile
źródło
5
Tego rodzaju rzeczy dodają całą masę nieco odmiennej terminologii (np. „Null” i „nz”, z których żadna nie znaczy nic w kontekście Pythona), importowane z innych języków, a także z wariantami (ścisłe lub nie ścisłe!). To tylko dodaje zamieszanie. Jawne kontrole „brak” to to, czego powinieneś używać. Co więcej, nie zyskujesz żadnej skrótowej semantyki, którą operatorzy mogą zrobić, gdy używasz wywołania funkcji.
spookylukey