06 - Curso de Python - Funciones

De ChuWiki

Cualquier duda suelo atender en este foro de python

Todo el código de este curso de Python gratuito está en github https://github.com/chuidiang/chuidiang-ejemplos/tree/master/PYTHON/curso-python. En línea comandos python tienes como arrancar la consola de comandos de python por si quieres ir probando lo relativo a variables y tipos de datos en python.

Anterior: 05 - Curso de Python - Operadores aritmeticos y de cadenas -- Índice: Curso de Python -- Siguiente: 07 - Curso de Python - Entradas y Salidas Standard.

¿Qué es una función?[editar]

Hay trozos de código que hacen algo concreto y que en nuestro programa podemos querer utilizar con cierta frecuencia. No es una buena solución repetir ese trozo de código en varios sitios. Quizás el copy-paste nos ayude a hacerlo y aparentemente sea solo un poco engorroso, pero ¿qué pasa si hay un error y tenemos que corregirlo?. Pues que hay que ir por todo el código arreglando todas las copias que hemos hecho. ¿Y si el código que queremos reutilizar es muy grande?. Pues al final nos quedarán programas muy largos donde muchas líneas están repetidas.

Es buena costumbre de programación meter ese código que podemos querer usar en muchos sitios en una "función". Veamos un ejemplo muy sencillo para entenderlo mejor. Imagina que tenemos tres números que representan un ángulo: grados, minutos y segundos. Queremos convertirlo a grados con decimales. Por ejemplo, 1 grado y 30 minutos son 1,5 grados, ya que un grado tiene 60 minutos. La cuenta a realizar sería

>>> grados = 1
>>> minutos = 30
>>> segundos = 0
>>> grados + minutos/60 + segundos/3600
1.5

Aunque solo es una línea, podemos meterlo en una función de la siguiente forma

Definición de función en python[editar]

>>> def ggmmss_a_gg(grados,minutos,segundos):
...    return grados + minutos/60 + segundos/3600
...
>>> ggmmss_a_gg(1,30,0)
1.5

La función se define con la palabra def, un nombre que le queramos dar a la función, en este caso ggmmss_a_gg que es una especie de abreviatura de grados, minutos y segundos a grados. Luego, entre paréntesis, variables que representen los valores que vamos a usar, en este caso grados, minutos y segundos. Estas variables entre paréntesis al definir la función se llaman parámetros de la función. Según lo que necesitemos en la función, podemos poner más o menos parámetros o incluso ninguno, dejando entonces los paréntesis vacíos (). Luego un dos puntos : y en las siguientes líneas, sangrando a la derecha, todas las líneas que necesitemos para nuestra función. En este caso solo la cuente.

Como queremos obtener un valor de vuelta, ponemos return con el valor que queremos devolver, que en este caso, es el resultado de la cuenta. Si no queremos que la función devuelva nada, no haría falta poner el return. Este sería el caso, por ejemplo, de funciones que escriben algo por pantalla.

Hecho esto, solo nos queda llamar a la función con su nombre y pasando entre paréntesis y en orden, los valores de grados, minutos y segundos que queremos convertir.

Pasar parámetros por nombre[editar]

Hemos comentado que la llamada debe ser con los parámetros en el mismo orden. Tenemos sin embargo otra opción que es ponerlos en cualquier orden siempre y cuando digamos el nombre del parámetro, por ejemplo

>>> ggmmss_a_gg(segundos=0, grados=1, minutos=30)
1.5

Hemos pasado primero los segundos, luego los grados y finalmente los minutos. Pero hemos puesto el nombre de la variable y un =.

Parámetros por defecto[editar]

En el momento de declarar la función podemos dar valores por defecto a los parámetros, así

>>> def ggmmss_a_gg(grados=0, minutos=0, segundos=0):
...    return grados + minutos/60 + segundos/3600
...

De esta forma, si no indicamos algún parámetro, se entiende que su valor es 0. Las siguientes llamadas serían válidas

>>> # Sin parámetros, se entiende que todos valen cero.
>>> ggmmss_a_gg()
0.0

>>> # Algunos de los parámetros, indicando su nombre. 
>>> ggmmss_a_gg(minutos=30)
0.5

>>> # Los parámetros en orden, pero no necesariamente todos. En este caso, solo los grados.
>>> ggmmss_a_gg(1)
1.0

Entre el valor de los parámetros por defecto podemos usar None para indicar que no hay valor para el parámetro, pero tendremos que tenerlo en cuenta a la hora de hacer el código de la función

>>> def ggmmss_a_gg(segundos=None, grados=None, minutos=None):
...    return grados + minutos/60 + segundos/3600
...
>>> ggmmss_a_gg(minutos=30)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in ggmmss_a_gg
TypeError: unsupported operand type(s) for +: 'NoneType' and 'float'

Vemos que da error. De una forma algo críptica nos dice que no puede sumar un número con None. Una definición que solventa este prroblema

>>> def ggmmss_a_gg(grados=None, minutos=None,segundos=None):
...    if (grados==None or minutos==None or segundos==None):
...       print('Debes meter todos los valores')
...       return
...    return grados + minutos/60 + segundos/3600
...
>>> ggmmss_a_gg(1)
Debes meter todos los valores

Verificamos con un if si alguno de los parámetros no tiene valor, sacamos un aviso en caso afirmativo y salimos de la función con <codereturn sin devolver ningún valor. En caso contrario, echamos la cuenta y devolvemos el valor.

Ambito de las varaibles y parámetros[editar]

>>> def valor(a):
...    a=5
...    print(a)
...
>>> a=9
>>> valor(a)
5
>>> print (a)
9
>>>

Hemos definido una función valor() con un parámetro a. Dentro de la función asignamos un valor a a y lo sacamos por pantalla. Después, creamos una variable a y le damos valor 9. Llamamos a valor() pasando esta variable a, dentro de la función se pone un valor 5 y se saca por pantalla. Si escribimos después la variable a de fuera de la función, veremos que sigue valiendo 9. No se ha alterado su valor dentro de la función.

El motivo de esto es que la variable a dentro de la función es distinta de la variable a que hay fuera de la función, aunque se llamen igual, y pueden tener valores distintos. De hecho, si en el mismo ejemplo hubieramos llamado b al parámetro de la función, seguramente no daría lugar a dudas de si a y b son lo mismo

>>> def valor(b):
...    b=5
...    print(b)
...
>>> a=9
>>> valor(a)
5
>>> print (a)
9
>>>

Aquí parece más claro, a y b son variables distintas. Y este ejemplo nos da pie a verificar lo siguiente, la variable b solo está disponible dentro de la función. Si intentamos sacarla por pantalla fuera de la función obtendremos un error.

>>> def valor(b):
...    b=5
...    print(b)
...
>>> a=9
>>> valor(a)
5
>>> print(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined
>>>

Esto es importante. Los parámetros y variables que declaremos dentro de una función solo existen dentro de la función. No se pueden utilizar fuera y si los declaras fuera, son variables distintas aunque se llamen igual.

Compliquemos un poco el ejemplo, imagina que hacemos lo mismo, pero con una lista

<pre>
>>> def valor(b):
...    b[2]=44
...    print(b)
...
>>> a=[1,2,3,4]
>>> valor(a)
[1, 2, 44, 4]
>>> print(a)
[1, 2, 44, 4]

Suponemos ahora que el parámetro b va a ser una lista y dentro de la función, en su tercera posición ponemos un 44. Recuerda que la primera posición es la de índice 0. Luego sacamos por pantalla la lista.

Ya fuera de la función declaramos una variable a con una lista, llamamos a la función pasando esta lista como parámetro y vemos como, efectivamente, la tercera posición cambia a un valor 44. Pero ahora, también fuera de la función, sacamos por pantalla la lista a. Vemos que también ha cambiado el valor de su tercera posición. ¿Por qué es esto?. Porque como dijimos al principio, a y b son variables distintas, pero la lista que tienen dentro es la misma. No se hace una copia de la lista, así que si cualquiera de las variables modifica algún valor de la lista, ambas variables ven el cambio.

En ocasiones esto puede ser lo que deseamos, que la función nos cambie el contenido de la lista siguiendo algún criterio, porque es el objetivo de dicha función. Pero si queremos evitar esto, debemos pasar una copia de la lista original, como en el siguiente ejemplo

>>> def valor(b):
...    b[2]=44
...    print(b)
...
>>> a=[1,2,3,4]
>>> valor(a.copy())
[1, 2, 44, 4]
>>> print(a)
[1, 2, 3, 4]

Veamos ahora otro ejemplo por ir un paso más allá y que quede claro otro punto adicional. Imagina que dentro de la función cambiamos la lista entera así

>>> def valor(b):
...    b=['a','b','c']
...    print(b)
...
>>> a=[1,2,3]
>>> valor(a)
['a', 'b', 'c']
>>> print(a)
[1, 2, 3]

Aquí ves que el cambio en b no se refleja en a. El motivo es el que indicamos al principio, a y b son variables disintas. Si a la variable b le asignamos una lista entera completa, la variable a no va a cambiar, no va a tener la misma lista que b, sino que mantiene la lista original. ¿Y cual es la diferencia con el ejemplo en el que alterabamos la tercera posición de la lista?. La diferencia es la asignación a la variable b. En ese ejemplo, en ningún momento, asignabamos a b una variable nueva. Simplemente alterábamos el contenido de la lista que tenía. En este último ejemplo sí asignamos a b una lista nueva completa.

Número arbitrario de parámetros[editar]

Podemos querer una función que no trate un número fijo de parámetros, sino un número indeterminado de ellos. Un ejemplo es la función print(). Aunque hasta ahora la hemos usado con un solo parámetro siempre, podemos ponerle todos los parámetros que queramos y los saca por pantalla

>>> print(1,'a',[1,2,3])
1 a [1, 2, 3]

Una opción para hacer esto es que el parámetro sea una lista. Algo como esto

>>> def mi_print(a):
...    for valor in a:
...       print (valor)
...
>>> mi_print([1,'a',[1,2,3]])
1
a
[1, 2, 3]

pero la sintaxis de la llamada es un poco engorro, ya que tenemos que poner los []. Y si solo pasamos un parámetro, también tenemos que ponerle los [], porque la función espera una lista. Da error si no lo ponemos

>>> mi_print(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in mi_print
TypeError: 'int' object is not iterable

Así que python nos ofrece una forma más elegante de poner un número indeterminado de paraémtros a una función. Simplemente debemos poner un * delante del nombre del parámetro. Python entiende así que hay un número indeterminado de parámetros y dentro de la función se puede tratar como una tupla.

>>> def mi_print(*a):
...    print(a)
...    for valor in a:
...       print(valor)
...
>>> mi_print(1,'a',[1,2,3])
(1, 'a', [1, 2, 3])
1
a
[1, 2, 3]

Hemos sacado por pantalla a tal cual. Vemos que lo saca como una tupla, porque va entre paréntesis. Si fuera una lista iría entre corchetes. Recuerda que una tupla es una lista que no se puede modificar. Luego hacemos el bucle para ir sacando por pantalla cada elemento de la tupla. El resultado es una función con un número indeterminado de parámetros que se puede llamar cómodamente sin necesidad de poner corchetes en la llamada.

Las listas tienen también una opción de "desempaquetado". Imagina el caso contrario al que hemos expuesto ahora, una función que admite dos parámetros y resulta que cuando queremos llamarla, tenemos esos dos parámetros en una lista. El siguiente código explica opciones y si funionan o no.

>>> # Una funcion que suma.
>>> def suma(a,b):
...    return a+a
...

>>> # Los valores a sumar, por circunstancias de la vida, los tenemos en una lista
>>> lista=[1,2]

>>> # Si intentamos llamar a la función pasando la lista directamente, tenemos error.
>>> print (suma(lista))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: suma() missing 1 required positional argument: 'b'

>>> # Podemos llamar pasando los paraémtros uno a uno, sacándolos de la lista.
>>> print (suma(lista[0],lista[1]))
2

>>> # O usamos el *, que desepaqueta la lista.
>>> print (suma(*lista))

Vemos que el * desempaqueta la lista, asignando cada uno de sus elementos a uno de los parámetros. Hasta cierto punto es lo contrario del caso anterior. En el caso anterior, convertiamos n parámetros en la llamada en un tupla dentro de la función. Aqui separamos una lista en la llamada en n parámetros dentro de la función.

Número arbitrario de parámetros por nombre[editar]

En el ejemplo que acabamos de ver admitimos un número indefinido de parámetros que irán en orden en la llamada, es decir, el primero es el primero, el segundo el segundo, etc. Pero a veces nos puede interesar identificar el parámetro con algún nombre y no por su posición. Siempre es más elegante en código si cada parámetro es una cosa distinta tener una forma fácil de identificarlo, un nombre.

Para ello podemos usar el doble asterisco **. Esto hace que la función espere una lista de parejas clave/valor (o nombre/valor), similar a un diccionario. Por ejemplo

>>> def nombre_y_apellidos(**a):
...    print(a['nombre'])
...    print(a['apellidos'])
...
>>> nombre_y_apellidos(nombre='Juan', apellidos='Gomez Perez', edad=24)
Juan
Gomez Perez

La función admite una variable a que podremos interpretar como un diccionario, es decir, varias parejas clave/valor. Sacamos por pantalla los valores cuyos nombres son nombre y apellidos. Luego, en la llamada, podemos pasar todos los parámetros que queramos, pero solo se hará caso a aquellos que se llamen nombre y apellidos.

Y al igual que antes, podemos usar ** para desempaquetar un diccionario en una llamada, siempre que las claves del diccionario coincidan con los parámetros. No podemos en este caso poner de más, debemos poner las justas que espera la función.

>>> def nombre_y_apellidos(nombre, apellidos):
...    print(nombre)
...    print(apellidos)
...

>>> persona={'nombre':'Juan','apellidos':'Gomez Perez'}

>>> # La llamada tal cual da error.
>>> nombre_y_apellidos(persona)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: nombre_y_apellidos() missing 1 required positional argument: 'apellidos'

>>> # Pero con doble asterisco funciona.
>>> nombre_y_apellidos(**persona)
Juan
Gomez Perez

Documentar la función[editar]

Comentario[editar]

Como la función es un trozo de código que vamos a querer usar varias veces o incluso pueden usar otras personas que trabajen en nuestro grupo de desarrollo. O incluso si publicamos las funciones en internet para que la use quien quiera, suele ser buena idea documentarla de alguna forma. Veamos algunas posiblidades.

Una opción es poner justo en la línea detrás de def nombre(parametros): un texto explicativo, comenzado y acabado en triples comillas """, así

>>> def suma(a,b):
...    """ Devuelve a+b """
...    return a+b
...

Este texto no tiene por qué ocupar una línea, puede ir en varias líneas, sólo es necesario unas triples comillas al principio de la primera línea y otras al final de la última. Aparte de ver el comentario en la definción de la función, luego se puede acceder a él de estas dos formas

>>> help(suma)
Help on function suma in module __main__:

suma(a, b)
    Devuelve a+b

>>> print (suma.__doc__)
 Devuelve a+b
  • Llamando a la función help() que viene predefinida en python pasándole el nombre de la función. Esto saca por pantalla una descripción de la función.
  • Con suma.__doc__. Esta variable representa nuestro texto tal cual, podemos sacarlo por pantalla.

Tipos[editar]

A veces la función espera parámetros de tipos concretos y devuelve un valor de un tipo concreto. Es posible que si la función es compleja, viendo la definición, no sea evidente qué tipos se esperan o se devuelven. Phyton permite ponerlos de esta forma

>>> def suma(a: int, b: int) -> int:
...    return a+b
...

>>> suma(1,2)
3

>>> suma("a","b")
'ab'

Es decir, detrás de cada nombre de parámetro el tipo separado por dos puntos y al final una flecha -> indicando el tipo de valor devuelto. También, como se ve en el ejemplo, esto es puramente una ayuda. Nada nos impide luego llamar pasando parámetros tipo string, por ejemplo. Si la función funciona con ellos, no saltará ningún tipo de error por poner tipos de parámetros que no son.

Funciones predefinidas[editar]

Python tiene un montón de funciones predefinidas. Sería complejo enumerarlas todas. Algunas ya lo hemos hecho, como print(). Vemoas algunas más.

max() y min() dan el valor máximo y mínimo respectivamente de una lista

>>> max(1,2,3,4)
4
>>> min(1,2,3,4)
1
>>> max('a','e','i')
'i'

len() da la longitud de algo

>>> len([1,2,3,4]) 4 >>> len('hola') 4

str() convierte algo a texto

>>> str(1)
'1'
>>> str('hola')
'hola'
>>> str([1,2,3])
'[1, 2, 3]'

int() y float() convierten algo a int o float

>>> int(1)
1
>>> int('1')
1
>>> int(1.9)
1
>>> float('3.4')
3.4
>>> float('1e-5')
1e-05
>>> float('1e-1')
0.1

>>> # Si no se puede convertir, da error
>>> float('pepe')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: 'pepe'

sum() devuelve la suma de una colección de números

>>> sum([1,2,3])
6

Si te has fijado antes, int(1.9) convierte a int y devuelve 1, aunque el valor es cercano a 2. count() redondea al entero más cercano.

>>> round(1.1)
1
>>> round(1.4)
1
>>> round(1.4999)
1
>>> round(1.5)
2
>>> round(1.9)
2


Anterior: 05 - Curso de Python - Operadores aritmeticos y de cadenas -- Índice: Curso de Python -- Siguiente: 07 - Curso de Python - Entradas y Salidas Standard.