Python - CEIA - Apunte Teórico
Python - CEIA - Apunte Teórico
3. Variables 4
4. Funciones 5
8. Tipos numéricos 9
8.1. Números enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
8.2. Números de punto flotante . . . . . . . . . . . . . . . . . . . . . . . . . . 9
8.3. Representación de los números de punto flotante . . . . . . . . . . . . . . 10
8.4. Números complejos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
8.5. Aritmética de los tipos numéricos . . . . . . . . . . . . . . . . . . . . . . 11
9. Tipo booleano 12
9.1. Operadores lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
11.Tomando Decisiones 15
13.Tuplas 19
14.Revisitando la asignación 21
16.Definición de Funciones 28
1
1. ¿Por qué necesitamos algoritmos?
Problema. Dado un número N se quiere calcular N 33 . Una solución posible, por
supuesto, es hacer el producto N ∗ N ∗ ... ∗ N , que involucra 32 multiplicaciones.
Otra solución, más eficiente, es:
Calcular N ∗ N .
Cada una de estas dos soluciones representa un algoritmo, es decir, un método de cálculo
diferente. Para un mismo problema pueden haber algoritmos diferentes que lo resuelven,
cada uno con un costo distinto en términos de recursos computacionales involucrados.
2
2. Cómo darle instrucciones a la máquina con Python
El lenguaje Python provee un intérprete, es decir un programa que interpreta las ór-
denes que le damos a medida que las escribimos. La forma más típica de invocar al
intérprete es ejecutar el comando python en “modo consola” (usando cmd ), o bien ejecu-
tando un script (programa .py guardado como archivo) ejecutando el comando python
<programa>.
C: python
Python 3.10.2 (tags/v3.10.2:a58ebcc, Jan 17 2022, 14:12:15) [MSC
v.1929 64 bit (AMD64)] on win32
Type “help”, “copyright”, “credits” or “license” for more
information.
≫
>>> 2+3
5
>>>
3
>>> 5*7
35
>>> 2+3*7
23
>>> (2+3)*7
35
>>> 10/4
2.5
>>> 5**2
25
Además de efectuar operaciones matemáticas, Python nos permite trabajar con por-
ciones de texto, que llamaremos cadenas, y que se introducen entre comillas simples (’) o
dobles (”):
Python fue creado a finales de los años 80 por Guido van Rossum, quien sigue
siendo aún hoy el líder del desarrollo del lenguaje. La versión 2.0, lanzada en
2000 era mucho más madura, incluyendo un recolector de basura. La última
versión de esta línea es la 2.7 que fue lanzada en noviembre de 2010 y aún está
vigente.
En diciembre de 2008 se lanzó la rama 3.0, cuya versión actual es la 3.10, de
octubre de 2021, y es la que utilizamos en este curso. Python 3 fue diseñado
para corregir algunos defectos de diseño en el lenguaje, y muchos de los cambios
introducidos son incompatibles con las versiones anteriores. Por esta razón, las
ramas 2.x y 3.x aún coexisten con distintos grados de adopción.
3. Variables
Python nos permite asignarle un nombre a un valor, de forma tal de “recordarlo” para
usarlo posteriormente, mediante la sentencia <nombre>= <expresión>.
4
>>> x = 8
>>> x
8
>>> y = x * x
>>> 2 * y
128
>>> lenguaje = 'Python'
>>> 'Estoy programando en ' + lenguaje
'Estoy programando en Python'
En este ejemplo creamos tres variables, llamadas x, y y lenguaje, y las asociamos a los
valores 8, 64 y ’Python’, respectivamente. Luego podemos usar esas variables como parte
de cualquier expresión, y en el momento de evaluarla, Python reemplazará las variables
por su valor asociado.
4. Funciones
Para efectuar algunas operaciones particulares necesitamos introducir el concepto de
función. Una función es un fragmento de programa que permite efectuar una operación
determinada. abs, max, min y len y son ejemplos de funciones de Python. La función
abs permite calcular el valor absoluto de un número, max y min permiten obtener el
máximo y el mínimo entre un conjunto de números, y len permite obtener la longitud
de una cadena de texto.
Una función puede recibir 0 o más parámetros o argumentos (expresados entre entre
paréntesis, y separados por comas), efectúa una operación y devuelve un resultado. Por
ejemplo, la función abs recibe un parámetro (un número) y su resultado es el valor
absoluto del número.
5
>>> abs(10)
10
>>> abs(-10)
10
>>> max(5, 9, -3)
9
>>> min(5, 9, -3)
-3
>>> len("abcd")
4
Python viene equipado con muchas funciones. La mayoría de los programas que uti-
lizamos cotidianamente no son más que grandes fragmentos de código implementados
introduciendo nuevas funciones a la máquina, escritas por uno o muchos programadores.
Nosotros luego también seremos creadores de funciones, programas, o sistemas completos.
>>> suma = 0
>>> suma = suma + 1*1
>>> suma = suma + 2*2
>>> suma = suma + 3*3
>>> suma = suma + 4*4
>>> suma = suma + 5*5
>>> suma
55
Esto resuelve el problema, pero resulta poco útil. ¿Y si quisiéramos encontrar la suma
de los primeros 100 números cuadrados? En ese caso tendríamos que repetir la línea
suma = suma + ... * ... 100 veces. ¿Se puede hacer algo mejor que esto? Para resolver
este tipo de problema (repetir un cálculo para los valores contenidos en un intervalo dado)
de una manera más eficiente, introducimos el concepto de ciclo definido, que tiene la
siguiente forma:
for x in range(n):
<hacer algo con x>
6
Esta instrucción se lee del siguiente modo:
Para cada uno de los valores enteros que toma x en el intervalo generado, se debe
hacer lo indicado por <hacer algo con x>.
Notar que el intervalo es semiabierto. Se puede indicar también el valor inicial del
intervalo:
>>> suma = 0
>>> for x in range(6):
... suma = suma + x*x
...
>>> suma
55
Notar el . . . luego de terminar el encabezado (lo escribe solo el intérprete), pero luego
hay que dejar un sangrado al comenzar el cuerpo del ciclo. Notar también que al terminar
el cuerpo del ciclo aparece otro . . . que pone el intérprete indicando que terminó de leer
la linea. Se puede hacer todo en una sola línea, siempre que el cuerpo lo permita:
>>> suma = 0
>>> for x in range(6): suma = suma + x*x
>>> suma
55
7
6. Interacción con usuarias
Ya vimos que la función print nos permite mostrar información a la usuaria del pro-
grama. En algunos casos también necesitaremos que ella ingrese datos al programa. Por
ejemplo en el práctico pedimos un script (programa interpretado) que pida el nombre, y
luego la salude.
La función input interpreta cualquier valor que la usuaria ingresa mediante el teclado
como una cadena de caracteres. Es decir, input siempre devuelve una cadena, incluso
aunque la usuaria haya ingresado una secuencia de dígitos.
$ python saludar.py
Por favor ingrese su nombre: Elisa
Hola Elisa!
El programa .py que tendremos como archivo será algo como lo siguiente:
Diccionarios
Conjuntos
8
Iteradores
Clases
Instancias
etc. etc.
8. Tipos numéricos
Python define tres tipos de datos numéricos básicos: enteros, números de punto flotante
(simularía el conjunto de los números reales, pero ya veremos que no es así del todo) y
los números complejos.
El tipo de los números enteros es int. Este tipo de dato comprende el conjunto de
todos los números enteros, pero como dicho conjunto es infinito, en Python el conjunto
está limitado realmente por la capacidad de la memoria disponible. No hay un límite
de representación impuesto por el lenguaje. Un número de tipo int se crea a partir de
una constante literal que represente un número entero o bien como resultado de una
expresión o una llamada a una función. También podemos representar los números enteros
en formato binario, octal o hexadecimal prefijando con 0b, 0o o 0x la constante.
9
(hecha con bits, o sea, números binarios) establece una relación de compromiso entre la
precisión de la representación y la memoria asignada.
Esta circunstancia tiene efectos a veces insospechados. En mi consola, por ejemplo, veo
lo siguiente!!!:
Tenemos que meternos en un detalle molesto para entender lo que ocurre (y otras
cosas más). Para representar el mayor número posible de reales con las limitaciones de
memoria (tamaños de palabra de una cantidad “práctica” de bits), se adaptó la notación
científica de representación de números reales al sistema binario (que es el sistema que
se utiliza en programación para representar los datos e instrucciones).
En esta notación, llamada de mantisa y exponente, los números se representan así:
Esto permite “negociar” cuántos bits se utilizan para los dígitos significativos, y cuántos
para la cantidad de ceros a izquierda o a derecha del punto decimal (por eso se la denomina
punto flotante). Si por alguna razón necesitásemos reales con mucha mayor precisión,
existe el tipo de datos decimal.
El “problema” (ponele) es que la suma de la representación en punto flotante en binario
del número 1,1 y de la representación en punto flotante en binario del número 2,2, da como
resultado 3,3000000000000003. Pero hay más casos, como por ejemplo la representación
del número 1/3. En algún momento, el ordenador tiene que truncar el número periódico
resultante. Más en detalle, la mantisa se representa como un cociente entre dos números
binarios (enteros), lo cual da bastante amplitud (podemos representar a todos los números
10
racionales dependiendo de la memoria que comprometamos), y siempre hay un racional
“bastante cerca” de cualquier número real.
El último tipo de dato numérico básico que tiene Python es el de los números complejos,
los complex. Los números complejos tienen una parte real y otra imaginaria y cada una
de ellas se representa como un float. Para crear un número complejo, se sigue la siguiente
estructura: <parte_real> + <parte_imaginaria> j.
Se puede acceder a la parte real e imaginaria a través de los atributos real e imag.
Con todos los tipos numéricos se pueden aplicar las operaciones usuales de la aritmé-
tica: suma, resta, producto, división, exponentes, etc. algunos de los cuales ya vimos sin
presentarlos formalmente. En Python está permitido realizar una operación aritmética
con números de distinto tipo. En este caso, el tipo numérico «menor» se convierte auto-
máticamente al del tipo «mayor», de manera que el tipo del resultado siempre es de este
último tipo (con int <float <complex).
Estas “conversiones automáticas de tipos” son una fuente de problemas si no las con-
ceptualizamos con cuidado. Por tanto, es posible, por ejemplo, sumar un int y un float,
etc.
11
>>> entero = 2
>>> real = 2.1
>>> comple = 1 + 1j
>>> entero + real
4.1
>>> real + comple
(3.1+1j)
9. Tipo booleano
En Python el tipo que representa valores de verdad es el bool. Solo tiene dos valores:
True para representar verdadero y False para representar falso.
Expresión Significado
a == b a es igual a b
a != b a es distinto de b
a < b a es menor que b
a <= b a es menor o igual que b
a > b a es mayor que b
a >= b a es mayor o igual que b
12
>>> 5 > 3
True
>>> 3 > 5
False
>>> 6 == 6
True
>>> 6 != 6
False
>>> 6 > 6
False
>>> 6 >= 6
True
>>> 6 > 4
True
>>> 6 <= 4
False
>>> 4 < 6
True
>>> real = 2.2
>>> entero = 3
>>> 2 < real < 3
True
2 < real < entero < 4
True
De la misma manera que se puede operar entre números mediante las operaciones
de suma, resta, etc., también existen tres operadores lógicos para combinar expresiones
booleanas: and (y), or (o) y not (no). El significado de estos operadores es igual al del
castellano, pero vale la pena recordarlo:
Expresión Significado
Algunos ejemplos:
a > b and a > c es verdadero si a es simultáneamente mayor que b y que c.
a > b or a > c es verdadero si a es mayor que b o a es mayor que c.
13
>>> 5 > 2 and 5 > 3
True
>>> 5 > 2 and 5 > 6
False
>>> 5 > 8
False
>>> not 5 > 8
True
>>> 5 > 2
True
>>> not 5 > 2
False
Una particularidad (práctica pero a veces confusa) es que cualquier expresión puede
ser usada en un contexto donde se requiera comprobar si algo es verdadero o falso. Por
defecto, cualquier valor (o expresión) es considerado como verdadero excepto:
None
False
14
UNICODE es un estándar de codificación de caracteres diseñado para facilitar
el tratamiento, transmisión y visualización de textos en numerosos idiomas. El
término UNICODE proviene de sus tres características: universalidad, uniformi-
dad y unicidad. UNICODE define cada carácter o símbolo mediante un nombre
y un identificador numérico o código, y otras informaciones (sistema de escritu-
ra, categoría, direccionalidad, mayúsculas y otros atributos). UNICODE trata
los caracteres alfabéticos, ideográficos y símbolos de forma igualitaria, se pueden
mezclar en un mismo texto sin marcas o caracteres de control.
Para crear un string, rodeamos una secuencia de caracteres con comillas simples (após-
trofos) ’ ’ o dobles “ “ . Se puede usar indistintamente comillas simples o dobles, con
una particularidad. Si el string contiene una comilla simple, se pueden usar comillas do-
bles para encerrar el string, o bien, usar comillas simples pero anteponer el carácter \ a
la comilla simple del interior de la cadena. A diferencia de otros lenguajes, en Python no
existe el tipo «carácter».
Para la segunda línea introducimos una nueva estructura de control que llamaremos
condicional y tiene la siguiente forma:
if <condición>:
<hacer algo si se da la condición>
15
x = int(input("Ingrese un número: "))
if x > 0:
print("Número positivo")
$ python positivo
Ingrese un número: 4
Número positivo
$ python positivo
Ingrese un número: -25
$ python positivo
Ingrese un número: 0
La negación de x >0 se traduce en Python como not x > 0 (o bien x >= 0), por lo
que implementamos nuestra solución en Python como:
$ python positivo_o_no
Ingrese un número: 4
Número positivo
$ python positivo_o_no
Ingrese un número: -25
Número no positivo
$ python positivo_o_no
Ingrese un número: 0
Número no positivo
16
Sin embargo hay algo que nos preocupa: si ya averiguamos una vez si x >0, ¿Es real-
mente necesario preguntarlo de nuevo para negarlo ? Existe una construcción alternativa
para la estructura de decisión: Si se da la condición C, hacer S, de lo contrario,
hacer T. Esta estructura tiene la forma:
if <condición C>:
<hacer tarea(s) S>
else:
<hacer tarea(s) T>
17
Que un string sea inmutable significa (entre otras cosas) que no puedo asignar a ninguno
de sus caracteres
18
Un ejemplo: encontrar el mayor de tres enteros
13. Tuplas
La tupla es un objeto secuencial inmutable, es una colección ordenada de objetos (que
pueden contener a su vez tuplas).
19
>>> tupla = (a, b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> a=1
>>> b=2
>>> tupla(a,b)
>>> tupla
(1, 2)
En las tuplas son aplicables los mismos operadores de indexación que en los strings.
También son aplicables los operadores + y *.
>>> t+(1,2)
(23, 'abc', 4.56, (2, 3), 'def', 1, 2)
>>> t*3
(23, 'abc', 4.56, (2, 3), 'def', 23, 'abc', 4.56, (2, 3), 'def', 23,
'abc', 4.56, (2, 3), 'def')
20
>>> t # repetimos nuestro objeto para recordarlo
(23, 'abc', 4.56, (2, 3), 'def')
>>> 23 in t
True
>>> 'abc' in t
True
>>> 'bc' in t[1]
True
>>> 2 in t[3]
True
>>> 'de' in t
False
>>> 2 in t
False
>>> str='abc'
>>> for x in str: print(x)
...
a
b
c
21
Python permite asignaciones entre tuplas (si del lado izquierdo del operador
de asignación hay solamente una tupla de referencias. Esto permite hacer asig-
naciones múltiples en una misma línea:
x, y = 3, 4
y = x
En este caso, si x es inmutable, podemos pensar que se crea otra instancia idéntica
del mismo objeto referenciado por x , a la cual y ahora referenciará, o bien podemos
pensar que x e y referencian al mismo objeto, pero que si luego x o y cambiasen, lo
harían creando un nuevo objeto como se explicó arriba.
Con los objetos mutables (por ejemplo una lista) este mecanismo será diferente, por lo
que es indispensable conocer si un tipo es mutable o no para administrar correctamente
las asignaciones.
while <condición> :
<ejecutar una tarea>
22
while es una palabra reservada, condición es una expresión booleana, igual que en
las instrucciones condicionales (if). El cuerpo es, como siempre, una o más instrucciones
del lenguaje.
El funcionamiento de la instrucción while es el siguiente:
1. Evaluar la condición.
4. Volver a 1.
23
while True:
x = int(input("Ingresá un número: "))
if x > 0:
print("Número positivo")
elif x==0:
print("Igual a cero")
else:
print("Número no negativo")
Claramente esto tiene algunas cosas para mejorar, por ejemplo poder salir del ciclo sin
romper el intérprete:
HayMasDatos = "Si"
while HayMasDatos == "Si":
x = int(input("Ingresá un número: "))
if x > 0:
print("Número positivo")
elif x==0:
print("Igual a cero")
else:
print("Número no negativo")
HayMasDatos = input("¿Querés seguir? <Si-No>: ")
Mastermind
Con lo que ya sabemos del lenguaje, y un poco de “pensamiento algorítmico” estamos en
condiciones de proponer cosas más interesantes. Pensemos en el juego “Mastermind”:
hay que adivinar un número de cuatro dígitos distintos.
Queremos un programa que haga lo siguiente:
Vamos a desensamblar esta especificación en partecitas, para que luego nos sirva como
ejemplo de construcción “más racional” de programas complejos.
¿Cómo armar un string con cuatro dígitos diferentes y “aleatorios”?
24
Comenzar con un string vacío
Repetir 4 veces
25
2. Caso contrario si propuesta[i] in codigo aumentamos coincidencias
Queremos ahora que el juego “cicle” hasta que la jugadora gane, cuente cuántas veces
la jugadora intentó, y que cuando gane le indique la cantidad de intentos:
Incrementar intentos
Contar aciertos y coincidencias
Si aciertos == 4
felicitar a la jugadora
indicar cuántos intentos
asignar ganaste = True
Caso contrario
avisar cuántos aciertos y coincidencias
pedir nueva propuesta
26
print("Bienvenidx al Mastermind!") # etc etc
intentos = 0
ganaste = False
# acá viene elegir el código
propuesta = input("Que código proponés?: ")
while not ganaste:
intentos = intentos + 1
# acá viene determinar aciertos y concidencias
if aciertos == 4:
ganaste = True
print("Felicitaciones, has ganado ...) # etc. etc.
else:
print("Tu propuesta tiene tantos aciertos...) # etc. etc.
propuesta = input("Elegí otro código: ")
import random
print("Bienvenidx al Mastermind!")
print("Tenés que adivinar un número de cuatro dígitos distintos")
intentos = 0
ganaste = False
27
16. Definición de Funciones
El programa queda un poco “largo” y requiere “subir y bajar” la lectura para entenderlo
y no perderse. Esa es siempre una práctica dudosa y riesgosa. Para poder factorizar
nuestro programa en unidades conceptualmente adecuadas, podemos utilizar la definición
de funciones.
def saludar(usuarie):
print("Hola " + usuarie + "!")
def es una palabra reservada que avisa al intérprete que lo que sigue (de acuerdo
con la indentación y espacios) es la definición de una función. Las funciones pueden
recibir ninguno, uno, o varios parámetros, y devolver ninguno, uno, o varios resultados.
Es importante observar que los parámetros de la función son evaluados cuando ésta se
invoca.
#Se puede "comentar" la definición de una función para que luego la ayuda
#online nos recupere el comentario.
def saludar(usuarie):
"""Saluda a la persona indicada por parámetro."""
print("Hola " + usuarie + "!")
>>> saludar('profe')
Hola profe!
>>> help(saludar)
Help on function saludar in module __main__:
saludar(usuarie)
Saluda a la persona indicada por parámetro.
28
def elegir_codigo(): # no recibe parámetros!!!
""" Devuelve un código de 4 dígitos elegido al azar."""
digitos = ('0','1','2','3','4','5','6','7','8','9')
codigo = ''
for i in range(4):
candidato = random.choice(digitos)
while candidato in codigo:
candidato = random.choice(digitos)
codigo = codigo + candidato
return codigo
aciertos = 0
coincidencias = 0
for i in range(4):
if propuesta[i] == codigo[i]:
aciertos = aciertos + 1
elif propuesta[i] in codigo:
coincidencias = coincidencias + 1
return aciertos, coincidencias # devuelve una tupla!!
import random
print("Bienvenidx al Mastermind!")
print("Tenés que adivinar un número de cuatro dígitos distintos")
codigo = elegir_codigo()
propuesta = input("Que código proponés?: ")
intentos = 1
while propuesta != codigo:
intentos = intentos + 1
aciertos, coincidencias = analizar(propuesta, codigo)
print("Tu propuesta "+str(propuesta)+" tiene "+str(aciertos)+"
aciertos y "+str(coincidencias)+" coincidencias")
propuesta = input("Elegí otro código: ")
print("Felicitaciones, has ganado en "+str(intentos)+" intentos!")
29
17. Alcance de las variables
Podemos declarar variables dentro del cuerpo de una función. Estas variables serán
solamente visibles y accesibles dentro de la misma. Las variables y los parámetros que
se declaran dentro de una función no existen fuera de ella, y por eso se las denomina
variables locales. Fuera de la función se puede acceder únicamente al valor que devuelve
la función mediante return.
Las variables declaradas dentro de una función pueden tener nombres iguales a otras
variables (existentes fuera de la función, o existentes dentro de otras funciones) pero
referencian a objetos diferentes y por lo tanto "no se mezclan".
def funcion_1():
x = 'este es el x de la funcion 1'
funcion_2()
print(x)
def funcion_2():
x = 'este es el x de la funcion 2'
print(x)
Sin embargo, si dentro de una función referenciamos a una variable que no está decla-
rada dentro de la misma, el intérprete buscará un identificador fuera de la variable (hasta
ahora, en el programa principal). Ver cómo cambia el comportamiento siguiente:
def funcion_1():
#x = 'este es el x de la funcion 1'
funcion_2()
print(x)
def funcion_2():
x = 'este es el x de la funcion 2'
print(x)
Es posible también declarar una función dentro de otra (lo cual hace que la primera
30
sea visible sólo dentro de la segunda). Las reglas de alcance de las variables se aplican de
idéntico modo.
Un ejemplo ‘complejo’: Bhaskara
Queremos recibir los tres coeficientes de una ecuación de segundo grado y encontrar
sus raíces si es que éstas existen. La idea es definir una función con tres parámetros
(numéricos reales o enteros) que devuelva dos string con las dos raíces, un solo string no
vacío si es un sistema lineal, y dos strings vacíos si el sistema no tiene solución:
e, f = bsk(a,b,c)
if e:
print('raiz 1 = ' + e)
if f:
print('raiz 2 = ' + f)
31
>>> a = 125
>>> b = "#"
>>> c = "Ana"
>>> d = a, b, c
>>> d
(125, '#', 'Ana')
>>> x, y, z = d
>>> x
125
>>> y
'#'
>>> z
'Ana'
>>> p, p, p = d
>>> p
'Ana'
>>> m, n = d
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack
>>> m, n, o, p = d
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: need more than 3 values to unpack
32
Universidad Nacional del Sur
Centro de Estudiantes de Ingeniería y Agrimensura
22.Diccionarios 16
22.1. Algunos métodos y funciones con diccionarios . . . . . . . . . . . . . . . 21
23.Conjuntos 22
23.1. Funciones aplicables a conjuntos . . . . . . . . . . . . . . . . . . . . . . . 24
24.Ordenamiento de secuencias 25
25.Archivos 27
25.1. El cursor (los archivos no son estructuras!) . . . . . . . . . . . . . . . . . 29
25.2. Archivos y estructuras secuenciales . . . . . . . . . . . . . . . . . . . . . 30
25.3. Cerrar un archivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
25.4. Escribiendo en archivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
26.Recursión 35
26.1. ¿Dónde (y cómo) se mantienen las computaciones intermedias? . . . . . . 37
1
19. Listas
La lista será nuestro primer tipo de dato mutable. Es una secuencia ordenada de ele-
mentos (como es la tupla) pero en la lista se pueden modificar los elementos, agregar,
quitar, etc. A diferencia de la tupla, se demarca con '[' y ']'.
Para acceder a los elementos de una lista utilizamos la misma notación que para strings:
>>> lista_enteros[2]
900
>>> lista_heterogenea[0]
'Hola'
>>> lista_con_listas[1]
[1, 2, 3]
>>> lista_con_listas[2]
[3.4, 'casa']
>>> lista_con_listas[2][1]
'casa'
Cant = 0
for elemento in lista_enteros :
Cant = Cant + 1
print('La cantidad de elementos en la lista es: ', cant)
Sumatoria = 0
for elemento in lista_enteros :
Sumatoria = Sumatoria + elemento
print('La suma de los elementos en la lista es: ', Sumatoria)
Mayor = lista_enteros[0]
for elemento in lista_enteros:
if elemento > Mayor :
Mayor = elemento
print("El mayor elemento en la lista es: ", Mayor )
2
Buscado = int(input("Ingrese el elemento que desea buscar"))
>>> L = [1, 2, 3]
>>> L
[1, 2, 3]
>>> L2 = L + L
>>> L2
[1, 2, 3, 1, 2, 3]
>>> L3 = L*3
>>> L3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> LL = [L, L]
>>> LL
[[1, 2, 3], [1, 2, 3]]
>>> L3[1:4]
[2, 3, 1]
>>> LL[1][1]
2
Y también transformarlas:
3
>>> LT = [elemento * 3 for elemento in L]
>>> LT
[4.5, 12, 15, 18]
>>> LE = [elemento for elemento in L if elemento > 4]
>>> LE
[5, 6]
4
Triángulo de Pascal
1
5
Quisiéramos (primero) armar un programa que dado N escriba las primeras N filas del
triángulo. Para ello definimos una función que recibe una fila (representada como una
lista de enteros) y devuelve la fila inferior de acuerdo a la definición.
Esta función primero coloca ceros al comienzo y fin de la fila recibida, y luego genera
una fila nueva sumando los elementos correlativos de a dos:
def nueva_fila(fila):
fila.insert(0,0)
fila.append(0)
nf = []
for i in range(len(fila)-1) :
nf.append(fila[i] + fila[i+1])
return(nf)
Luego, definimos una función que a partir de la fila 0 (que sería la lista [1]) genere la
fila uno, luego la dos, etc., hasta llegar al N provisto por la usuaria:
def pascal(N):
fila = [1]
for i in range(N) :
print(fila)
fila = nueva_fila(fila)
Observar que con N=0 el ciclo for no se ejecuta, con N=1 se ejecuta una sola vez e
imprime la primera fila (que es la fila 0), etc. Finalmente, el programa solo pide el valor
de N y llama a esta última función.
Para eliminar un elemento de una lista se utiliza el método remove. Aquí no nos interesa
el lugar en el que está el elemento, sino su valor. Si hay más de una aparición se elimina
la primera. Si el elemento no existe se genera un error.
6
>>> L = [1, 2, 3, 4, 5, 1]
>>> L.remove(1)
>>> L
[2, 3, 4, 5, 1]
>>> L.remove(1)
>>> L
[2, 3, 4, 5]
>>> L.remove(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
Listas, tuplas y strings son tipos de valor secuencia. Las funciones list y tuple ge-
neran listas o tuplas a partir de secuencias.
>>> list('hola')
['h', 'o', 'l', 'a']
>>> list((1, 2))
[1, 2]
>>> list(['a', 'b'])
['a', 'b']
>>> tuple('hola')
('h', 'o', 'l', 'a')
>>> tuple([1, 2])
(1, 2)
>>> tuple(('a', 'b'))
('a', 'b')
El método split aplicado a una cadena de caracteres permite armar una lista respecto
de una subcadena separadora.
A la inversa, el método join aplicado a una cadena separadora, recibe una lista de
elementos y los une en un string separándolos por dicha cadena.
7
>>> "-".join(list("hola"))
'h-o-l-a'
>>> bs = [5, 2, 4, 2]
>>> cs = sorted(bs)
>>> bs
[5, 2, 4, 2]
>>> cs
[2, 2, 4, 5]
>>> ds = [5, 3, 4, 5]
>>> ds.sort()
>>> ds
[3, 4, 5, 5]
Supongamos que queremos hacer un pequeño sistema de información, por ejemplo para
el manejo de legajos. Tomamos los datos del SIU-Guaraní utilizando un web-scrapper
(veremos este tema más adelante). La siguiente página:
8
Se transforma (después de seleccionar las tres primeras columnas y filtrar caracteres
innecesarios) en la siguiente lista:
Queremos hacer un buscador por clave, que nos permita ver si une alumne está en
la lista, buscando el nombre, el DNI o el legajo a partir de alguno de los otros datos.
Colocamos dicho “data frame” (en este caso una lista de listas) en una variable Legajos.
Supongamos que queremos buscar el nombre, a partir del DNI o el legajo. Armamos una
función que reciba DNI o legajo, busque en Legajos, y devuelva el nombre del registro
(si lo encuentra):
def Buscar_Nombre(Buscado) :
Dato = [e for e in Legajos if Buscado in e]
if Dato : return(Dato[0][1])
else : return("No se encontró la persona")
Es fácil ver los demás casos. Luego, necesitamos iterar el proceso de preguntar a la usua-
ria qué dato desea buscar. Lo podemos hacer de muchas maneras, por ejemplo pidiendo
una letra:
9
elif Flag == "D" :
Buscado = input("Ingrese Nombre o Legajo: ")
print(Buscar_DNI(Buscado))
Flag = input("Ingrese qué desea buscar ('N' Nombre, 'L'
Legajo, 'D' DNI, '' para salir: ")
print("Gracias por usar nuestros servicios (ponele)")
Ingrese qué desea buscar ('N' Nombre, 'L' Legajo, 'D' DNI,
'' para salir: N
Ingrese DNI o Legajo: 42316124
Schmit, Lucas Gabriel
Ingrese qué desea buscar ('N' Nombre, 'L' Legajo, 'D' DNI,
'' para salir: L
Ingrese DNI o Nombre: Petruf, Joaquín
136172
Ingrese qué desea buscar ('N' Nombre, 'L' Legajo, 'D' DNI,
'' para salir: D
Ingrese Nombre o Legajo: 107378
38093296
Ingrese qué desea buscar ('N' Nombre, 'L' Legajo, 'D' DNI,
'' para salir:
Gracias por usar nuestros servicios (ponele)
>>> L = [1,2,3,4]
>>> L
[1, 2, 3, 4]
>>> L1=L
>>> L1
[1, 2, 3, 4]
>>> L.pop()
4
>>> L1
[1, 2, 3]
10
>>> L
[1, 2, 3]
A este mecanismo por el cual un mismo objeto puede tener dos (o más referencias)
se lo denomina “aliasing”. Esta posibilidad puede parecer extraña pero tiene su razón de
ser. En muchos problemas de programación que veremos, es necesaria la posibilidad de
recibir por parámetro parte de una estructura para luego modificarla.
Pensemos por ejemplo en un cursor, en un editor de texto. El archivo está siendo
referenciado por una variable (el nombre del archivo), y el cursor al comienzo referencia
a la misma estructura, pero luego el cursor puede “navegar” dentro de la misma. Si luego
alteramos lo referenciado por el cursor, queremos que el archivo registre dicho cambio.
Podríamos pensar ahora para tener una lista ordenada por nombre, “rotar” cada registro
para que quede el nombre al frente, y luego ordenar la lista con los registros rotados:
L2 = Legajos
for e in L2 : e.append(e.pop(0))
L2.sort()
for e in L2 : print(e)
11
Esto va a dar resultado, pero en realidad L2 es un alias de Legajos! Para evitar alterar
a esta última, podríamos utilizar el método copy en la primera línea:
L2 = Legajos.copy()
Podríamos pensar entonces en copiar una lista a la cual le copiamos los elementos (y
así recursivamente). Por suerte esto ya está implementado en el método deepcopy() de
la clase copy.
import copy
...
L2 = copy.deepcopy(Legajos)
...
L3 = copy.deepcopy(Legajos)
for e in L3 : e.insert(0,(e.pop()))
L3.sort()
for e in L3 : print(e)
Es entonces importante ver que para cualquier problema “razonable” que tengamos,
sepamos buscar el import que nos hace falta.
12
21. Repaso de lo visto hasta ahora
Las tuplas se definen como una sucesión de valores encerrados entre paréntesis y sepa-
rados por comas. Una vez definidas, no se pueden modificar los valores asignados. Casos
particulares:
tupla_vacia = ()
tupla_unitaria = (3459,)
13
[valor1, valor2, valor3]
Las listas se definen como una sucesión de valores encerrados entre corchetes y sepa-
rados por comas. Se les puede agregar, quitar o cambiar los valores que contienen. Caso
particular:
lista_vacia = []
x, y, z = secuencia
len(secuencia)
elemento in secuencia
secuencia[i]
secuencia[i:j:k]
14
lista.append(valor)
lista.insert(posicion, valor)
lista.remove(valor)
lista.pop()
Quita el elemento del final de la lista, y lo devuelve. Si la lista está vacía, se produce
un error.
lista.pop(posicion)
Quita el elemento que está en la posición indicada, y lo devuelve. Si la lista tiene menos
de posicion + 1 elementos, se produce un error.
lista.index(valor)
sorted(secuencia)
lista.sort()
cadena.split(separador)
Devuelve una lista con los elementos de cadena, utilizando separador como separador
de elementos. Si se omite el separador, toma todos los espacios en blanco como separa-
dores.
15
separador.join(lista)
Genera una cadena a partir de los elementos de lista, utilizando separador como
unión entre cada elemento y el siguiente.
22. Diccionarios
El diccionario es una estructura de datos mutable muy práctica, que consiste en una
colección (es decir, una “bolsa”) de pares clave-valor. Esta estructura no solo es muy útil a
la hora de simplificar el desarrollo de algoritmos, sino que también es el formato genérico
que se utiliza casi hegemónicamente para intercambiar información entre aplicaciones
(por ejemplo en el formato JASON).
Para definirlo junto con los miembros que va a contener, se encierra el listado de valores
entre llaves, las parejas de clave y valor se separan con comas, y la clave y el valor se
separan con ‘:’
Las claves tienen que ser valores inmutables (números, strings, tuplas), pero los valores
pueden ser de cualquier tipo (inclusive diccionarios). Se puede crear un diccionario vacío
con el conjunto vacío '{}' o bien con la función dict(). Una vez creado se le pueden
asignar valores directamente a cada índice.
Para acceder al valor asociado a una determinada clave, se lo hace de la misma forma
que con las listas, pero utilizando la clave elegida en lugar del índice.
>>> materias["lunes"]
[6103, 7540]
Es posible asignar una cantidad arbitraria de pares, con la única condición que las
claves sean inmutables y que no se repitan.
16
>>> dicc={'número': 2, 'string': 'abc', 'tupla': (1,2,3),
'lista': ['a', 'b'], 'diccionario': {'x': 2, 'y': 3}}
>>> dicc
{'número': 2, 'string': 'abc', 'tupla': (1, 2, 3), 'lista':
['a', 'b'], 'diccionario': {'x': 2, 'y': 3}}
>>> dicc['diccionario']
{'x': 2, 'y': 3}
dicc['string'] = 'def'
>>> dicc
{'número': 2, 'string': 'def', 'tupla': (1, 2, 3), 'lista':
['a', 'b'], 'diccionario': {'x': 2, 'y': 3}}
El acceso por clave falla si se provee una clave que no está en el diccionario:
>>> materias["domingo"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'domingo'
El método get() si se utiliza sobre una clave inexistente produce el valor nulo.
>>> materias.get("domingo")
>>> # no hay tal clave: no hace nada
Existen diversas formas de recorrer un diccionario. Es posible recorrer sus claves y usar
esas claves para acceder a los valores.
17
Es posible, también, obtener los valores como tuplas donde el primer elemento es la
clave y el segundo el valor.
Además de modificar los valores de una clave, podemos remover el par valor-clave con
el método pop(). Este método remueve el ítem cuya clave se pasa por parámetro, y
devuelve el valor asociado. Si quisiésemos que se elimine y retorne el último par clave-
valor, utilizamos el método popitem() (sin argumentos). Todos los items pueden ser
removidos de una utilizando el método clear(). Si queremos eliminar el diccionario,
utilizamos la función genérica del.
18
>>>cuadrados = {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
>>>print(cuadrados.pop(4))
16
>>>cuadrados
{1: 1, 2: 4, 3: 9, 5: 25}
>>> print(cuadrados.popitem())
(5, 25)
>>> cuadrados
{1: 1, 2: 4, 3: 9}
>>> cuadrados.clear()
>>> cuadrados
{}
>>> del(cuadrados)
>>> cuadrados
Traceback (most recent call last): ...etc...
Puede darse el caso que tengamos que armar un diccionario a partir de dos listas y
necesitemos primero combinarlas en una tupla para poder armar el diccionario.
19
>>> diccio
{'Lucas': 8, 'Florencia': 9, 'Matías': 7}
while texto :
dicc = dict()
palabras = texto.split(' ')
Podemos modularizar algunas de esas tareas en funciones, lo cual nos permitiría luego
un mantenimiento más rápido y una flexibilidad mayor:
def contar_palabras(texto) :
diccio = dict()
palabras = texto.split(' ')
for palabra in palabras :
if palabra in diccio : diccio[palabra] += 1
else: diccio[palabra] = 1
return(diccio)
20
def agregar_palabras(texto, diccionario) :
diccio = contar_palabras(texto)
for palabra in diccio :
if palabra in diccionario : diccionario[palabra] += diccio[palabra]
else: diccionario[palabra] = diccio[palabra]
return(diccionario)
{clave1:valor1, clave2:valor2}
Se crea un nuevo diccionario con los valores asociados a las claves. Si no se ingresa
ninguna pareja de clave y valor, se crea un diccionario vacío.
diccionario[clave]
clave in diccionario
diccionario.get(clave, valor_predeterminado)
Devuelve el valor asociado a la clave. A diferencia del acceso directo utilizando [clave],
en el caso en que el valor no se encuentre devuelve el valor_predeterminado.
diccionario.keys()
Devuelve una secuencia desordenada, con todas las claves que se hayan ingresado al
diccionario.
diccionario.values()
Devuelve una secuencia desordenada, con todos los valores que se hayan ingresado al
diccionario.
21
diccionario.items()
Devuelve una secuencia desordenada con tuplas de dos elementos, en las que el primer
elemento es la clave y el segundo el valor.
diccionario.pop(clave)
diccionario.popitem()
Quita del diccionario el último par clave-valor (puede variar) y devuelve el valor del
par.
diccionario.clear()
23. Conjuntos
Un conjunto es una estructura de datos mutable que consiste en una colección de valores
inmutables que no pueden estar repetidos (esto es análogo al conjunto de claves en un
diccionario).
22
>>> set((1, 2, 3, 4, 3))
{1, 2, 3, 4}
>>> set('pepin')
{'i', 'p', 'e', 'n'}
>>> set({1, 2, 3})
{1, 2, 3}
>>> set({'a':1, 'b':2})
{'a', 'b'}
>>> conj = set()
>>> conj
set() # set() representa el conjunto vacío
>>> conj.discard(6)
>>> conj
{1, 2, 3, 4, 5, 7, 8, 9, 'a', (1, 2), 'c', 'b'}
>>> conj.remove(7)
>>> conj
{1, 2, 3, 4, 5, 8, 9, 'a', (1, 2), 'c', 'b'}
>>> conj.remove(7)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 7
>>> conj.discard(7)
>>> conj
{1, 2, 3, 4, 5, 8, 9, 'a', (1, 2), 'c', 'b'}
23
Es posible realizar las operaciones matemáticas usuales entre conjuntos (unión, inter-
sección, diferencia, y “diferencia simétrica”. El operador in también aplica.
>>> A = {1, 2, 3, 4}
>>> B = {3, 4, 5, 6}
>>> A|B
{1, 2, 3, 4, 5, 6}
>>> A.union(B)
{1, 2, 3, 4, 5, 6}
>>> B.union(A)
{1, 2, 3, 4, 5, 6}
>>> A&B
{3, 4}
>>> A.intersection(B)
{3, 4}
>>> B.intersection(A)
{3, 4}
>>> A-B
{1, 2}
>>> B-A
{5, 6}
>>> A.difference(B)
{1, 2}
>>> B.difference(A)
{5, 6}
>>> A^B
{1, 2, 5, 6}
>>> A.symmetric_difference(B)
{1, 2, 5, 6}
>>> B.symmetric_difference(A)
{1, 2, 5, 6}
all()
Retorna True si ningún elemento del conjunto evalúa a False o si el conjunto es vacío.
24
any()
Retorna False si todos los elementos del conjunto evalúan a False o si el conjunto es
vacío.
enumerate()
Retorna objeto no asignable (como el zip) que consiste en una tupla de pares enume-
rados (índice, valor).
Retorna la longitud, mayor elemento, menor elemento, o suma entre los elementos del
conjunto.
Como los conjuntos pueden ser claves adecuadas o
útiles en un diccionario, pero no podrían serlo por ser
mutables, existe el tipo frozenset, que es una versión
inmutable de conjuntos (algo así como la relación que
hay entre tuplas y listas).
>>> A = frozenset([1,2,3,4])
>>> A
frozenset({1, 2, 3, 4})
>>> A.add(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'
25
>>> T = (5, 6, 4, 3, 9)
>>> sorted(T)
[3, 4, 5, 6, 9]
>>> C = {'f', 'g', 'a', 'z'}
>>> sorted(C)
['a', 'f', 'g', 'z']
>>> S = tuple(zip(T,C))
>>> S
((5, 'a'), (6, 'g'), (4, 'z'), (3, 'f'))
>>> sorted(S)
[(3, 'f'), (4, 'z'), (5, 'a'), (6, 'g')]
se realice de una manera diferente, podemos utilizar el parámetro opcional key, en el cual
se indica la manera de comparar los elementos de la secuencia. Lo ideal es indicar a través
de este parámetro cuál será la función para comparar.
Recordamos nuestro ejemplo de estructura de datos:
26
>>> for x in sorted(Lista, key=nombre) : print(x)
...
['117806', 'Aldebert, Gabriel Ivan', '41460080']
['136052', 'Amoroso, Sebastian', '44298744']
['125675', 'Castro, Facundo Nicolás', '42114637']
['126148', 'Cricco, Nicolás', '42652489']
['130064', 'Gogg Aller, Francisco Agustin', '43040917']
etc...
25. Archivos
Los archivos requieren un mecanismo un poco más complejo que otras estructuras:
1. Primero necesitamos asociar un identificador al archivo (este identificador es como
cualquier otra variable, pero se denomina handler por razones que se van a ir
aclarando).
2. Esta asociación se hace con la función predefinida open, a la cual hay que indicarle
por parámetro el nombre del archivo que queremos gestionar, y el modo de acceso).
3. Si luego de esta asociación, el archivo (existente en el path desde el cual se ejecuta
el script o el intérprete) fue accedido por el handler en modo lectura, entonces el
handler nos permite acceder a una copia (buffer) del archivo como si fuese una
secuencia de strings.
print("Bienvenidx al Mastermind!")
En este ejemplo, hemos asumido que el archivo 'mastermind.py' estaba “en el path”,
es decir en la misma carpeta o folder desde la cual estamos corriendo el intérprete. Lo
abrimos con 'r' para indicar solo lectura (read).
27
Podemos abrir un archivo cualquiera indicando el path absoluto:
c:\carpeta\...\archivo.py.
Nos irrita un poco que los acentos salgan mal. Esto se debe al encoding utilizado por
el notepad para los caracteres que no están entre los primeros 127 de ASCII.
Los conjuntos de valores que puede tomar cada byte de un carácter mul-
tibyte, son disjuntos, por lo que no es posible confundirlos entre sí.
print("Bienvenidx al Mastermind!")
28
25.1. El cursor (los archivos no son estructuras!)
Si bien el ejemplo anterior puede hacernos creer que un archivo es una secuencia de
strings, tenemos que observar varios aspectos. Hagamos varias pruebas:
Esto quiere decir que el archivo es un “objeto no legible” (como son los zip).
print("Bienvenidx al Mastermind!")
No es que el archivo “se borró” (o el buffer en memoria local), sino que el for en este
caso recorre un cursor, el cual al final de la recorrida queda al final del archivo.
Podemos “resetear” el cursor (a la línea deseada) utilizando el método seek(). El pará-
metro indicado es la cantidad de caracteres desde el comienzo del archivo a la que quere-
mos que el cursor pase a señalar. Si queremos posicionar el cursor en un lugar dado del ar-
chivo, indicamos archivo.seek(desplazamiento, lugar), dónde desplazamiento in-
dica cuántos caracteres desde lugar nos queremos desplazar, y lugar puede ser 0 (comien-
zo), 1 (posición actual), o 2 (final). Para ejemplificar pensemos en el siguiente archivo:
29
0 (o sea '0/n') etc.
1
2
...
9
>>>archi.seek(3,0)
3
>>> for L in archi : print(L)
...
1
2
...
9
El uso de readlines() es más flexible, dado que un valor no nulo indica copiar
solo una línea y deja el cursor al final de la última línea que copió.
30
>>> archi.seek(0,0)
0
>>> data = archi.readlines(2)
>>> data
['import random\n']
>>> data = archi.readlines(2)
>>> data
['print("Bienvenidx al Mastermind!")\n']
>>> data = archi.readlines(2) + archi.readlines(2)
>>> data
['print("Tenés que adivinar un número de cuatro dígitos distintos")\n',
'intentos = 0\n']
>>>
31
25.3. Cerrar un archivo
>>> archi.close()
>>> archi
<_io.TextIOWrapper name='mastermind.py' mode='r' encoding='utf-8'>
>>> archi.seek(0,0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.
32
25.4. Escribiendo en archivos
Para escribir en un archivo tenemos que abrirlo en el modo de sólo escritura ('w'). En
este caso el archivo existente es vaciado si existe, y se lo crea si no existe. Hay un modo
sólo escritura posicionándose al final del archivo ('a') o append. En este caso se crea
el archivo, si no existe, pero en caso de que exista se posiciona al final, manteniendo el
contenido original.
De la misma forma que para la lectura, existen dos formas distintas de escribir a
un archivo, mediante cadenas archivo.write(cadena) o mediante listas de cadenas
archivo.writelines(lista_de_cadenas).
Así como la función readline devuelve las líneas con los caracteres de fin de línea (\n),
será necesario agregar los caracteres de fin de línea a las cadenas que se vayan a escribir
en el archivo.
33
Abrir un archivo en modo 'a' puede parecer raro, pero es bastante útil. Uno
de sus usos es para logs, que nos permite ver en su secuencia cronológica los
distintos eventos que se fueron sucediendo (por ejemplo el muro de una red social,
los comentarios a lectores en un medio digital, etc.). En los log automáticos
que generan los sistemas (operativo, navegador, correo, etc.) ver la secuencia
cronológica permite encontrar los pasos (no siempre evidentes) que ha ejecutado
nuestro programa y encontrar un error en alguno de ellos.
34
26. Recursión
0! = 1
x! = x (x - 1)! si x >0
Esta definición lleva a un “correlato” algorítmico inmediato:
def factorial(n) :
if n == 0 : return 1
else : return n * factorial(n - 1)
35
1. La función debe tener por lo menos un caso base (no recursivo) y nunca debe
ejecutarse una llamada recursiva si corresponde ejecutarse un caso base.
2. La llamada recursiva debe hacerse sobre argumentos que acerquen a la función al
caso base (i.e., los argumentos están “más cerca” del caso base que los parámetros).
3. La complejidad con la cual los parámetros recibidos se transforman en los argumen-
tos en la llamada recursiva debe ser menor que el cálculo recursivo en sí mismo.
En el ejemplo que vimos, la “utilidad” de utilizar recursión es dudosa, la función iterativa
es también sencilla y posiblemente más eficiente (aunque tiene algunos defectos también):
def factorial(n) :
fact = 1
for num in range(1, n+1) : fact *= num
return fact
En algunos casos hay versiones recursivas e iterativas con paridad de virtudes y defectos,
en otros hay alguna versión que es claramente mejor que la otra, etc.
def potencia(b,n) :
if n <= 0 : return 1
elif n % 2 == 0 :
p = potencia(b, n // 2)
return p * p
else :
p = potencia(b, (n - 1) // 2)
return p * p * b
def fib(n):
if n == 0 or n == 1 : return n
else : return fib(n - 1) + fib(n - 2)
Un ejemplo revisitado
Supongamos que queremos retomar el ejemplo de la sumatoria de la serie visto en clase,
pero queremos simplificar las fracciones.
def suma_frac(frac1,frac2):
dic_suma_frac = {}
dic_suma_frac["den"] = frac1["den"] * frac2["den"]
dic_suma_frac["num"]=frac2["den"]*frac1["num"]+frac1["den"]*frac2["num"]
simplificar(dic_suma_frac)
return(dic_suma_frac)
Para eso utilizamos la definición recursiva de máximo común divisor entre dos enteros:
36
mcd(a, b) = b si a es divisible por b
mcd(a, b) = mcd(b, a mod b)
def simplificar(frac) :
n = mcd(frac["nom"], frac["den"])
frac["nom"], frac["den"] = frac["nom"]//n, frac["den"]//n
def mcd(a,b) :
if a%b == 0 : return b
else : return(mcd(b, a%b))
def comb(i,j) :
if i==j or i==1 : return 1
else : return(comb(i-1, j-1) + comb(i, j-1))
37
def f(x):
b = 20
print("En f, x vale", x)
def g(x):
b = 45
print("En g, antes de llamar a f, x vale", x)
f(b)
x = 10
print("En g, después de llamar a f, x vale", x)
>>> g(1)
En g, antes de llamar a f, x vale 1
En f, x vale 45
En g, después de llamar a f, x vale 10
38
Se efectúa el llamado a la función f(b) desde g. f recibe a
este argumento como parámetro x (local a f).
39
En realidad ya veníamos usando objetos en Python (sin mencionarlo explícitamen-
te). Sin embargo, la manera de hacerlo no estaba establecida formalmente. Por ejemplo,
cuando utilizamos lista.pop(), estamos llamando al método pop sobre un objeto de
la clase list. Los valores asociados a ese objeto (su longitud, por ejemplo) se denominan
atributos.
La OOP introduce o modifica la terminología establecida, dado que al realizarse un
cambio de paradigma es necesario cambiar la forma de nombrar a los elementos que se
aprendieron en el paradigma anterior.
En Python los tipos son clases predefinidas (aunque no en todos los lenguajes de pro-
gramación es así), y un objeto es una instancia de una clase. En general, podemos decir
que una clase es una forma específica de organizar los atributos y los métodos a utilizar
sobre dichos atributos u otros datos. Los objetos tienen estado y comportamiento, ya
que los valores que tengan sus atributos determinan la situación actual de esa instancia,
y los métodos definidos en una clase determinan cómo se va a comportar un objeto de
una clase dada.
En el caso de los tipos básicos (números, por ejemplo) esta superestructura
parece excesiva y por eso nunca la mencionamos hasta ahora. Por otro lado,
tampoco es una buena idea que un lenguaje tenga dos grupos distintos de ele-
mentos (tipos “simples” y clases).
Nuestro objetivo ahora es poder definir nuestras propias clases, en cuya especificación
se indican cuáles son los atributos y métodos que van a tener los objetos que sean de
esa clase. A partir de una clase es posible crear distintos objetos que pertenecen a esa
clase, pero también (ya veremos cómo) crear clases más complejas a partir de incorporar
la definición de otras clases más simples.
Queremos definir nuestra clase que represente un punto en el plano. Lo primero que de-
bemos notar es que existen varias formas de representar un punto en el plano, por ejemplo,
coordenadas polares o coordenadas cartesianas. En esta primera implementación, optare-
mos por utilizar la representación de coordenadas Cartesianas, e iremos implementando
las operaciones a medida que las vayamos necesitando. En primer lugar, creamos una
clase Punto2D que simplemente almacena las coordenadas.
40
class Punto2D :
"""Representación de un punto en el plano en coordenadas cartesianas"""
def __init__(self, x, y) :
"Constructor del Punto2D. x e y deben ser numéricos"
En la primera línea de código indicamos que vamos a crear una nueva clase, llamada
Punto2D. Por convención, en los nombres de las clases definidas por el programador se
escribe cada palabra del nombre con la primera letra en mayúsculas.
Además, definimos uno de los métodos especiales, __init__ el constructor de la clase.
Este método se llama cada vez que se crea una nueva instancia de la clase, recibiendo
como primer parámetro a la instancia misma sobre la que está trabajando (sería una
“auto-referencia”). Por convención a ese primer parámetro se lo llama self (“a sí mismo”)
para que se entienda que la definición actúa sobre esa instancia particular.
>>> p = Punto2D(4,5)
Creando un objeto Punto2D con x=4 y=5
>>> p
<__main__.Punto2D object at 0x000001EF57B69D20>
>>> p.x
4
>>> type(p)
<class '__main__.Punto2D'>
Hemos creado una clase Punto2D que permite guardar valores x e y. Sin embargo, por
más que en “la documentación” se indique que los valores deben ser numéricos, el código
no impide que a x o y se les asigne un valor cualquiera, no numérico.
41
class Punto2D :
def __init__(self, x, y) :
if validar_numero(x) and validar_numero(y) :
print('Creando un objeto Punto2D con x={} y={}'.format(x,y))
self.x = x
self.y = y
else : print('valor(es) no válido(s)')
def validar_numero(x) :
return isinstance(x, (int, float, complex))
>>> p = Punto2D(4,5)
Creando un objeto Punto2D con x=4 y=5
>>> q = Punto2D('x',4)
valor(es) no válido(s)
>>> q
<__main__.Punto2D object at 0x00000284E3C0B9D0>
>>> q.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Punto2D' object has no attribute 'x'
Hasta ahora hemos creado una clase Punto2D que permite construir objetos con un par
de atributos, que deben ser numéricos, pero no podemos operar con sus valores (salvo que
vayamos atributo por atributo). Queremos definir métodos adicionales, por ejemplo, poder
calcular la distancia entre dos puntos. Para ello definimos un nuevo método distancia
que se aplica sobre un objeto Punto2D y recibe por parámetro otro punto entre el cual se
quiere calcular la distancia.
>>> p=Punto2D(3,4)
>>> q=Punto2D(1,2)
>>> p.distancia(q)
2.8284271247461903
42
>>> q.distancia(p)
2.8284271247461903
>>> q.distancia(q)
0.0
Podemos ver, sin embargo, que la operación para calcular la distancia incluye la ope-
ración de restar dos puntos y la de obtener la norma de un vector. Sería deseable incluir
también estas dos operaciones dentro de la clase. Agregaremos, entonces, el método para
restar dos puntos:
Si bien la resta entre dos puntos no es un nuevo punto (por qué?) por el momento este
método devuelve una nueva instancia de Punto2D, en lugar de modificar las instancias
self u otro. A continuación definimos el método para calcular la norma del vector que se
forma uniendo un punto con el origen.
def norma(self) :
"""Norma del vector que va desde el origen hasta el punto. """
return (self.x * self.x + self.y * self.y) ** 0.5
En base a estos dos métodos podemos ahora volver a escribir el método distancia para
que aproveche el código ambos
En definitiva, hemos definido tres métodos en la clase Punto2D, que nos sirven para
calcular restas, normas de vectores al origen, y distancias entre puntos.
>>> p = Punto2D(5, 7)
>>> q = Punto2D(2, 3)
>>> r = p.restar(q)
>>> (r.x, r.y)
(3, 4)
>>> r.norma()
5.0
>>> q.distancia(r)
1.41421356237
43
27.2. Atributos de la clase, atributos “por defecto”
En muchos contextos, todas las instancias de una clase (los objetos creados que per-
tenecen a la misma) poseen atributos genéricos de la clase.
class Perro:
especie = "cánido"
Igualmente a lo que ocurre con los parámetros de las funciones, los métodos pueden
tener valores por defecto
El método __init__ debe estar en toda clase, y se invoca cuando se intenta crear
un objeto de esa clase. En ese sentido es “especial” dado que posee mecanismos (y un
nombre) que no son exactamente los mismos que para otros métodos. Del mismo modo,
hay otros métodos que son muy prácticos y necesarios en varios contextos. Uno de ellos
es indispensable cuando queremos “imprimir” el estado de un objeto:
44
>>> p
<__main__.Perro object at 0x00000279D11BAFB0>
>>> str(p)
'<__main__.Perro object at 0x00000279D11BAFB0>'
Si queremos que nuestra clase incluya la posibilidad de imprimir el estado de sus obje-
tos, entonces sobrecargamos la función str() del siguiente modo:
def __str__(self) :
return('Perro({},{},{})'.format(self.nombre,self.edad,self.raza))
Muchas de las funciones provistas por Python, que ya hemos utilizado, como
str, len o help, invocan internamente a los métodos de los objetos correspon-
dientes. Es decir que la función str internamente invoca al método __str__ del
objeto que recibe como parámetro.
Esto muestra que el método __init__ es el que se ejecuta en la creación de un
objeto cuando se lo asignamos a una variable, y que estos “métodos especiales”
están definidos por sobrecarga para las clases predefinidas. Claramente hay
otros métodos sobrecargados, por ejemplo el ‘+’ asociado a números, strings
y colecciones. El método __repr__ nos permite acceder a la “representación
interna” de un objeto.
>>> p1=Punto2D(1,2)
>>> p2=Punto2D(4,3)
>>> p1-p2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -:
'Punto2D' and 'Punto2D'
>>> p=Punto2D(4,3)
>>> q=Punto2D(2,5)
45
>>> p-q
Punto2D(2,-2)
>>> p+p
Punto2D(8,6)
>>> r=p+p-q
>>> r
Punto2D(6,1)
Para resolver comparaciones entre puntos (por ejemplo por su norma), será necesa-
rio definir algunos métodos especiales que permiten comparar objetos. En particular,
cuando se quiere que los objetos puedan ser ordenados, es suficiente con definir el mé-
todo __lt__ (less than), que corresponde al operador matemático de comparación <.
El método __lt__ recibe dos parámetros, self y otro y debe devolver True si self es
comparativamente “menor” a otro de acuerdo a nuestra definición.
Una vez que tenemos definido el __lt__, esto nos permite ordenar listas de objetos:
>>> p=Punto2D(1,2)
>>> q=Punto2D(3,2)
>>> r=p-q
>>> s=p+p-q
>>> w=Punto2D(8,-8)
>>> lista=[p,q,r,s,r,q,p,w]
>>> lista
[Punto2D(1,2), Punto2D(3,2), Punto2D(-2,0), Punto2D(-1,2), Punto2D(-2,0),
Punto2D(3,2), Punto2D(1,2), Punto2D(8,-8)]
>>> lista.sort()
>>> lista
[Punto2D(-2,0), Punto2D(-2,0), Punto2D(1,2), Punto2D(-1,2), Punto2D(1,2),
Punto2D(3,2), Punto2D(3,2), Punto2D(8,-8)]
También es posible utilizar el flag key en el método sort. Si queremos ordenar por el
valor de y, por ejemplo, utilizamos una función lambda.
46
>>> lista.sort(key=lambda p:p.y)
>>> lista
[Punto2D(8,-8), Punto2D(-2,0), Punto2D(-2,0), Punto2D(1,2), Punto2D(-1,2),
Punto2D(1,2), Punto2D(3,2), Punto2D(3,2)]
Las funciones lambda son “funciones anónimas” (hace falta una función, pero
no hace falta definirla, darle un nombre, etc.). Tienen una sintaxis y comporta-
miento similar a una función “normal” pero no requieren toda la burocracia de
la definición usual.
También se puede sobrecargar el operador de igualdad == utilizando __eq__.
27.5. Resumen
class NombreClase:
variable = NombreClase(...)
Crea un nuevo objeto como instancia de la clase. Los parámetros que se ingresen serán
pasados a la constructora, luego del parámetro especial self.
variable.atributo
El primer parámetro de cada método de una clase es una referencia a la instancia sobre
la que va a operar el método. Se lo llama por convención self, pero puede tener cualquier
nombre.
variable.metodo(...)
47
def __str__(self):
Método especial que debe devolver una cadena de caracteres, con la representación
“informal” de la instancia. Se invoca al hacer str(variable) o print(variable).
def __repr__(self):
Método especial que debe devolver una cadena de caracteres, con la representación
“formal” de la instancia. Se invoca al hacer repr(variable).
48
28.1. Break
En varios ejemplos vimos que es necesario “repetir” alguna sentencia para expresar una
estructura de control dada. Por ejemplo un programa que pide entrada a la usuaria y
luego ejecuta una tarea y regresa, o bien se termina la ejecución:
Parece más sensato preguntar al comienzo del while, y si la respuesta es salir, entonces
abandonar el while, pero no contamos con un mecanismo para hacerlo.
Otro ejemplo similar ocurrió en la búsqueda secuencial, donde vamos recorriendo una
lista de a un elemento hasta encontrar el buscado o terminar.
Aquí también parece sensato no estar preguntando en cada paso si “lo encontré” sino
más bien abandonar el while cuando ello ocurra. En estos y otros casos nos sirve el meca-
nismo break como parte de un ciclo, que como su nombre indica “rompe” la ejecución del
ciclo y lleva el control a la sentencia inmediata posterior. Nuestros ejemplos “mejorados”
con el uso del break quedarían así:
while True :
Flag = input(" bla bla única vez “)
if Flag == "N" : hacer algo
elif Flag == "L" : hacer otra cosa
elif Flag == "Salir" : break
En breve: cuando durante la ejecución del cuerpo de un ciclo for o while se llega a una
instrucción break, la ejecución abandona el ciclo inmediatamente. Similarmente existe la
49
sentencia continue, en cuyo caso la ejecución abandona el resto del ciclo que se está
ejecutando y se pasa inmediatamente al ciclo siguiente.
La otra situación en la cual es deseable interrumpir el flujo natural del control es cuando
ocurre algo excepcional (archivo no encontrado, división por cero, dato mal conformado,
etc.) en cuyo caso lo que normalmente ocurre es que la excepción nos interrumpe el pro-
grama o nos genera un mensaje de error indeseado. Además no siempre podemos predecir
en qué contexto ocurrirá la excepción. Podemos tener una función F que potencialmente
genera una división por cero, la cual puede ser a su vez llamada desde otras funciones.
Si la función A, llama a la función B y ésta a F y en F ocurre la excepción, si F no
gestiona la excepción entonces se interrumpe y el control retorna a B. Si B no maneja la
excepción, se interrumpe y retorna a A, etc.
Para evitar esta cadena de malas posibilidades, tenemos la sentencia try, que permite
englobar la o las sentencias que pudiesen generar una excepción, la cual en caso de ocurrir
es gestionada por otro grupo de sentencias englobadas dentro de la sentencia except.
def reciproco(n):
try: return 1/n
except: return 99999999999999999
while True:
n = input('Ingresar dato ')
if n=='': break
print('El recíproco de {} es {}'.format(int(n), reciproco(int(n))))
Ingresar dato 1
El recíproco de 1 es 1.0
50
Ingresar dato 2
El recíproco de 2 es 0.5
Ingresar dato 0
El recíproco de 0 es 9999999999999
Ingresar dato
Dado que dentro de un mismo bloque try pueden producirse excepciones de distinto
tipo, es posible definir un bloque except para cada caso. Esto se hace especificando el
nombre de la excepción que se pretende capturar. Si bien luego de un bloque try puede
haber varios bloques except, se ejecutará a lo sumo uno de ellos.
try:
# aquí está el código que puede generar excepciones
except IOError:
# excepción IOError es de entrada/salida
except ZeroDivisionError:
# excepción ZeroDivisionError ya se sabe qué es
except:
# en caso que se haya producido una excepción diferente
def reciproco2(n):
try:
return 1/n
except Exception as ex:
print('Ocurrió una excepción de tipo {}'. format(type(ex).__name__))
>>> reciproco2(1)
1.0
>>> reciproco2(0)
Ocurrió una excepción de tipo ZeroDivisionError
>>> reciproco2('a')
Ocurrió una excepción de tipo TypeError
class Triangulo:
def __init__(self, a, b, c):
lados = [a, b, c]
lados.sort()
if lados[0]+lados[1] < lados[2]:
raise ValueError('Triángulo mal conformado!')
51
self.a = a
self.b = b
self.c = c
exception ArithmeticError
La clase base para las excepciones predefinidas que se generan para varios errores
aritméticos: OverflowError, ZeroDivisionError, FloatingPointError.
exception BufferError
exception AttributeError
exception EOFError
Se genera cuando la función input() alcanza una condición de fin de archivo (EOF)
sin leer ningún dato (en cambio io.IOBase.read() y io.IOBase.readline() retornan
una cadena vacía cuando llegan a EOF).
52
exception FloatingPointError
Excepción depricada, para errores numéricos, pero no se usa en las nuevas versiones.
exception ImportError
exception IndexError
Se genera cuando un subíndice de secuencia está fuera del rango. (Los índices de la
rebanada son truncados silenciosamente para caer en el intervalo permitido; si un índice
no es un entero, se genera TypeError.)
exception KeyError
exception KeyboardInterrupt
exception MemoryError
Se genera cuando una operación se queda sin memoria pero la situación aún puede ser
recuperada (eliminando algunos objetos). El valor asociado es una cadena que indica que
tipo de operación (interna) se quedó sin memoria.
exception NameError
53
exception OSError([arg], errno, strerror[, filename[, winerror[, filename2]]])
Esta excepción se produce cuando una función del sistema retorna un error relacionado
con el sistema, que incluye fallas de E/S como file not found o disk full (no para
tipos de argumentos ilegales u otros errores incidentales).
exception OverflowError
exception RecursionError
exception RuntimeError
Se genera cuando se detecta un error que no corresponde a ninguna de las otras cate-
gorías. El valor asociado es una cadena que indica exactamente qué salió mal.
exception IndentationError
exception SystemError
exception TypeError
54
exception ValueError
Se genera cuando una operación o función recibe un argumento que tiene el tipo correcto
pero un valor inapropiado.
exception ZeroDivisionError
55