Esiste un equivalente Python dell'operatore null # a coalescenza?


301

In C # c'è un operatore a coalescenza nulla (scritto come ??) che consente un controllo null (breve) facile durante l'assegnazione:

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

Esiste un equivalente di Python?

So che posso fare:

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

Ma c'è un modo ancora più breve (dove non ho bisogno di ripetere s)?


14
L' ??operatore è proposto come PEP 505 .
200_successo

Risposte:


421
other = s or "some default value"

Ok, deve essere chiarito come funziona l' oroperatore. È un operatore booleano, quindi funziona in un contesto booleano. Se i valori non sono booleani, vengono convertiti in booleani ai fini dell'operatore.

Si noti che l' oroperatore non restituisce solo Trueo False. Al contrario, restituisce il primo operando se il primo operando restituisce true e restituisce il secondo operando se il primo operando restituisce false.

In questo caso, l'espressione x or yrestituisce xse è Trueo viene valutata vera quando convertita in booleana. Altrimenti, ritorna y. Nella maggior parte dei casi, ciò servirà allo stesso scopo dell'operatore a coalescenza nulla di C♯, ma tieni presente:

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

Se usi la tua variabile sper contenere qualcosa che è o un riferimento all'istanza di una classe o None(purché la tua classe non definisca membri __nonzero__()e __len__()), è sicuro usare la stessa semantica dell'operatore a coalescenza nulla.

In effetti, potrebbe anche essere utile avere questo effetto collaterale di Python. Poiché sai quali valori vengono valutati come falsi, puoi utilizzarlo per attivare il valore predefinito senza utilizzare Nonespecificamente (un oggetto errore, ad esempio).

In alcune lingue questo comportamento è indicato come operatore Elvis .


3
Funzionerà allo stesso modo? Voglio dire, si romperà se sè un valore valido ma non è vero? (Non conosco Python, quindi non sono sicuro che si applichi il concetto di "verità".)
cHao,

9
Il numero 0, Nonee i contenitori vuoti (comprese le stringhe) sono considerati falsi, oltre alla costante False. Quasi tutto il resto è considerato vero. Direi che il pericolo principale qui sarebbe quello di ottenere un valore vero ma senza stringhe, ma questo non sarà un problema in alcuni programmi.
kindall

25
L'uso di questo altro otterrà il valore predefinito se s è Nessuno o Falso , che potrebbe non essere quello desiderato.
pafcu,

6
Ci sono anche molti bug oscuri causati da questo. Ad esempio prima di Python 3.5, datetime.time(0)era anche falsa!
Antti Haapala,

19
Questo non va bene. Consiglio di aggiungere un avviso sulle sue insidie. E raccomandando di non usarlo.
Mateen Ulhaq,

61

In senso stretto,

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

Altrimenti, s = Falsediventerà "default value", che potrebbe non essere quello che era previsto.

Se vuoi ridurlo, prova:

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

Ecco una funzione che restituirà il primo argomento che non è 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()potrebbe inutilmente scorrere su tutti gli argomenti anche se il primo argomento non lo è None, quindi puoi anche usare questa versione:

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)fa lo stesso del tuo ultimo esempio in una riga.
glglgl,

2
Ho capito che la gente vuole spiegare se altrimenti sytnax ecc., Ma la coesione prende un elenco di argomenti arbitrari, quindi questa dovrebbe davvero essere la risposta migliore.
Eric Twilegar,

2
glglgl ha la risposta migliore. Ho usato timeit su un array di test di grandi dimensioni e l'implementazione ridotta è inaccettabilmente lenta, la versione multilinea per / se è la più veloce e l'implementazione successiva è leggermente indietro. La prossima versione è la migliore in assoluto se si considera semplicità e concisione.
argilla,

3
@glglgl ha un frammento interessante. Sfortunatamente perché Python non ha il pass-by-name, la coalizione come questa non è un corto circuito; tutti gli argomenti vengono valutati prima dell'esecuzione del codice.
user1338062,

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

5

Mi rendo conto che questa è una risposta, ma c'è un'altra opzione quando hai a che fare con oggetti.

Se hai un oggetto che potrebbe essere:

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

Puoi usare:

obj.get(property_name, value_if_null)

Piace:

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

Aggiungendo {}come valore predefinito, se manca "name", viene restituito un oggetto vuoto e passato al successivo get. Questo è simile alla navigazione null-safe in C #, che sarebbe simile obj?.name?.first.


Non tutti gli oggetti hanno .get, questo funziona solo con oggetti simili a dict
timdiels,

Sto inviando una modifica di risposta per coprire getattr()anche.
dgw

getsu dict non utilizza il parametro predefinito se il valore è Nessuno ma utilizza il parametro predefinito se il valore non esiste perché la chiave non è presente in dict. {'a': None}.get('a', 'I do not want None')ti darà comunque Nonecome risultato.
Patrick Mevzek,

3

Oltre alla risposta di Juliano sul comportamento di "o": è "veloce"

>>> 1 or 5/0
1

Quindi a volte potrebbe essere una scorciatoia utile per cose come

object = getCachedVersion() or getFromDB()

17
Il termine che stai cercando è "cortocircuiti".
jpmc26,

1

In aggiunta alla risposta di @Bothwells (che preferisco) per i singoli valori, al fine di verificare in modo nullo la valutazione dei valori di ritorno della funzione, è possibile utilizzare un nuovo operatore tricheco (dal momento che python3.8):

def test():
    return

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

Pertanto, la testfunzione non deve essere valutata due volte (come in a = 2 if test() is None else test())


1

Nel caso in cui sia necessario nidificare più di un'operazione di coalescenza nulla come:

model?.data()?.first()

Questo non è un problema facilmente risolvibile or. Inoltre, non può essere risolto con .get()chi richiede un tipo di dizionario o simile (e non può essere nidificato comunque) o getattr()che genererà un'eccezione quando NoneType non ha l'attributo.

Il pip pertinente che considera l'aggiunta di coalescenza nulla al linguaggio è PEP 505 e la discussione rilevante per il documento è nel thread di Python- Ideas.


0

Per quanto riguarda le risposte di @Hugh Bothwell, @mortehu e @glglgl.

Imposta set di dati per i test

import random

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

Definire le implementazioni

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)

Rendi la funzione di test

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

Risultati su Mac i7 a 2,7 Ghz usando 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

Chiaramente la not_nonefunzione risponde correttamente alla domanda del PO e gestisce il problema "falsa". È anche il più veloce e facile da leggere. Se si applica la logica in molti luoghi, è chiaramente il modo migliore di procedere.

Se hai un problema in cui vuoi trovare il primo valore non nullo in un iterabile, allora la risposta di @ mortehu è la strada da percorrere. Ma è una soluzione a un problema diverso rispetto a OP, sebbene possa gestire parzialmente quel caso. Non può accettare un valore iterabile E un valore predefinito. L'ultimo argomento sarebbe il valore predefinito restituito, ma in questo caso non si passerà a un iterabile e non è esplicito che l'ultimo argomento sia un valore predefinito.

Potresti quindi fare di seguito, ma userei comunque not_nullper il caso d'uso a valore singolo.

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

0

Per quelli come me che sono inciampati qui alla ricerca di una soluzione praticabile a questo problema, quando la variabile potrebbe non essere definita, il più vicino che ho ottenuto è:

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

Si noti che è necessaria una stringa quando si esegue il check in globale, ma successivamente viene utilizzata la variabile effettiva per il controllo del valore.

Altre informazioni sull'esistenza di variabili: come posso verificare se esiste una variabile?


1
(variablename or False) == Trueè lo stesso divariablename == 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

se non riesci a trovare il nome all'interno del dizionario, verrà restituito il valore predefinito, se il nome esiste, aggiungerà qualsiasi valore esistente con 1.

spero che questo possa aiutare


1
Ciao, benvenuto in Stack Overflow. Quali nuove informazioni aggiunge la tua risposta che non era già coperta dalle risposte esistenti? Vedi la risposta di @ Craig per esempio
Gricey,

-6

Le due funzioni seguenti ho trovato molto utili quando si tratta di molti casi di test variabili.

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
Questo tipo di cose aggiunge un sacco di termini leggermente diversi (es. "Null" e "nz" nessuno dei quali significa nulla nel contesto di Python), importati da altre lingue, oltre a varianti (rigoroso o non rigoroso!). Questo aggiunge solo confusione. I controlli espliciti "is None" sono quelli che dovresti usare. Inoltre, non si ottiene il vantaggio di alcuna semantica abbreviata che gli operatori possono fare quando si utilizza una chiamata di funzione.
spookylukey,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.