Funciones.
Operaciones de entrada y
salida
Funciones
Las funciones permiten agrupar un bloque de código o conjunto de instrucciones.
Opcionalmente reciben uno o más argumentos y retornan un valor. En Python, para crear
una función empleamos la palabra reservada def seguido de su nombre y sus parámetros
entre paréntesis, en caso de tener. A continuación se coloca su contenido utilizando una
indentación de 4 espacios.
def f():
pass
La función anterior literalmente no hace nada. La palabra reservada pass se utiliza para
rellenar un bloque de código que es requerido sintácticamente (no podría estar vacío porque
lanzaría un error de sintaxis).
Vamos con algo más productivo:
def sumar(a, b):
return a + b
Hemos creado una función con el nombre sumar con dos parámetros de nombres a y b, y
que retorna el resultado de la suma de ambos. Como se trata de una función pequeña
podemos escribirla también en la consola interactiva, se vería más o menos así:
>>> def sumar(a, b):
... return a + b
Y luego hacer algunas pruebas:
>>> sumar(7, 5)
12
A propósito, los nombres de objetos (“variables”) y funciones en Python se escriben en
minúscula y con guiones bajos en lugar de espacios.
Para indicar parámetros opcionales, indicamos su valor por defecto con el signo de igual
(=).
>>> def imprimir_mensaje(mensaje="No has escrito nada."):
... print(mensaje)
...
>>> imprimir_mensaje("Hola mundo")
Hola mundo
>>> imprimir_mensaje()
No has escrito nada.
(Nótese que, por convención, cuando indicamos valores por defectos en la definición de una
función no se colocan espacios alrededor del signo de igual).
Los argumentos con valores por defecto siempre deben ir al final de la función. Por ejemplo,
el siguiente código es inválido:
def sumar(a=5, b):
return a + b
Cuando una función no retorna ningún valor (es decir, no incluye la palabra reservada
return), por defecto retorna None.
A los argumentos con valores por defecto se los conoce como keyword arguments o
“argumentos por nombre”, ya que al llamar a la función se puede especificar su valor
indicando su nombre. Por ejemplo:
>>> def sumar(a=5, b=10):
... return a + b
...
>>> sumar()
15
>>> sumar(b=50)
55
Esto permite que, en la segunda llamada, a mantenga su valor por defecto mientras que b
obtenga el número 50. En el caso que se especifiquen ambos argumentos, indicar o no su
nombre es indistinto.
>>> sumar(7, 5)
12
>>> sumar(a=7, b=5)
12
Al definir una función generalmente se le añade un una cadena entre comillas triples (otra
forma válida de crear textos en Python) indicando qué es lo que hace.
def sumar(a, b):
"""
Retorna el resultado de la operación a + b.
"""
return a + b
A esto se lo conoce como “docstring”.
La razón por la que se utiliza una cadena y no un comentario es que dicha información es
automáticamente almacenada por Python en un atributo de la función (veremos con detalle
más adelante). Por ejemplo, podemos acceder al docstring de nuestra función vía:
help(sumar)
La función help() está incorporada en el lenguaje y es sumamente útil cuando utilizamos
la consola interactiva. Siempre que quieras conocer de qué trata una función y qué
argumentos espera, intentá llamado a help(nombre_funcion). Los docstrings son
también usados por generadores automáticos de documentación.
Por último, en Python es también posible crear funciones anónimas o lambda. Sin embargo,
por lo general es más conveniente crear funciones convencionales, por lo que únicamente
las mencionamos al pasar.
>>> sumar = lambda a, b: a + b
>>> sumar(5, 7)
12
Más sobre colecciones
Formación de cadenas de caracteres
Habíamos visto cómo crear una cadena de caracteres utilizando comillas dobles o simples.
Sin embargo es bastante usual tener que incluir otros tipos de datos dentro de una cadena.
Para esto utilizamos la función format(). Por ejemplo:
>>> nombre = "Pablo"
>>> edad = 30
>>> mensaje = "Hola, mi nombre es {0}. Tengo {1} años."
>>> [Link](nombre, edad)
'Hola, mi nombre es Pablo. Tengo 30 años.'
En el código creamos una cadena en donde queremos incluir los objetos nombre y edad
(una cadena y un entero, respectivamente). En su lugar, colocamos {0} y {1} (y así
sucesivamente en relación con la cantidad de objetos que queramos incluir) y por último
llamamos a format() pasándole como argumentos los elementos que queremos insertar
dentro de mensaje.
Nótese que las cadenas en Python son objetos inmutables, por lo que format() retorna
una nueva cadena con los valores reemplazados.
A partir de la versión 3.6 Python introduce un método más legible, que consiste en poner
nombres de variables entre llaves dentro de una cadena y anteponiendo una letra f.
>>> nombre = "Pablo"
>>> edad = 30
>>> mensaje = f"Hola, mi nombre es {nombre}. Tengo {edad} años."
>>> mensaje
'Hola, mi nombre es Pablo. Tengo 30 años.'
El lenguaje también soporta un sistema más antiguo proveniente de C a través del operador
%.
>>> "Hola, mi nombre es %s y tengo %d años." % (nombre, edad)
'Hola, mi nombre es Pablo y tengo 30 años.'
De todas formas los métodos recomendados son los dos primeros.
“Unpacking”
Las listas y tuplas soportan un método llamado unpacking (cuya traducción podría ser
“desempaquetamiento”). Veámoslo en un ejemplo:
>>> t = (1, 2, 3)
>>> a, b, c = t
>>> a
1
>>> b
2
>>> c
3
La operación a, b, c = t equivale a:
>>> a = t[0]
>>> b = t[1]
>>> c = t[2]
Es importante que la cantidad de objetos a la izquierda coincida con el número de
elementos en la colección a la derecha.
Entrada y salida de archivos
La función incorporada open() toma como argumento la ruta de un archivo y retorna una
instancia del tipo file.
f = open("[Link]")
Si no se especifica una ruta, el fichero se busca en el directorio actual. Por defecto el modo
de apertura es únicamente para lectura. La función read() retorna el contenido del archivo
abierto.
print([Link]())
Una vez que se ha terminado de trabajar con el fichero debe cerrarse vía close().
[Link]()
Para abrir un archivo en modo escritura, debe especificarse en el segundo argumento.
f = open("[Link]", "w")
Para escribir en él se emplea el método write().
[Link]("Hola mundo")
Nótese que la función write() reemplaza todo el contenido anterior. Para añadir datos al
final del archivo sin borrar información previa, el fichero debe abrirse en la modalidad
append ("a").
f = open("[Link]", "a")
[Link]("Hola ")
[Link]("mundo")
Para leer, escribir y añadir contenido de un fichero en formato binario, deben utilizarse los
modos "rb", "wb" y "ab", respectivamente.
Apéndice I
El símbolo * (asterisco) también es utilizado para agrupar y desagrupar un conjunto de
objetos. Consideremos una función f con la siguiente definición:
def f(a, b, c):
print(a, b, c)
Supongamos entonces que queremos invocar a esta función pero los argumentos los
tenemos en una tupla. Por ejemplo:
args = (1, 2, 3)
f(args[0], args[1], args[2])
Esto es posible cuando conocemos a priori la cantidad de argumentos. Para el resto de los
casos, Python nos permite simplemente hacer:
f(*args)
La expresión *args “desagrupa” los elementos de la tupla y los pasa como argumentos,
dado que f(args) simplemente pasaría una tupla como el primer argumento y restarían
los últimos dos.
Ocurre de forma similar para los argumentos con valores por defecto. Considerando la
siguiente función f:
def f(a, b, c=3, d=4):
print(a, b, c, d)
Podemos definir con anterioridad un diccionario cuyas claves corresponden a los nombres
de los argumentos con nombre y luego “desagruparlo” empleando doble asterisco.
args = (1, 2)
kwargs = {"c": 5, "d": 6}
f(*args, **kwargs)
Cuando en la definición de una función se incluyen asteriscos, indica que Python debe
agrupar los argumentos pasados en una tupla y/o diccionario. Por ejemplo:
def f(*args, **kwargs):
print("Argumentos:", args)
print("Argumentos por nombre:", kwargs)
De modo que en la llamada f(1, 2, c=3, d=4), args es una tupla (1, 2) y kwargs
un diccionario {"c": 3, "d": 4}.
Nótese que en una definición, *args puede estar precedido por argumentos posicionales.
Por ejemplo, considerando:
def f(a, *args):
print(a, args)
Al llamar a f(1, 2, 3) sucede que a == 1 y args == (2, 3).
Apéndice II ─ Paradigma de objetos
Aspectos básicos de una clase
Python tiene soporte de primer nivel para el paradigma de orientación a objetos. Las clases
se definen vía la palabra reservada class seguida de su nombre y, entre paréntesis, el
nombre de las clases de las cuales debe heredar, en caso de haber.
class MiClase:
pass
Nótese que la convención de nombramiento difiere de las funciones y otros objetos; en las
clases cada término se escribe con su primera letra en mayúscula y no se emplean guiones
bajos. Recordá que pass no ejecuta ninguna operación sino que simplemente rellena un
lugar que es requerido sintácticamente.
Ahora bien, todas las clases necesitan un punto de entrada: ese código que se ejecutará
cada vez que se haga uso de ella y en donde el programador tendrá la posibilidad de
inicializar lo que sea necesario.
class MiClase:
def __init__(self):
pass
A las funciones definidas dentro de una clase se las conoce con el nombre de método.
Todas las funciones que empiezan y terminan con doble guión bajo son métodos especiales
que utilizará Python y que no están diseñados para ser llamados manualmente (con algunas
excepciones).
La función __init__() es un método que será invocado automáticamente por Python
cada vez que se cree un objeto a partir de una clase. Por ejemplo:
mi_objeto = MiClase()
Se dice que mi_objeto es una instancia de la clase MiClase. Para ilustrarlo mejor,
prueba el siguiente código:
class MiClase:
def __init__(self):
print("Has creado una instancia de MiClase.")
mi_objeto = MiClase()
Habrás observado que si bien el método __init__() requiere de un argumento, no
hemos especificado ninguno al crear una instancia. Esto se debe a que Python coloca el
primer argumento de todos los métodos de una clase automáticamente (por convención se
lo llama self). self es una referencia a la instancia actual de la clase.
Por ejemplo, consideremos el siguiente código, el cual crea dos instancias de MiClase.
mi_objeto = MiClase()
otro_objeto = MiClase()
En ambos casos, para crear la instancia Python automáticamente llama al método
__init__(). Sin embargo, en la primera línea self será una referencia a mi_objeto,
mientras que en la segunda, a otro_objeto.
Por lo general estarás utilizando el método __init__() para inicializar objetos dentro de
tu clase. Por ejemplo:
class MiClase:
def __init__(self):
self.a = 1
mi_objeto = MiClase()
print(mi_objeto.a)
Se dice que a es un atributo de mi_objeto o, en general, de MiClase. También podemos
cambiar el valor de un atributo desde fuera de la clase:
mi_objeto = MiClase()
mi_objeto.a += 1
print(mi_objeto.a) # Imprime 2
El método __init__() puede tener argumentos posicionales y por nombre como
cualquier otra función convencional, que serán especificados al momento de crear una
instancia de la clase.
class MiClase:
def __init__(self, a):
self.a = a
mi_objeto = MiClase(5)
print(mi_objeto.a) # Imprime 5
Una clase también puede incluir funciones, aunque recuerda, siempre el primer argumento
debe ser self.
class MiClase:
def __init__(self, a):
self.a = a
def imprimir_a(self):
print(self.a)
mi_objeto = MiClase(5)
mi_objeto.imprimir_a()
Otros ejemplos:
class MiClase:
def __init__(self, a):
self.a = a
def imprimir_a(self):
print(self.a)
def sumar_e_imprimir(self, n):
self.a += n
print(self.a)
mi_objeto = MiClase(5)
mi_objeto.sumar_e_imprimir(7) # Imprime 12
Herencia
Cuando una clase B hereda de una clase A, aquélla conserva todos los métodos y atributos
de ésta.
class ClaseA:
def __init__(self):
self.a = 1
class ClaseB(ClaseA):
pass
mi_objeto = ClaseB()
print(mi_objeto.a)
En este código, mi_objeto es una instancia de C laseB, que no ha definido ningún
atributo a. Sin embargo, lo hereda de la ClaseA. La ClaseB podría definir nuevos atributos
y funciones, e incluso reemplazar a los de su clase padre.
class ClaseB(ClaseA):
def __init__(self):
self.b = 2
En este caso, definimos el método de inicialización __init__() y creamos un nuevo
atributo b. No obstante, esto reemplaza la antigua definición de __init__() en ClaseA
de modo que, por ejemplo, self.a no se habrá definido. Siempre que reemplazamos un
método, para permitir que las funciones con el mismo nombre en las clases padres se
ejecuten debemos emplear la función super() del siguiente modo.
class ClaseB(ClaseA):
def __init__(self):
super().__init__()
self.b = 2
Como alternativa también es posible llamar a la función __init__() de la clase padre
directamente (aunque el método anterior es más recomendado).
class ClaseB(ClaseA):
def __init__(self):
ClaseA.__init__(self)
self.b = 2
Herencia múltiple
Una clase puede heredar de una cantidad arbitraria de clases, indicándolas en la definición
separadas por comas, tal como se haría con los argumentos. Hay que tener en cuenta que
la herencia se resuelve de derecha a izquierda.
class ClaseA:
def __init__(self):
self.a = 1
class ClaseB:
def __init__(self):
self.a = 2
class ClaseC(ClaseA, ClaseB):
pass
mi_objeto = ClaseC()
print(mi_objeto.a) # Imprime 1
La ClaseC hereda el atributo a de sus clases padres pero, como la resolución ocurre de
derecha a izquierda, el atributo ClaseA.a (1) reemplaza a ClaseB.a (2). Lo mismo ocurre
con los métodos:
class ClaseA:
def __init__(self):
self.a = 1
def mensaje(self):
print("Mensaje de la clase A.")
class ClaseB:
def __init__(self):
self.a = 2
def mensaje(self):
print("Mensaje de la clase B.")
class ClaseC(ClaseA, ClaseB):
pass
mi_objeto = ClaseC()
mi_objeto.mensaje() # Imprime "Mensaje de la clase A."
Convenciones de encapsulamiento
En Python todos los atributos y métodos de una clase son públicos. No obstante se estila
prefijar un guión bajo a aquellos objetos que se quiera indicar que se tratarán como privados
y no se espera que sean alterados por fuera de la clase. Por ejemplo:
class MiClase:
def __init__(self):
self._atributo_privado = 1
def _funcion_privada(self):
print(self._atributo_privado)