Existe-t-il un équivalent Python de l'opérateur de coalescence nulle C #?

302

En C #, il y a un opérateur de coalescence nulle (écrit comme ??) qui permet une vérification nulle (courte) facile pendant l'affectation:

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

Existe-t-il un équivalent python?

Je sais que je peux faire:

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

Mais y a-t-il un chemin encore plus court (où je n'ai pas besoin de répéter s)?

Klaus Byskov Pedersen
la source
14
L' ??opérateur est proposé comme PEP 505 .
200_success

Réponses:

422
other = s or "some default value"

Ok, il faut clarifier le fonctionnement de l' oropérateur. Il s'agit d'un opérateur booléen, il fonctionne donc dans un contexte booléen. Si les valeurs ne sont pas booléennes, elles sont converties en booléennes pour les besoins de l'opérateur.

Notez que l' oropérateur ne retourne pas seulement Trueou False. Au lieu de cela, il renvoie le premier opérande si le premier opérande est évalué à vrai et il renvoie le deuxième opérande si le premier opérande est évalué à faux.

Dans ce cas, l'expression x or yretourne xsi elle est Trueou est évaluée à true lorsqu'elle est convertie en booléen. Sinon, il revient y. Dans la plupart des cas, cela servira aux mêmes fins que l'opérateur de coalescence nulle de C♯, mais gardez à l'esprit:

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

Si vous utilisez votre variable spour contenir quelque chose qui est soit une référence à l'instance d'une classe ouNone (tant que votre classe ne définit pas de membres __nonzero__()et __len__()), il est sûr d'utiliser la même sémantique que l'opérateur de coalescence nulle.

En fait, il peut même être utile d'avoir cet effet secondaire de Python. Étant donné que vous savez quelles valeurs sont évaluées comme fausses, vous pouvez l'utiliser pour déclencher la valeur par défaut sans utiliserNone spécifiquement (un objet d'erreur, par exemple).

Dans certaines langues, ce comportement est appelé opérateur Elvis .

Juliano
la source
3
Est-ce que cela fonctionnera de la même façon? Je veux dire, est-ce que ça cassera si sc'est une valeur valide mais n'est-ce pas vrai? (Je ne connais pas Python, donc je ne sais pas si le concept de `` véridique '' s'applique.)
cHao
9
Le nombre 0, Noneet les conteneurs vides (y compris les chaînes) sont considérés comme faux, en plus de la constante False. Presque tout le reste est considéré comme vrai. Je dirais que le principal danger ici serait que vous obteniez une valeur vraie mais non chaîne, mais ce ne sera pas un problème dans certains programmes.
kindall
25
L'utilisation de cet autre obtiendra la valeur par défaut si s est None ou False , ce qui n'est peut-être pas ce que l'on souhaite.
pafcu
6
Il existe également de nombreux bugs obscurs. Par exemple, avant Python 3.5, datetime.time(0)c'était aussi faux!
Antti Haapala
19
C'est mauvais. Je recommande d'ajouter un avis sur ses pièges. Et en recommandant de ne pas l'utiliser.
Mateen Ulhaq
61

Strictement,

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

Sinon, s = Falsedeviendra "default value", ce qui peut ne pas être ce qui était prévu.

Si vous souhaitez raccourcir ce délai, essayez:

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

other = notNone(s, "default value")
Hugh Bothwell
la source
1
Consider x()?.y()?.z()
nurettin
40

Voici une fonction qui renverra le premier argument qui ne l'est pas 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()pourrait itérer inutilement sur tous les arguments même si le premier argument ne l'est pas None, vous pouvez donc également utiliser cette version:

def coalesce(*arg):
  for el in arg:
    if el is not None:
      return el
  return None
mortehu
la source
23
def coalesce(*arg): return next((a for a in arg if a is not None), None)fait la même chose que votre dernier exemple sur une ligne.
glglgl
2
J'obtiens que les gens veulent expliquer si d'autre sytnax etc, mais coalesce prend une liste d'arguments arbitraires, donc cela devrait vraiment être la meilleure réponse.
Eric Twilegar
2
glglgl a la meilleure réponse. J'ai utilisé timeit sur un grand tableau de test et l'implémentation de réduction est trop lente, la version multiligne pour / si est la plus rapide et l'implémentation suivante est très légèrement en retard. La prochaine version est la meilleure dans l'ensemble si l'on considère la simplicité et la concision.
terre battue
3
@glglgl a un extrait intéressant. Malheureusement parce que Python n'a pas de mot de passe, la fusion comme celle-ci n'est pas en court-circuit; tous les arguments sont évalués avant l'exécution du code.
user1338062
Consider x()?.y()?.z()
nurettin
5

Je sais que c'est répondu, mais il existe une autre option lorsque vous traitez avec des objets.

Si vous avez un objet qui pourrait être:

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

Vous pouvez utiliser:

obj.get(property_name, value_if_null)

Comme:

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

En ajoutant {}comme valeur par défaut, si "nom" est manquant, un objet vide est retourné et transmis à la prochaine get. Ceci est similaire à la navigation null-safe en C #, ce qui serait comme obj?.name?.first.

Craig
la source
Tous les objets n'ont pas .get, cela ne fonctionne que pour les objets de type dict
timdiels
Je soumets une modification de réponse à couvrir getattr()également.
dgw
geton dict n'utilise pas le paramètre par défaut si la valeur est None mais utilise le paramètre par défaut si la valeur n'existe pas car la clé n'est pas dans le dict. {'a': None}.get('a', 'I do not want None')vous donnera toujours Nonecomme résultat.
Patrick Mevzek
3

En plus de la réponse de Juliano sur le comportement de "ou": c'est "rapide"

>>> 1 or 5/0
1

Parfois, cela peut être un raccourci utile pour des choses comme

object = getCachedVersion() or getFromDB()
Orca
la source
17
Le terme que vous recherchez est «court-circuit».
jpmc26
1

En plus de la réponse @Bothwells (que je préfère) pour les valeurs uniques, afin de vérifier la valeur nulle des valeurs de retour de fonction, vous pouvez utiliser un nouvel opérateur morse (depuis python3.8):

def test():
    return

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

Ainsi, la testfonction n'a pas besoin d'être évaluée deux fois (comme dans a = 2 if test() is None else test())

Henhuy
la source
1

Dans le cas où vous devez imbriquer plusieurs opérations de coalescence nulle telles que:

model?.data()?.first()

Ce n'est pas un problème facile à résoudre or. Il ne peut pas non plus être résolu avec .get()lequel nécessite un type de dictionnaire ou similaire (et ne peut pas être imbriqué de toute façon) ou getattr()qui lèvera une exception lorsque NoneType n'a pas l'attribut.

Le pip pertinent envisageant d'ajouter une coalescence nulle au langage est PEP 505 et la discussion pertinente au document se trouve dans le fil d' idées python .

nurettin
la source
0

Concernant les réponses de @Hugh Bothwell, @mortehu et @glglgl.

Configuration du jeu de données pour les tests

import random

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

Définir les implémentations

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)

Faire la fonction de test

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

Résultats sur Mac i7 @ 2.7Ghz en utilisant 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

De toute évidence, not_none fonction répond correctement à la question de l'OP et gère le problème de "falsification". Il est également le plus rapide et le plus facile à lire. Si vous appliquez la logique à de nombreux endroits, c'est clairement la meilleure façon de procéder.

Si vous avez un problème où vous voulez trouver la 1ère valeur non nulle dans un itérable, alors la réponse de @ mortehu est la voie à suivre. Mais c'est une solution à un problème différent de celui de l'OP, bien qu'il puisse gérer partiellement ce cas. Il ne peut pas prendre une valeur itérable ET une valeur par défaut. Le dernier argument serait la valeur par défaut retournée, mais vous ne seriez pas en train de passer un itérable dans ce cas aussi bien qu'il n'est pas explicite que le dernier argument est une valeur par défaut.

Vous pouvez ensuite le faire ci-dessous, mais je l'utiliserais toujours not_nullpour le cas d'utilisation à valeur unique.

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

Pour ceux comme moi qui ont trébuché ici à la recherche d'une solution viable à ce problème, lorsque la variable peut être indéfinie, le plus proche que j'ai obtenu est:

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

Notez qu'une chaîne est nécessaire lors de l'archivage des globaux, mais ensuite la variable réelle est utilisée lors de la vérification de la valeur.

En savoir plus sur l'existence de variables: comment vérifier si une variable existe?

Itaca
la source
1
(variablename or False) == Trueest le même quevariablename == 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

si vous ne trouvez pas le nom dans le dictionnaire, il renverra la valeur par défaut, si le nom existe, il ajoutera toute valeur existante avec 1.

j'espère que cela peut aider

Angelo
la source
1
Salut, bienvenue dans Stack Overflow. Quelles nouvelles informations votre réponse ajoute-t-elle qui n'étaient pas déjà couvertes par les réponses existantes? Voir la réponse de @ Craig par exemple
Gricey
-6

Les deux fonctions ci-dessous se sont révélées très utiles pour traiter de nombreux cas de test de variables.

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
la source
5
Ce genre de choses ajoute un tas de terminologies légèrement différentes (par exemple "null" et "nz" qui ne signifient rien dans le contexte de Python), importées d'autres langues, plus avec des variantes (strictes ou non strictes!). Cela ajoute seulement de la confusion. Les vérifications explicites «est nul» sont ce que vous devriez utiliser. De plus, vous ne bénéficiez d'aucune sémantique abrégée que les opérateurs peuvent effectuer lorsque vous utilisez un appel de fonction.
spookylukey