usar tutorial tablas recorrer para panda leer hacer funciones documentacion datos data como comandos python function return-type

python - tutorial - ¿Por qué las funciones siempre devuelven el mismo tipo?



pandas python tutorial (7)

Leí en alguna parte que las funciones siempre deberían devolver solo un tipo, por lo que el siguiente código se considera un código incorrecto:

def x(foo): if ''bar'' in foo: return (foo, ''bar'') return None

Supongo que la mejor solución sería

def x(foo): if ''bar'' in foo: return (foo, ''bar'') return ()

¿No sería más barato que la memoria devolviera una Ninguna para crear una nueva tupla vacía o esta diferencia de tiempo es demasiado pequeña como para notarla incluso en proyectos más grandes?


¿Por qué deberían las funciones devolver valores de un tipo consistente? Para cumplir con las siguientes dos reglas.

Regla 1: una función tiene un "tipo": entradas asignadas a las salidas. Debe devolver un tipo de resultado consistente, o no es una función. Es un desastre.

Matemáticamente, decimos que alguna función, F, es un mapeo del dominio, D, al rango, R. F: D -> R El dominio y el rango forman el "tipo" de la función. Los tipos de entrada y el tipo de resultado son tan esenciales para la definición de la función como el nombre o el cuerpo.

Regla 2: cuando tiene un "problema" o no puede devolver un resultado adecuado, genere una excepción.

def x(foo): if ''bar'' in foo: return (foo, ''bar'') raise Exception( "oh, dear me." )

Puede romper las reglas anteriores, pero el costo de mantenimiento y comprensibilidad a largo plazo es astronómico.

"¿No sería más barato en cuanto a memoria devolver un Ninguno?" Pregunta equivocada.

El punto no es optimizar la memoria a costa de un código claro, legible y obvio.


Aquí están mis pensamientos sobre todo eso e intentaré explicar también por qué creo que la respuesta aceptada es en su mayoría incorrecta.

En primer lugar, todas programming functions != mathematical functions . Lo más cercano que puede llegar a las funciones matemáticas es si realiza una programación funcional, pero incluso entonces hay muchos ejemplos que dicen lo contrario.

  • Las funciones no tienen que tener entrada
  • Las funciones no tienen que tener salida.
  • Las funciones no tienen que asignar la entrada a la salida (debido a los dos puntos anteriores anteriores)

Una función en términos de programación se debe ver simplemente como un bloque de memoria con un inicio (el punto de entrada de la función), un cuerpo (vacío o de otro tipo) y un punto de salida (uno o varios según la implementación), todos los cuales están ahí con el propósito de reutilizar el código que has escrito. Incluso si no lo ves, una función siempre "devuelve" algo. Este algo es en realidad la dirección de la siguiente declaración justo después de la llamada a la función. Esto es algo que verás en todo su esplendor si haces alguna programación de muy bajo nivel con un lenguaje ensamblador (te animo a hacer un esfuerzo adicional y hacer un código de máquina a mano como Linus Torvalds, que siempre lo menciona durante Sus seminarios y entrevistas: D). Además, también puede tomar alguna entrada y también escupir una salida. Es por eso que

def foo(): pass

Es una pieza de código perfectamente correcta.

Entonces, ¿por qué sería malo devolver varios tipos? Bueno ... No lo es en absoluto a menos que lo abuses. Esto es, por supuesto, una cuestión de habilidades de programación deficientes y / o no saber qué puede hacer el lenguaje que está usando.

¿No sería más barato que la memoria devolviera una Ninguna para crear una nueva tupla vacía o esta diferencia de tiempo es demasiado pequeña como para notarla incluso en proyectos más grandes?

Que yo sepa, sí, devolver un objeto NoneType sería mucho más barato en cuanto a memoria. Aquí hay un pequeño experimento (los valores devueltos son bytes):

>> sys.getsizeof(None) 16 >> sys.getsizeof(()) 48

Basado en el tipo de objeto que está utilizando como su valor de retorno (tipo numérico, lista, diccionario, tupla, etc.), Python administra la memoria de diferentes formas, incluido el almacenamiento inicialmente reservado.

Sin embargo, también debe considerar el código que está alrededor de la llamada a la función y cómo maneja lo que devuelve su función. ¿Comprobar si no es NoneType ? ¿O simplemente verifica si la tupla devuelta tiene una longitud de 0? Esta propagación del valor devuelto y su tipo ( NoneType tipo frente a tupla vacía en su caso) podría ser más tedioso de manejar y explotar en su cara. No lo olvide: el código en sí está cargado en la memoria, por lo que si el manejo del NoneType requiere demasiado código (incluso pequeños fragmentos de código, pero en gran cantidad) es mejor dejar la tupla vacía, lo que también evitará la confusión en la mente de las personas que usan Su función y olvidando que en realidad devuelve 2 tipos de valores.

Hablando de devolver múltiples tipos de valor, esta es la parte en la que estoy de acuerdo con la respuesta aceptada (pero solo parcialmente): devolver un solo tipo hace que el código sea más fácil de mantener, sin lugar a dudas. Es mucho más fácil verificar solo el tipo A y luego el A, B, C, ... etc.

Sin embargo, Python es un lenguaje orientado a objetos y, como tal, herencia, clases abstractas, etc., y todo lo que forma parte de la totalidad de los chanchullos OOP entra en juego. Puede llegar incluso a generar clases sobre la marcha, que descubrí hace unos meses y me sorprendí (nunca vi esas cosas en C / C ++).

Nota al margen: puede leer un poco acerca de las metaclases y las clases dinámicas en este artículo de descripción general con muchos ejemplos.

De hecho, hay múltiples patrones de diseño y técnicas que ni siquiera existirían sin las llamadas funciones polimórficas. A continuación, les presento dos temas muy populares (no puedo encontrar una mejor manera de resumir ambos en un solo término):

  • Tipografía de pato : a menudo es parte de los lenguajes de escritura dinámica de los que Python es un representante de
  • Patrón de diseño del método de Factory : básicamente es una función que devuelve varios objetos según la entrada que recibe.

Finalmente, si su función devuelve uno o varios tipos se basa totalmente en el problema que tiene que resolver. ¿Se puede abusar de este comportamiento polimórfico? Claro, como todo lo demás.


La mejor práctica en cuanto a lo que una función debería devolver varía mucho de un idioma a otro, e incluso entre diferentes proyectos de Python.

Para Python en general, estoy de acuerdo con la premisa de que devolver Ninguno es malo si su función generalmente devuelve un iterable, porque la iteración sin pruebas se vuelve imposible. Simplemente devuelva un iterable vacío en este caso, todavía probará False si usa la prueba de verdad estándar de Python:

ret_val = x() if ret_val: do_stuff(ret_val)

y aún así te permite iterar sobre él sin probar:

for child in x(): do_other_stuff(child)

Para las funciones que probablemente devuelvan un solo valor, creo que devolver Ninguno es perfectamente aceptable, solo documente que esto podría suceder en su cadena de documentación.


La optimización prematura es la fuente de todos los males. Los aumentos de eficiencia minúsculos pueden ser importantes, pero no hasta que haya probado que los necesita.

Sea cual sea su idioma: una función se define una vez, pero tiende a usarse en cualquier número de lugares. Tener un tipo de retorno consistente (sin mencionar las condiciones previas y posteriores documentadas) significa que tiene que hacer un mayor esfuerzo para definir la función, pero simplifica enormemente el uso de la función. ¿Adivina si los costos de una sola vez tienden a superar los ahorros repetidos ...?


No está tan claro que una función siempre debe devolver objetos de un tipo limitado, o que devolver Ninguno es incorrecto. Por ejemplo, re.search puede devolver un objeto _sre.SRE_Match o un objeto NoneType :

import re match=re.search(''a'',''a'') type(match) # <type ''_sre.SRE_Match''> match=re.search(''a'',''b'') type(match) # <type ''NoneType''>

Diseñado de esta manera, puedes probar una coincidencia con el idioma

if match: # do xyz

Si los desarrolladores hubieran requerido re.search para devolver un objeto _sre.SRE_Match , entonces el idioma tendría que cambiar a

if match.group(1) is None: # do xyz

No habría ninguna ganancia importante al requerir que re.search devuelva siempre un objeto _sre.SRE_Match .

Así que creo que la forma en que se diseña la función debe depender de la situación y, en particular, la forma en que se planea utilizarla.

También tenga en cuenta que tanto _sre.SRE_Match como NoneType son instancias de objeto, por lo que en un sentido amplio son del mismo tipo. Así que la regla de que "las funciones siempre deben devolver solo un tipo" no tiene sentido.

Dicho esto, hay una hermosa simplicidad en las funciones que devuelven objetos que comparten todas las mismas propiedades. (¡La escritura de pato, no la estática, es la manera en que se puede usar Python!) Puede permitirle encadenar las funciones: foo (barra (baz)) y conocer con certeza el tipo de objeto que recibirá en el otro extremo.

Esto puede ayudarte a verificar la corrección de tu código. Al exigir que una función devuelva solo objetos de un cierto tipo limitado, hay menos casos para verificar. "foo siempre devuelve un entero, por lo tanto, mientras se espere un entero en todas partes, foo es oro ..."


Personalmente creo que está perfectamente bien que una función devuelva una tupla o Ninguna. Sin embargo, una función debe devolver como máximo 2 tipos diferentes y el segundo debe ser Ninguno. Una función nunca debe devolver una cadena y una lista, por ejemplo.


Si x se llama así

foo, bar = x(foo)

devolviendo None resultaría en una

TypeError: ''NoneType'' object is not iterable

si ''bar'' no está en foo .

Ejemplo

def x(foo): if ''bar'' in foo: return (foo, ''bar'') return None foo, bar = x(["foo", "bar", "baz"]) print foo, bar foo, bar = x(["foo", "NOT THERE", "baz"]) print foo, bar

Esto resulta en:

[''foo'', ''bar'', ''baz''] bar Traceback (most recent call last): File "f.py", line 9, in <module> foo, bar = x(["foo", "NOT THERE", "baz"]) TypeError: ''NoneType'' object is not iterable