¿Hay un equivalente en Python del operador de fusión nula C #?

301

En C # hay un operador de fusión nula (escrito como ??) que permite una verificación nula fácil (corta) durante la asignación:

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

¿Hay un pitón equivalente?

Sé que puedo hacer:

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

¿Pero hay una forma aún más corta (donde no necesito repetir s)?

Klaus Byskov Pedersen
fuente
14
El ??operador se propone como PEP 505 .
200_success

Respuestas:

421
other = s or "some default value"

Ok, hay que aclarar cómo or operador. Es un operador booleano, por lo que funciona en un contexto booleano. Si los valores no son booleanos, se convierten a booleanos para los fines del operador.

Tenga en cuenta que el oroperador no regresa solo Trueo False. En cambio, devuelve el primer operando si el primer operando se evalúa como verdadero, y devuelve el segundo operando si el primer operando se evalúa como falso.

En este caso, la expresión x or ydevuelve xsi es Trueo se evalúa como verdadera cuando se convierte en booleana. De lo contrario, vuelve y. Para la mayoría de los casos, esto servirá para el mismo propósito del operador de fusión nula de C♯, pero tenga en cuenta:

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 usa su variable spara contener algo que sea una referencia a la instancia de una clase o None(siempre que su clase no defina miembros __nonzero__()y__len__() ), es seguro usar la misma semántica que el operador de fusión nula.

De hecho, incluso puede ser útil tener este efecto secundario de Python. Como sabe qué valores se evalúan como falsos, puede usar esto para activar el valor predeterminado sin usar Noneespecíficamente (un objeto de error, por ejemplo).

En algunos idiomas, este comportamiento se conoce como el operador de Elvis .

Juliano
fuente
3
¿Funcionará igual? Quiero decir, ¿se romperá si ses un valor válido pero no es verdadero? (No sé Python, así que no estoy seguro de si el concepto de 'Truthy' se aplica.)
Chao
99
El número 0, Noney los contenedores vacíos (incluidas las cadenas) se consideran falsos, además de la constante False. Casi todo lo demás se considera cierto. Diría que el peligro principal aquí sería que obtendría un valor verdadero pero no de cadena, pero eso no será un problema en algunos programas.
poco
25
El uso de este otro obtendrá el valor predeterminado si s es None o False , que puede no ser lo que se desea.
pafcu
66
Hay muchos errores oscuros causados ​​por esto también. Por ejemplo, antes de Python 3.5, ¡ datetime.time(0)también era falso!
Antti Haapala
19
Esto es malo. Recomiendo agregar un aviso sobre sus dificultades. Y recomendando no usarlo.
Mateen Ulhaq
61

Estrictamente,

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

De lo contrario, s = Falsese convertirá"default value" , lo que puede no ser lo que se pretendía.

Si quieres acortar esto, prueba:

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

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

Aquí hay una función que devolverá el primer argumento que no es 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()podría iterar innecesariamente sobre todos los argumentos, incluso si el primer argumento no Nonelo es , por lo que también puede usar esta versión:

def coalesce(*arg):
  for el in arg:
    if el is not None:
      return el
  return None
mortehu
fuente
23
def coalesce(*arg): return next((a for a in arg if a is not None), None)hace lo mismo que su último ejemplo en una línea.
glglgl
2
Entiendo que la gente quiere explicar si más sytnax, etc., pero coalesce toma una lista de argumentos arbitrarios, por lo que esta debería ser la mejor respuesta.
Eric Twilegar
2
glglgl tiene la mejor respuesta. Utilicé timeit en una gran matriz de prueba y la implementación de reducción es inaceptablemente lenta, la versión multilínea para / si es más rápida y la siguiente implementación está muy por detrás. La próxima versión es la mejor en general cuando se considera la simplicidad y la concisión.
arcilla
3
@glglgl tiene un fragmento interesante. Desafortunadamente, debido a que Python no tiene nombre de paso, la fusión como esta no está en cortocircuito; Todos los argumentos se evalúan antes de que se ejecute el código.
user1338062
Consider x()?.y()?.z()
nurettin
5

Me doy cuenta de que esto se responde, pero hay otra opción cuando se trata de objetos.

Si tiene un objeto que podría ser:

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

Puedes usar:

obj.get(property_name, value_if_null)

Me gusta:

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

Al agregar {}como valor predeterminado, si falta "nombre", se devuelve un objeto vacío y se pasa al siguiente get. Esto es similar a null-safe-navigation en C #, que sería como obj?.name?.first.

Craig
fuente
No todos los objetos tienen .get, esto solo funciona para objetos tipo dict
timdiels
Estoy enviando una edición de respuestas para cubrir getattr()también.
dgw
geton dict no usa el parámetro predeterminado si el valor es None pero usa el parámetro predeterminado si el valor no existe porque la clave no está en el dict. {'a': None}.get('a', 'I do not want None')todavía te dará Nonecomo resultado.
Patrick Mevzek
3

Además de la respuesta de Juliano sobre el comportamiento de "o": es "rápido"

>>> 1 or 5/0
1

Entonces, a veces puede ser un atajo útil para cosas como

object = getCachedVersion() or getFromDB()
Orca
fuente
17
El término que está buscando es "cortocircuitos".
jpmc26
1

Además de la respuesta de @Bothwells (que prefiero) para valores individuales, a fin de verificar la evaluación nula de los valores de retorno de la función, puede usar un nuevo operador de morsa (desde python3.8):

def test():
    return

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

Por lo tanto, la testfunción no necesita ser evaluada dos veces (como en a = 2 if test() is None else test())

Henhuy
fuente
1

En caso de que necesite anidar más de una operación de fusión nula como:

model?.data()?.first()

Este no es un problema fácil de resolver or. Tampoco se puede resolver con lo .get()que requiere un tipo de diccionario o similar (y no se puede anidar de todos modos) ogetattr() que arrojará una excepción cuando NoneType no tenga el atributo.

El pip relevante que considera agregar una fusión nula al lenguaje es PEP 505 y la discusión relevante para el documento está en el hilo de python-ideas .

nurettin
fuente
0

Respecto a las respuestas de @Hugh Bothwell, @mortehu y @glglgl.

Configurar el conjunto de datos para probar

import random

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

Definir implementaciones

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)

Hacer la función de prueba

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

Resultados en mac i7 @ 2.7Ghz 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

Claramente el not_none función responde la pregunta del OP correctamente y maneja el problema de "falsedad". También es el más rápido y fácil de leer. Si aplica la lógica en muchos lugares, es claramente la mejor manera de hacerlo.

Si tiene un problema en el que desea encontrar el primer valor no nulo en un iterable, entonces la respuesta de @ mortehu es el camino a seguir. Pero es una solución a un problema diferente al OP, aunque puede manejar parcialmente ese caso. No puede tomar un valor iterativo Y predeterminado. El último argumento sería el valor predeterminado devuelto, pero entonces no pasaría un iterable en ese caso y no es explícito que el último argumento sea un valor predeterminado.

Podrías hacerlo a continuación, pero igual lo usaría not_nullpara el caso de uso de valor único.

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

Para aquellos como yo que tropezaron aquí en busca de una solución viable para este problema, cuando la variable podría estar indefinida, lo más cercano que tengo es:

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

Tenga en cuenta que se necesita una cadena cuando se registran globales, pero luego se utiliza la variable real cuando se verifica el valor.

Más información sobre la existencia de variables: ¿Cómo verifico si existe una variable?

Itaca
fuente
1
(variablename or False) == Truees lo mismo 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 no puede encontrar el nombre dentro del diccionario, devolverá el valor predeterminado, si el nombre existe, agregará cualquier valor existente con 1.

espero que esto pueda ayudar

Angelo
fuente
1
Hola, bienvenido a Stack Overflow. ¿Qué nueva información agrega su respuesta que no estaba cubierta por las respuestas existentes? Ver la respuesta de @ Craig, por ejemplo
Gricey
-6

Las dos funciones a continuación me han resultado muy útiles cuando se trata con muchos casos de pruebas 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
fuente
55
Este tipo de cosas agrega un montón de terminología ligeramente diferente (por ejemplo, "nulo" y "nz", ninguno de los cuales significa nada en el contexto de Python), importado de otros lenguajes, además de variantes (¡estricto o no estricto!). Esto solo agrega confusión. Las comprobaciones explícitas de "es ninguno" son lo que debe utilizar. Además, no obtiene el beneficio de ninguna semántica abreviada que los operadores pueden hacer cuando usa una llamada de función.
spookylukey