volver, regresar Ninguno, y no regresar en absoluto?

386

Considere tres funciones:

def my_func1():
  print "Hello World"
  return None

def my_func2():
  print "Hello World"
  return

def my_func3():
  print "Hello World"

Todos parecen devolver ninguno. ¿Hay alguna diferencia entre cómo se comporta el valor devuelto de estas funciones? ¿Hay alguna razón para preferir uno frente al otro?

Clay Wardell
fuente
40
Tenga en cuenta que hay una diferencia estilística. return Noneme implica que la función a veces tiene un Nonevalor de no retorno, pero en la ubicación de return None, no existe dicho valor de retorno. Escribir un no returnsignifica para mí que nunca hay un valor de retorno interesante, como un "procedimiento" en lugar de una "función". returnimplica existir temprano de un "procedimiento" según el punto anterior.

Respuestas:

500

Sobre el comportamiento real, no hay diferencia. Todos regresan Noney eso es todo. Sin embargo, hay un momento y un lugar para todo esto. Las siguientes instrucciones son básicamente cómo deberían usarse los diferentes métodos (o al menos cómo me enseñaron que deberían usarse), pero no son reglas absolutas, por lo que puede mezclarlos si lo considera necesario.

Utilizando return None

Esto indica que la función está destinada a devolver un valor para su uso posterior, y en este caso devuelve None. Este valor Nonese puede usar en otro lugar. return Nonenunca se usa si no hay otros posibles valores de retorno de la función.

En el siguiente ejemplo, volvemos person's mothersi el persondado es un ser humano. Si no es un humano, regresamos Noneya personque no tiene un mother(supongamos que no es un animal o algo así).

def get_mother(person):
    if is_human(person):
        return person.mother
    else:
        return None

Utilizando return

Esto se usa por la misma razón que breaken los bucles. El valor de retorno no importa y solo desea salir de toda la función. Es extremadamente útil en algunos lugares, aunque no lo necesite con tanta frecuencia.

Tenemos 15 prisonersy sabemos que uno de ellos tiene un cuchillo. Recorremos cada prisoneruno por uno para verificar si tienen un cuchillo. Si golpeamos a la persona con un cuchillo, simplemente podemos salir de la función porque sabemos que solo hay un cuchillo y no hay razón para verificar el resto prisoners. Si no lo encontramos prisonercon un cuchillo, levantamos una alerta. Esto podría hacerse de muchas maneras diferentes y el uso returnprobablemente no sea la mejor manera, pero es solo un ejemplo para mostrar cómo usar returnpara salir de una función.

def find_prisoner_with_knife(prisoners):
    for prisoner in prisoners:
        if "knife" in prisoner.items:
            prisoner.move_to_inquisition()
            return # no need to check rest of the prisoners nor raise an alert
    raise_alert()

Nota: nunca debe hacerlo var = find_prisoner_with_knife(), ya que el valor de retorno no debe ser capturado.

Usando no returnen absoluto

Esto también regresará None, pero ese valor no está destinado a ser usado o capturado. Simplemente significa que la función finalizó con éxito. Básicamente es lo mismo que returnen voidfunciones en lenguajes como C ++ o Java.

En el siguiente ejemplo, establecemos el nombre de la madre de la persona y luego la función se cierra después de completarse con éxito.

def set_mother(person, mother):
    if is_human(person):
        person.mother = mother

Nota: nunca debe hacerlo var = set_mother(my_person, my_mother), ya que el valor de retorno no debe ser capturado.

Josh Downes
fuente
55
var = get_mother()está bien si está pasando vara otras funciones que aceptan None- "nunca" es un poco extremo
joelb
¿Cómo se debe aceptar el valor de retorno si var = get_mother()no se puede usar? En mi humilde opinión, está totalmente bien hacer eso. Solo necesita asegurarse de verificar si no hay un Nonevalor if varantes de usarlo.
winklerrr
Pensé que era importante mencionar que esta no es solo una buena convención, es requerida por PEP8. Publico una respuesta para felicitar la suya y cito PEP8 sobre esto.
Fabiano
29

Sí, todos son iguales.

Podemos revisar el código de máquina interpretado para confirmar que todos están haciendo exactamente lo mismo.

import dis

def f1():
  print "Hello World"
  return None

def f2():
  print "Hello World"
  return

def f3():
  print "Hello World"

dis.dis(f1)
    4   0 LOAD_CONST    1 ('Hello World')
        3 PRINT_ITEM
        4 PRINT_NEWLINE

    5   5 LOAD_CONST    0 (None)
        8 RETURN_VALUE

dis.dis(f2)
    9   0 LOAD_CONST    1 ('Hello World')
        3 PRINT_ITEM
        4 PRINT_NEWLINE

    10  5 LOAD_CONST    0 (None)
        8 RETURN_VALUE

dis.dis(f3)
    14  0 LOAD_CONST    1 ('Hello World')
        3 PRINT_ITEM
        4 PRINT_NEWLINE            
        5 LOAD_CONST    0 (None)
        8 RETURN_VALUE      
David Marx
fuente
44
cuidado aquí ... dis.disregresa None: -X
mgilson
La única forma de obtener la salida de dis como una cadena es redirigir stdout a algún tipo de búfer de E / S (por ejemplo, StringIO). Incluso entonces, comparar directamente así podría no funcionar, ya que creo que dis.distambién informa algunos números de línea ...
mgilson
Es irrelevante si están utilizando exactamente los mismos registros de todos modos, así que cambié mi código, por lo que ahora solo estamos mirando el código de la máquina en lugar de hacer algún tipo de comparación de cadena tonta. Gracias por los grandes avisos.
David Marx
19

Cada uno devuelve el mismo singleton None: no hay diferencia funcional.

Creo que es razonablemente idiomático returnomitir la declaración a menos que la necesite para salir de la función antes de tiempo (en cuyo caso un desnudo returnes más común) o devolver algo distinto de None. También tiene sentido y parece ser idiomático escribir return Nonecuando está en una función que tiene otra ruta que devuelve algo distinto de None. Escribir return Noneexplícitamente es una señal visual para el lector de que hay otra rama que devuelve algo más interesante (y que el código de llamada probablemente necesitará manejar ambos tipos de valores de retorno).

A menudo, en Python, las funciones que devuelven Nonese usan como voidfunciones en C: su propósito generalmente es operar sobre los argumentos de entrada en su lugar (a menos que esté usando datos globales ( estremecimientos )). Regresar Nonegeneralmente hace más explícito que los argumentos fueron mutados. Esto deja un poco más claro por qué tiene sentido omitir la returndeclaración desde el punto de vista de las "convenciones del lenguaje".

Dicho esto, si está trabajando en una base de código que ya tiene convenciones preestablecidas en torno a estas cosas, definitivamente haría lo mismo para ayudar a que la base de código se mantenga uniforme ...

mgilson
fuente
1
O, de manera más general, cada vez que una función termina sin tocar una returninstrucción, regresa None.
No es idiomático omitir la declaración return si se espera que la función devuelva un valor. Solo las funciones que operan únicamente a través de efectos secundarios deben omitir la declaración de devolución.
molécula
@molecule - Sí, creo que tal vez no hice el mejor trabajo en mi explicación aquí. Intenté actualizarlo sin convertirlo en un clon completo de la (mucho mejor) respuesta anterior. Todavía no estoy muy contento con esta publicación ... Parte de mí quiere eliminarla (ya que la respuesta anterior es mucho mejor) y parte de mí quiere mantenerla: 9 personas hasta ahora han pensado que era bueno para una razón u otra ¿Tal vez me topé con algo que alguien más se perdió?
mgilson
1
@ZAB - No creo que sea posible. En python puedo parchear cualquier cosa con cualquier cosa (esto es por diseño y es una gran característica si se usa con moderación). Específicamente, puedo parchear una función que tiene un retorno con una que no. Todas las comprobaciones de "no una expresión" suceden en el momento del análisis, pero debido a los parches de mono, esto no podría suceder hasta el tiempo de ejecución. IOW, es completamente diferente. Personalmente, creo que el comportamiento actual es bastante razonable (y consistente con otros lenguajes de alto nivel), pero no soy el que necesita convencer :-).
mgilson
1
@ZAB: ambos Rubyy Javascriptadopta el mismo enfoque que Python. Estoy seguro de que no son ejemplos de lenguajes de alto nivel que dicen "vacío" en la expresión, pero no puedo pensar en ninguna en el momento (tal vez si se tiene en cuenta Javaun lenguaje de alto nivel) ... ¿Cómo lo haría mono-parcheo ¿Una función (que es útil para burlarse en las pruebas, entre otras cosas) funciona en un mundo hipotético donde Python obtuvo una voidpalabra clave que la hizo para que la función no devolviera nada? (Además, como dije antes, no necesitas convencerme de que este es un mal diseño. No puedo hacer nada, incluso si tienes razón)
mgilson
4

Como han respondido otros, el resultado es exactamente el mismo, Nonese devuelve en todos los casos.

La diferencia es estilística, pero tenga en cuenta que PEP8 requiere que el uso sea consistente:

Sea consistente en las declaraciones de devolución. O todas las declaraciones de retorno en una función deberían devolver una expresión, o ninguna de ellas debería hacerlo. Si cualquier declaración de retorno devuelve una expresión, cualquier declaración de retorno donde no se devuelve ningún valor debe indicarlo explícitamente como return None, y una declaración de retorno explícita debe estar presente al final de la función (si es accesible).

Si:

def foo(x):
    if x >= 0:
        return math.sqrt(x)
    else:
        return None

def bar(x):
    if x < 0:
        return None
    return math.sqrt(x)

No:

def foo(x):
    if x >= 0:
        return math.sqrt(x)

def bar(x):
    if x < 0:
        return
    return math.sqrt(x)

https://www.python.org/dev/peps/pep-0008/#programming-recommendations


Básicamente, si alguna vez devuelve un Nonevalor no válido en una función, significa que el valor devuelto tiene significado y está destinado a ser captado por las personas que llaman. Entonces, cuando regrese None, también debe ser explícito, para transmitir Noneen este caso tiene sentido, es uno de los posibles valores de retorno.

Si no necesita ningún retorno, su función básicamente funciona como un procedimiento en lugar de una función, por lo que simplemente no incluya la returndeclaración.

Si está escribiendo una función similar a un procedimiento y existe la oportunidad de regresar antes (es decir, ya ha terminado en ese punto y no necesita ejecutar el resto de la función), puede usar una returns vacía para indicar al lector es solo un final temprano de la ejecución y el Nonevalor devuelto implícitamente no tiene ningún significado y no debe ser capturado (la función similar a un procedimiento siempre regresa de Nonetodos modos).

Fabiano
fuente
3

En términos de funcionalidad, todos son iguales, la diferencia entre ellos está en la legibilidad y el estilo del código (que es importante tener en cuenta)

Yehuda Schwartz
fuente