Funciones — una decisión, un nombre
Escribir funciones con def, qué separa return de print, tratar casos frontera con una guarda al principio, y cadenas de documentación.
¿Cómo quieres aprender?
El problema que vamos a resolver
Dos grupos necesitan su nota media:
marks_a = [72, 45]
marks_b = [90, 33, 61]
print(round(sum(marks_a) / len(marks_a), 2))
print(round(sum(marks_b) / len(marks_b), 2))58.5
61.33Las dos líneas son casi idénticas; solo cambia el nombre. Un tercer grupo significa una tercera línea, un cuarto significa una cuarta.
El problema no es el tecleo — copiar es barato. El problema es que una decisión está ahora escrita en varios sitios. Si te piden mostrar la media con un decimal en vez de dos, tienes que encontrar y cambiar cada línea, y olvidar una deja el programa comportándose de dos formas distintas — sin error.
¿Y una lista vacía? sum([]) / len([]) divide por cero y el programa se detiene. Esa comprobación habría que añadirla a cada línea por separado.
Una función es una forma de escribir una decisión una vez y darle un nombre. Después, llamar al nombre hace el trabajo, y cambiarlo significa cambiar un solo sitio.
Al terminar este capítulo podrás
- Escribir y llamar una función con
def - Decir qué separa
returndeprint— lo más importante de aquí - Mantener una función pequeña y limitada a un trabajo
- Tratar casos frontera con una guarda al principio
- Escribir una cadena de documentación
Requisitos previos: Comprensiones.
Una primera función
def average(values):
return round(sum(values) / len(values), 2)
print(average([72, 45]))
print(average([90, 33, 61]))58.5
61.33La forma:
def— «estoy definiendo una función»average— el nombre, siguiendo las mismas reglas que un nombre de variable(values)— el parámetro, la puerta por donde entran los datos:y un bloque con sangría — el cuerpo, exactamente igual queifoforreturn— devolver un resultado
Escribir average([72, 45]) es llamarla. [72, 45] es el argumento, lo que se envía de verdad; values es el parámetro, el nombre de dentro. Dos palabras distintas, y las dos aparecen en los mensajes de error, así que vale la pena mantener la distinción.
Dos líneas en blanco. Dejar dos líneas en blanco después de la definición de una función es la convención de Python. Python funciona bien con menos, pero todo el código Python se escribe así, así que merece la pena adquirir el hábito.
return y print no son lo mismo
Esta es la sección más importante del capítulo, y la mayor confusión de quienes empiezan.
def with_return(x):
return x * 2
def with_print(x):
print(x * 2)
a = with_return(5)
b = with_print(5)
print(a)
print(b)
print(a + 1)10
10
None
11Los dos primeros 10 en pantalla hacen que las funciones parezcan idénticas. No lo son.
with_return entregó el valor de vuelta, así que a guarda 10 y se le puede sumar — de ahí el 11 en la última línea.
with_print mostró el valor y no entregó nada, así que b es None. El número apareció en pantalla y el programa no recibió nada. b + 1 lanzaría un TypeError.
Guarda la regla así: print es para una persona, return es para el programa. Una función que calcula debe return; mostrar pasa donde se llamó. Así la misma función puede servir a una pantalla un día y a un archivo al siguiente.
Sin un return, una función devuelve None en silencio — y ese es el mismo None que append y .sort() devolvieron en los capítulos doce y trece. Ahora puedes ver por qué lo hacían: su trabajo era cambiar algo, no entregar nada.
return detiene inmediatamente
def check(mark):
if mark >= 40:
return "pass"
return "fail"
print(check(72))
print(check(20))pass
failEn cuanto se ejecuta return, la función termina — las líneas de después nunca se ejecutan. Por eso no hizo falta else aquí: si el primer return ocurre, la segunda línea es inalcanzable.
Esta forma — return en cuanto una condición coincide — aplana los árboles profundos de if/else del capítulo nueve y hace el código mucho más fácil de leer.
Tratar los casos frontera — una guarda
¿Recuerdas el problema con el que abrió este capítulo? La lista vacía:
def average(values):
return sum(values) / len(values)
print(average([]))ZeroDivisionError: division by zeroLa corrección va al principio de la función:
def average(values):
if not values:
return 0.0
return round(sum(values) / len(values), 2)
print(average([72, 45]))
print(average([]))58.5
0.0if not values es la regla de veracidad del capítulo ocho otra vez: una lista vacía es falsa, así que not la hace verdadera. No hace falta escribir len(values) == 0.
Esto se llama una guarda: tratas el caso anómalo al principio, y después escribes el resto sin preocuparte. Las funciones hacen esto barato — la comprobación se escribe una vez y todos los que llaman se benefician.
¿Es0.0la respuesta correcta? Discutible. Confunde «nadie tiene nota» con «todos sacaron cero». Los programas reales suelen devolverNone, o lanzar un error con elraisedel capítulo veinticuatro.0.0se queda aquí por simplicidad — pero sabe que es una decisión, no la respuesta obvia.
Cadenas de documentación
Un texto en la primera línea de la función se vuelve su documentación:
def average(values):
"""Return the mean of values, or 0.0 when there are none."""
if not values:
return 0.0
return round(sum(values) / len(values), 2)
print(average([1, 2, 3]))
print(average.__doc__)2.0
Return the mean of values, or 0.0 when there are none.Lo que la separa de un comentario es que sobrevive en el programa en marcha — los editores la muestran, y help(average) la imprime.
Una buena cadena de documentación dice qué devuelve y qué pasa en los casos frontera, no cómo funciona el código. El código está justo debajo.
Un ejemplo completo
report.py:
# Three small functions, each doing one thing
def average(values):
"""Return the mean of values, or 0.0 when there are none."""
if not values:
return 0.0
return round(sum(values) / len(values), 2)
def grade(mark):
"""Turn a mark into a letter."""
if mark >= 80:
return "A"
if mark >= 70:
return "B"
if mark >= 40:
return "C"
return "F"
def summarise(name, marks):
"""Build one report line for a person."""
mean = average(marks)
return f"{name:<8} {len(marks):>2} papers avg {mean:>6} grade {grade(mean)}"
people = {
"rafi": [72, 88, 61],
"ahmed": [45, 38],
"dia": [],
}
for name, marks in people.items():
print(summarise(name, marks))rafi 3 papers avg 73.67 grade B
ahmed 2 papers avg 41.5 grade C
dia 0 papers avg 0.0 grade FCuatro cosas que vale la pena notar.
Cada función hace un trabajo. average promedia, grade da una letra, summarise construye una línea. Ninguna imprime — la impresión pasa en el bucle de abajo, en un sitio. Así que escribir este informe a un archivo en vez de a pantalla significa cambiar esa sola línea.
Las funciones llaman a funciones. summarise llama a average y grade. Este es el beneficio real: trabajo grande montado de piezas pequeñas y fiables.
grade no tiene else. Cada return para la función, así que las condiciones se quedan planas. La regla del capítulo nueve sigue valiendo: la condición más estricta primero — poner 40 antes que 80 daría a todo el mundo una C.
La lista vacía de dia no rompió nada. La guarda devolvió 0.0, grade lo convirtió en F, y la línea del informe se imprimió normal. El caso frontera se trató una vez, en un sitio.
Cuando falla
NameError: name 'greet' is not defined — pero la función está ahí Se llamó antes de que su línea def se ejecutara. Python lee un archivo de arriba a abajo, y el nombre no existe hasta que el def se ejecuta. Mantén todos los def arriba y el código que llama abajo.
TypeError: average() missing 1 required positional argument: 'values' No se dio ningún argumento — average() en vez de average(marks). El mensaje hasta nombra el parámetro que falta.
La función devuelve None No hay return, o hay uno en la sangría equivocada — dentro de un if cuya condición no se cumple, por ejemplo. Otra causa común: se escribió print donde se quería return.
Una variable de dentro de la función no se ve fuera Eso se espera — por qué es el tema del capítulo veintiuno. Por ahora: return lo que haga falta fuera.
SyntaxError — sin dos puntos en la línea def Como if y for, una línea def termina en dos puntos.
IndentationError: expected an indented block after function definition Un def necesita al menos una línea con sangría después.
Paso 4 de 6 — Predice
Comprueba lo que has entendido
La función imprime en vez de devolver. ¿Qué imprimen las dos líneas?
def double(x):
print(x * 2)
result = double(5)
print(result)- A10 None
- B10 10
- CNone 10
- D10
Dos return y nada de else. ¿Qué se imprime?
def check(mark):
if mark >= 40:
return "pass"
return "fail"
print(check(72))- Apass
- Bpass fail
- Cfail
- DNone
La función está ahí en el archivo. ¿Qué pasa aun así?
print(greet("rafi"))
def greet(name):
return "hello " + name- AUn `SyntaxError`
- Bhello rafi
- CUn `TypeError`
- DUn `NameError` — la función se llamó antes de que su línea `def` se ejecutara
Para responder necesitas una cuenta
Inicia sesión para comprobar tus respuestas
Las preguntas están arriba, y resolverlas mentalmente es lo que de verdad importa. Inicia sesión para ver las respuestas, las explicaciones y las pistas de tres niveles.
Tu turno
Escribe un archivo llamado text_tools.py con cuatro funciones, cada una con su cadena de documentación, y ninguna imprimiendo nada:
clean(text)— recorta espacios y pone en minúsculasinitials(full_name)— convierte"Rafi Ahmed"en"RA"word_count(text)— cuenta palabras, dando0para una cadena vacíasummarise(text)— llama a las otras tres y devuelve una línea legible
Luego, al final del archivo, llama a summarise con unas cuantas cadenas e imprime los resultados — ese es el único sitio donde algo se imprime.
Por último, tres experimentos:
- Llama a
word_count(""). ¿Qué habría pasado sin la guarda? - Cambia el
returndesummarisepor unprint, luego guarda su resultado en una variable e imprime esa. ¿Qué ves, y por qué? - Mueve la llamada a
cleanpor encima de su propiodef, y lee el error.
El segundo es los tres minutos más valiosos de este capítulo. Ver la diferencia entre print y return una vez con tus propios ojos significa no confundirlas nunca más.
Paso 6 de 6
Reto — el quiz del capítulo
Diez preguntas de fácil a difícil. Las últimas son difíciles a propósito.
Inicia sesión para hacer el quiz