0% encontró este documento útil (0 votos)
20 vistas89 páginas

Python - CEIA - Apunte Teórico

Cargado por

Martin Buñes
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
20 vistas89 páginas

Python - CEIA - Apunte Teórico

Cargado por

Martin Buñes
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 89

Universidad Nacional del Sur

Centro de Estudiantes de Ingeniería y Agrimensura

Introducción al lenguaje Python


Parte 1

Equipo del Laboratorio de Ciencias de las Imágenes


Abril 2024
Departamento de Ing. Eléctrica y Computadoras
Universidad Nacional del Sur
Índice
1. ¿Por qué necesitamos algoritmos? 2

2. Cómo darle instrucciones a la máquina con Python 3

3. Variables 4

4. Funciones 5

5. Iteradores: el ciclo definido 6

6. Interacción con usuarias 8

7. Tipos simples y operaciones en tipos simples 8

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

10.Tipo cadena de caracteres 14

11.Tomando Decisiones 15

12.Conversiones de valores entre tipos 17

13.Tuplas 19

14.Revisitando la asignación 21

15.Iteración revisitada: ciclo indefinido 22

16.Definición de Funciones 28

17.Alcance de las variables 30

18.Revisitando nuevamente la asignación 31

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 .

Al resultado anterior multiplicarlo por sí mismo con lo cual ya disponemos de N 4 .

Al resultado anterior multiplicarlo por sí mismo con lo cual ya disponemos de N 8 .

Al resultado anterior multiplicarlo por sí mismo con lo cual ya disponemos de N 16 .

Al resultado anterior multiplicarlo por sí mismo con lo cual ya disponemos de N 32 .

Al resultado anterior multiplicarlo por N con lo cual conseguimos el resultado de-


seado con sólo 6 multiplicaciones.

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.

Hasta hace no mucho tiempo se utilizaba el término algoritmo para referirse


únicamente a formas de realizar ciertos cálculos, pero con el surgimiento de la
computación, el término algoritmo pasó a abarcar cualquier método para ob-
tener un resultado. El uso de la palabra algorismo proviene del nombre de un
matemático persa famoso, en su época y para los estudiosos de esa época, Abu
Abdallah Muhammad ibn Mûsâ al-Jwârizmî. En la antigüedad, los algoristas
eran los que calculaban usando la numeración arábiga y mientras que los aba-
cistas eran los que calculaban usando ábacos. Con el tiempo el algorismo se
deformó en algoritmo, influenciado por el término aritmética.

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>.

En https://www.python.org/downloads/ se encuentran los enlaces para des-


cargar la última versión Python (y las anteriores).
En http://docs.python.org.ar/tutorial/3/interpreter.html hay más
información acerca de cómo ejecutar el intérprete en cada sistema operativo.

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.

Para orientarnos, el intérprete muestra los símbolos ≫ (llamaremos a esto el prompt),


indicando que podemos escribir a continuación una sentencia u orden que será evalua-
da. Algunas sentencias sencillas, por ejemplo, permiten utilizar el intérprete como una
calculadora simple con números (enteros o floats –reales). Para esto escribimos la expre-
sión que queremos resolver luego del prompt y presionamos la tecla Enter . El intérprete
de Python evalúa la expresión y muestra el resultado en la línea siguiente. Luego nos
presenta nuevamente el prompt.

>>> 2+3
5
>>>

Python permite utilizar las operaciones +, -, *, ! y ** (suma, resta, multiplicación,


división y potenciación). La sintaxis es la convencional (valores intercalados con opera-
ciones), y se puede usar paréntesis para modificar el orden de asociación natural de las
operaciones (potenciación, producto/división, suma/resta). El práctico 1 que presentará
Marina introduce estos operadores y otros conceptos.

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 (”):

>>> '¡Hola Mundo!'


'¡Hola Mundo!'
>>> 'abcd' + 'efgh'
'abcdefgh'
>>> 'abcd' * 3
'abcdabcdabcd'

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.

Funciones, programas, módulos, bibliotecas (libraries) son mecanismos para


articular fragmentos de código con funcionalidades específicas, hechas para per-
mitir el reuso, la portabilidad, la parametricidad, etc.

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.

5. Iteradores: el ciclo definido


Problema. Supongamos que queremos calcular la suma de los primeros 5 números
cuadrados. Podemos hacer algo como esto:

>>> 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:

Generar la secuencia de valores enteros del intervalo [0, n), y

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:

for x in range(n1, n2):


<hacer algo con x>

La instrucción que describe el rango en el que va a realizar el ciclo for x in range( )


es el encabezado del ciclo, y las instrucciones que describen la acción que se repite com-
ponen el cuerpo del ciclo. Todas las instrucciones que describen el cuerpo del ciclo deben
tener una sangría mayor que el encabezado del ciclo. Si hace falta más de una instrucción,
se colocan todas con el mismo sangrado.

>>> 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

El intérprete provee una ayuda en línea, nos puede dar la documentación de


cualquier función o instrucción. Para obtenerla llamamos a la función help().
Si le pasamos por parámetro el nombre de una función (por ejemplo help(abs)
o help(range)) nos dará la documentación de esa función. Para obtener la
documentación de una instrucción la debemos poner entre comillas; por ejemplo:
help('for'), help('return').

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:

nombre = input("Por favor ingrese su nombre: ")


saludo = 'Hola ' + nombre + '!'
print(saludo)

7. Tipos simples y operaciones en tipos simples


Los tipos de datos son conjuntos que determinan los valores admisibles que una variable
o constante del tipo puede asumir. Estos valores, y estos conjuntos, tienen una serie de
características y propiedades determinadas. En Python, todo valor que pueda ser
asignado a una variable tiene asociado un tipo de dato, y por lo tanto qué
operaciones se pueden realizar sobre la misma.
Los tipos de datos básicos de Python son los booleanos, los numéricos (enteros, punto
flotante y complejos) y las cadenas de caracteres, que ya hemos visto, y que en principio
son intuitivos de interpretar.
Python también define otros tipos de datos, entre los que se encuentran:

Secuencias: Lista, tupla y rango

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.

8.1. Números enteros

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.

>>> a = -1 # a es de tipo int y su valor es -1


>>> b = a + 2 # b es de tipo int y su valor es 1
>>> b
1

8.2. Números de punto flotante

Los números de punto flotante representan aproximadamente al conjunto de los núme-


ros reales. ¿Qué entendemos por aproximadamente? No nos referimos a que solamente
podemos representar algunos valores del conjunto, sino que tampoco podemos representar
con exactitud los valores en sí (salvo excepciones).
La representación en cualquier base numérica de un número irracional es infinita, lo
cual con la computadora no puede hacerse. La representación interna en la computadora

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!!!:

>>> 1.1 + 2.2


3.3000000000000003

8.3. Representación de los números de punto flotante

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í:

Número Notación científica

101,1 1,011 ∗ 102


0,032 3,2 ∗ 10− 2

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.

>>> Mi_real = 1.1 + 2.2 # Mi_real es un float


>>> Mi_real
3.3000000000000003
>>> print(f'{Mi_real:.2f}') # mostrando 2 cifras decimales
3.30

8.4. Números complejos

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.

>>> Mi_complejo = 1 + 2 j #los espacios son supérfluos


>>> Mi_complejo.real
1.0
>>> Mi_complejo.imag
2.0

8.5. Aritmética de los tipos numéricos

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.

George Boole (1815-1864) desarrolló un sistema de reglas que le permitían


expresar, manipular y simplificar mecánicamente problemas lógicos cuyos va-
lores admiten dos estados (verdadero o falso). Dicho sistema es un álgebra de
operadores lógicos utilizado como base para la computación contemporanea.

Las constantes o variables booleanas se pueden combinar entre sí formando expresiones


lógicas. Una característica importante de los lenguajes de programación es que permiten
comparar valores escalares (reales y enteros) y determinar el valor de verdad de una
comparación. A dichas expresiones se las denomina expresiones de comparación, y siempre
evalúan a verdadero o falso. Las expresiones booleanas de comparación que provee Python
son las siguientes:

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

En particular la pregunta de si x es mayor que cero, se codifica en Python como x > 0.


De esta forma, 5 > 3 es una expresión booleana cuyo valor es True, y 5 < 3 también es
una expresión booleana, pero su valor es False. Algunos ejemplos (ver los comparadores
combinados al final):

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

9.1. Operadores lógicos

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

a and b El resultado es True solamente si a es True y b es True de lo contrario


el resultado es False.
a or b El resultado es True si a es True o b es True (o ambos) de lo contrario
el resultado es False.
not a El resultado es True si a es False de lo contrario el resultado es False.

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 > 2 or 5 > 3


True
>>> 5 > 2 or 5 > 6
True
>>> 5 > 8 or 5 > 6
False

not a > b es verdadero si a > b es falso (o sea si a <= b es verdadero).

>>> 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

El valor cero de cualquier tipo numérico: 0, 0.0, 0j, ...

Secuencias y colecciones vacías: '', (), [], {}, set(), range(0)

10. Tipo cadena de caracteres


Otro tipo básico de Python es la secuencia o cadena de caracteres. Este tipo es conocido
como string str. Formalmente, un string es una secuencia inmutable de caracteres en
formato UNICODE.

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».

11. Tomando Decisiones


Problema. Debemos leer un número y, si el número es positivo, debemos devolver
“Número positivo”.
Diseñamos nuestra solución:

1. Solicitar a la usuaria un número, guardarlo en x.

2. Si x >0, imprimir "Número positivo"

La primera línea requiere convertir la cadena de caracteres de la usuaria y convertirla


a un entero (suponiendo que esto sea posible, caso contrario se generará un error).

x = int(input("Ingrese un número: "))

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")

Probamos nuestra solución en un script positivo.py:


Y luego probamos nuestro script desde la consola:

$ python positivo
Ingrese un número: 4
Número positivo
$ python positivo
Ingrese un número: -25
$ python positivo
Ingrese un número: 0

Problema. Necesitamos además un mensaje “Número no positivo” cuando no se cum-


ple la condición. Modificamos la especificación consistentemente y modificamos el diseño:

1. Solicitar a la usuaria un número, guardarlo en x.

2. Si x >0, imprimir "Número positivo"

3. Caso contrario, imprimir "Número no positivo"

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:

x = int(input("Ingrese un número: "))


if x > 0:
print("Número positivo")
if not x > 0:
print("Número no positivo")

Probamos la nueva solución y obtenemos el resultado buscado:

$ 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>

Nuestro script quedaría ahora

x = int(input("Ingrese un número: "))


if x > 0:
print("Número positivo")
else:
print("Número no positivo")

12. Conversiones de valores entre tipos

Operación Operador Comentario

Selecciona el i-ésimo caracter del string s, en base


Indexación s[i] cero (s[0] es el primer caracter). Un índice ne-
gativo se cuenta desde el último elemento.
Rango s[i:j] Selecciona el rango de caracteres entre [i:j).
Longitud len(s) Devuelve la cantidad de caracteres en s.
ASCII ord(c) Valor ASCII de un caracter.
El caracter que corresponde a un código ASCII
Caracter chr(x)
dado.
Conversión a Convierte un valor escalar al string correspon-
str(x)
string diente.
Conversión a Convierte un string con una cadena de dígitos al
int(s)
entero valor entero correspondiente.
Conversión a Convierte un string con un real al valor real co-
float(s)
real rrespondiente.

17
Que un string sea inmutable significa (entre otras cosas) que no puedo asignar a ninguno
de sus caracteres

>>> str = 'abc'


>>> str[0] = 'x' # esto genera un error!!

ASCII es el acrónimo de American Standard Code for Information Interchan-


ge, y es un código de caracteres basado en el alfabeto latino, tal como se usa en
inglés moderno. El código ASCII utiliza 7 bits para representar los caracteres.
En la actualidad define códigos para 32 caracteres no imprimibles, de los cuales
la mayoría son caracteres de control que tienen efecto sobre cómo se procesa el
texto, más otros 95 caracteres imprimibles que les siguen en la numeración (em-
pezando por el carácter espacio). Casi todos los sistemas informáticos actuales
utilizan el código ASCII o una extensión compatible para representar textos y
para el control de dispositivos que manejan texto como el teclado.

Algunos caracteres ASCII:

18
Un ejemplo: encontrar el mayor de tres enteros

>>> x = int(input("Ingresar el primer entero: "))


>>> y = int(input("Ingresar el segundo entero: "))
>>> z = int(input("Ingresar el tercer entero: "))
>>> if y > x:
>>> if z > y:
>>> print(z," es el mayor.")
>>> else:
>>> print(y," es el mayor.")
>>> else:
>>> if z > x:
>>> print(z," es el mayor.")
>>> else:
>>> print(x," es el mayor.")
>>> print("Listo!")

13. Tuplas
La tupla es un objeto secuencial inmutable, es una colección ordenada de objetos (que
pueden contener a su vez tuplas).

>>> tupla = (2, 'hola', 3.14, 2+3j, (2, 3))

Observación: todos los elementos en la tupla son (o referencian a) valores.

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 = (23, 'abc', 4.56, (2,3), 'def')


>>> t[1]
'abc'
>>> t[-3]
4.56
>>> t[1:4]
('abc', 4.56, (2,3))
>>> t[1:-1]
('abc', 4.56, (2,3))
>>> t[:2]
(23, 'abc')
>>> t[2:]
(4.56, (2,3), 'def’)

>>> 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')

El operador in evalua a un valor Booleano, si un elemento está en una tupla (y también,


si una sub-cadena está en un string). También nos puede servir como iterador en un
ciclo definido.

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

>>> for x in (1, 'a', (1, 2)):


... print(x)
...
1
a
(1, 2)

>>> str='abc'
>>> for x in str: print(x)
...
a
b
c

14. Revisitando la asignación


Con sentencias sencillas de asignación como x=4, estamos estableciendo varios aspectos
subyacentes:

1. Se crea un objeto (en este caso el int 4),

2. se crea un identificador (en este caso x)

3. y se crea una referencia, de manera que el identificador está ligado al objeto.

Los identificadores no tienen un tipo (los objetos si, y Python automáticamente lo


determina), por lo que el tipo del identificador depende del objeto al cual haga referencia.

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

Cuando el objeto es inmutable, su valor no cambia, y por lo tanto si se modifica el


valor referenciado por un identificador, subyacentemente se crea otro objeto con el nuevo
valor. Por ejemplo

x = 3 # se crea un nombre x, se crea un objeto 3, se crea una referencia


#de x a ese objeto
x = x + 1 # se crea un objeto 4, x referencia a ese objeto, el objeto 3
#queda “aislado”

Que ocurre cuando una variable se asigna a otra variable?

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.

15. Iteración revisitada: ciclo indefinido


En muchas ocasiones necesitamos iterar una tarea, pero la cantidad necesaria de pasos
es desconocida (o puede variar en cada ejecución de un mismo programa). Para poder
resolver este problema sin averiguar primero la cantidad de números a procesar, debe-
mos introducir una instrucción que nos permita construir ciclos que no requieran que se
informe de antemano la cantidad de veces que se repetirá el cálculo del cuerpo. Se trata
de ciclos indefinidos en los cuales se repite la ejecución del cuerpo mientras una cierta
condición es verdadera:

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.

2. Si la condición evalúa a alguno de los valores nulos, salir del ciclo.

3. Si la condición es verdadera, ejecutar el cuerpo.

4. Volver a 1.

Recordamos los valores nulos en una expresión


None
False
El valor cero de cualquier tipo numérico: 0, 0.0, 0j, ...
Secuencias y colecciones vacías: '', (), [], {}, set(), range(0)

¿Qué situación “riesgosa” se observa en la secuencia del funcionamiento del while?

Ejemplo: mi primer “sistema”


Habíamos mencionado que un sistema, a diferencia de un algoritmo, ejecuta su tarea de
manera indefinida. Podemos tomar nuestro ejemplo de decidir si un numero ingresado es
positivo o no y transformarlo en un programa de ejecución contínua, colocándolo dentro
de un ciclo while “infinito”:

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:

1. Elija un código secreto (codigo) de cuatro dígitos diferentes

2. Pide a la usuaria una propuesta que adivine el código

3. Si la usuaria adivinó la felicita y le indica cuántos intentos necesitó


Caso contrario:

Le indica cuántos dígitos hay correctamente elegidos (coincidencias),


indica cuántos están correctamente colocados (aciertos),
Vuelve al paso 2

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

1. Elegir un dígito al azar

2. Mientras el dígito esté en el string, elegir otro

3. Concatenar el dígito al string

import random #necesitamos para elegir al azar

# para elegir el codigo


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

Bibliotecas (libraries) y funciones de biblioteca


Si bien el intérprete está en condiciones de reconocer y ejecutar una gran canti-
dad de funciones (por ejemplo print, int, chr, etc.) hay una gran cantidad
(pero MUY grande!) de funciones disponibles. El intérprete no carga todas por-
que sería demasiado peso, entonces le delega a la programadora la decisión de
incorporar en su script o programa las funciones que necesite, agrupadas “temá-
ticamente” en bibliotecas (libraries).
Para avisarle a nuestro intérprete que utilizaremos una función de biblioteca,
al comienzo del script utilizamos la declaración import seguido del nombre de
la biblioteca (tenemos que saber cuál necesitamos, o buscarlo en Internet). Las
declaraciones no son instrucciones o sentencias de nuestro programa, sino avisos
al intérprete para que cargue la biblio correspondiente.

¿Cómo ver cuántos dígitos son coincidencias y cuántos aciertos?

Asignar aciertos, coincidencias = 0, 0

Para i entre 0 y 3 ver

1. Si propuesta[i] == codigo[i] aumentamos el valor de aciertos

25
2. Caso contrario si propuesta[i] in codigo aumentamos coincidencias

3. Sí aciertos == 4 la usuaria ganó!

# para determinar si ganaste


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
if aciertos == 4:
ganaste = True

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:

1. Saludar, inicializar intentos (a cero), inicializar ganaste (a falso)

2. Elegir el codigo secreto

3. Pedir la propuesta a la jugadora

4. Mientras ganaste sea falso repetir

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: ")

El script final queda así:

import random
print("Bienvenidx al Mastermind!")
print("Tenés que adivinar un número de cuatro dígitos distintos")
intentos = 0
ganaste = False

# para elegir el código


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

propuesta = input("Que código proponés?: ")

while not ganaste:


intentos = intentos + 1
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
if aciertos == 4:
ganaste = True
print("Felicitaciones, has ganado en "+str(intentos)+" intentos!")
else:
print("Tu propuesta "+str(propuesta)+" tiene "+str(aciertos)+"
aciertos y "+str(coincidencias)+" coincidencias")
propuesta = input("Elegí otro código: ")

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.

Volviendo al Mastermind, ¿cómo utilizar funciones para hacerlo más “articulado”?


return es una palabra reservada que como imaginamos le indica al intérprete que la
función terminó y debe devolver el valor indicado a continuación. En Python las tuplas
son tipos inmutables, y por lo tanto una función puede devolver (en una tupla) más de
un valor. Esta característica es muy ventajosa.

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

def analizar(propuesta, codigo):


""" Devuelve la cantidad de aciertos y coincidencias."""

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!!

Finalmente nuestro script queda (ponele) más elegante

import random

<--- aquí colocamos las definiciones de las funciones --->

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)

x = 'este es el x del programa principal'


funcion_1()
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)

x = 'este es el x del programa principal'


funcion_1()
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:

a = int(input("Ingrese el coeficiente de la variable cuadrática\n"))


b = int(input("Ingrese el coeficiente de la variable lineal\n"))
c = int(input("Ingrese el término independiente\n"))

e, f = bsk(a,b,c)
if e:
print('raiz 1 = ' + e)
if f:
print('raiz 2 = ' + f)

Tenemos que considerar todos los casos posibles:

from math import sqrt

def bsk(x2, x1, x0):


if x2 == 0:
if x1 == 0:
print("no tiene solución")
return '', ''
else:
print("sistema lineal")
return str(-x0/x1), ''
else:
discr = x1**2 - 4*x2*x0
if discr >= 0:
r1 = (-x1 + sqrt(discr))/(2*x2)
r2 = (-x1 - sqrt(discr))/(2*x2)
return str(r1), str(r2)
else:
re = -x1/(2*x2)
im = sqrt(-discr) / (2*x2)
return str(complex(re,im)), str(complex(re,-im))

18. Revisitando nuevamente la asignación


Python permite asignar tuplas (a esto se llama “empaquetado” y “desempaquetado”)

31
>>> a = 125
>>> b = "#"
>>> c = "Ana"
>>> d = a, b, c
>>> d
(125, '#', 'Ana')
>>> x, y, z = d
>>> x
125
>>> y
'#'
>>> z
'Ana'

Hay casos de empaquetado y desempaquetado que requieren consideración:

>>> 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

En la implementación de algunos programas suele ser necesario intercambiar el


valor de dos variables. Si queremos intercambiar el valor de a y b, una posibilidad
es utilizar una variable auxiliar:
aux = a
a = b
b = aux
Un truco que podemos aplicar es empaquetar y desempaquetar una tupla en
una única operación:
a, b = b, a

32
Universidad Nacional del Sur
Centro de Estudiantes de Ingeniería y Agrimensura

Introducción al lenguaje Python


Parte 2

Equipo del Laboratorio de Ciencias de las Imágenes


Abril 2024
Departamento de Ing. Eléctrica y Computadoras
Universidad Nacional del Sur
Índice
19.Listas 2

20.Revisitando otra vez más la asignación (‘ “No de nuevo!” me decía’) 10

21.Repaso de lo visto hasta ahora 13

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

27.Programación orientada por objetos 39


27.1. Definiendo nuevas clases . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
27.2. Atributos de la clase, atributos “por defecto” . . . . . . . . . . . . . . . . 44
27.3. Métodos “especiales” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
27.4. Métodos para comparar objetos . . . . . . . . . . . . . . . . . . . . . . . 46
27.5. Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

28.Alteración del flujo natural del control 48


28.1. Break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
28.2. Try – except – finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
28.3. Algunas de las excepciones más comunes . . . . . . . . . . . . . . . . . . 52

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 ']'.

lista_de_enteros = [1, 3, 900]


lista_heterogenea = ["Hola", 1, 2.34]
lista_con_listas = ["Elemento', [1, 2, 3], [3.4, "casa']]
lista_vacia = []

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'

Muchos procesos se realizan con iteradores sencillos:

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 )

Ejemplo un poco más elaborado: buscar un elemento y determinar en qué posición se


encuentra, o bien si no está.

2
Buscado = int(input("Ingrese el elemento que desea buscar"))

# Inicializamos los valores relevantes


Pos = 0
Lo_Encontre = False

# Recorremos la lista hasta encontrarlo o hasta que se termine


while Pos < len(lista_enteros) and not Lo_Encontre :
if lista_enteros[Pos] == Buscado :
Lo_Encontre = True
Pos = Pos + 1
# salimos del ciclo: o lo encontré o ya revisé toda la lista
if Lo_Encontre :
print("Encontré el elemento en la pos", Pos )
else:
print("El elemento no está en la lista.")

>>> 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

Los operadores usuales en otras colecciones también funcionan con listas.


Lo interesante con las listas es que podemos modificarlas:

>>> for i in range(len(L)) : L[i] = 0.5 + L[i]


...
>>> L
[1.5, 2.5, 3.5]
>>> L[1:3] = 4, 5, 6
>>> L
[1.5, 4, 5, 6]

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]

Algunos de los métodos sobre listas:

>>> lista = [1,2,3,4,5]


>>> lista.append(6)
[1, 2, 3, 4, 5, 6]
>>> lista.insert(1, 1.5) # no confundir con lista[1] = 1.5
[1, 1.5, 2, 3, 4, 5, 6]
>>> lista.index(5)
5
>>> lista.pop(0)
1
>>> lista
[1.5, 2, 3, 4, 5, 6]
>>> lista.pop(2)
3
>>> lista
[1.5, 2, 4, 5, 6]

4
Triángulo de Pascal
1

Es una estructura matemáti- 1 1


ca que se obtiene en cada cel-
1 2 1
da al sumar los dos números que
están inmediatamente arriba (al 1 3 3 1

“NE” y al “NO”), considerando 1 4 6 4 1


en los extremos que el exterior
1 5 10 10 5 1
del triángulo contiene todos ce-
ros. De esa forma se obtienen to-
0

0
dos los coeficientes binomiales y
1 1
 
números combinatorios. Cada fi- 0 1

la representa el orden del bino- 2


 2
 2

0 1 2
mio (o la cantidad de elementos)
3 3 3 3
   
0 1 2 3
y la “columna” el término del bi-
nomio o la cantidad de elemen- ↑
tos a extraer. Última fila correspondiente a (a + b)3

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.

Ingrese la cantidad de filas deseada 8


[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]

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.

>>> string = "Uno, dos, tres, cuatro"


>>> string.split(' ')
['Uno,', 'dos,', 'tres,', 'cuatro']
>>> string.split(',')
['Uno', ' dos', ' tres', ' cuatro']
>>> string.split(', ')
['Uno', 'dos', 'tres', 'cuatro']
>>> string.split('res')
['Uno, dos, t', ', cuatro']

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.

>>> "@".join(['a', 'b'])


'a@b'

7
>>> "-".join(list("hola"))
'h-o-l-a'

>>> " + ".join(list("hola"))


'h + o + l + a'

La función sort recibe una lista y devuelve otra lista ordenada.

>>> bs = [5, 2, 4, 2]
>>> cs = sorted(bs)
>>> bs
[5, 2, 4, 2]
>>> cs
[2, 2, 4, 5]

El método sorted aplicado a una lista, ordena la misma lista.

>>> 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:

[["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"],
["137310", "Gomez Lara, Yanella Verónica", "44321437"],
["131644", "Guerrero, Juan Ignacio", "43972078"],
["135859", "Martínez Sonaglioni, Amparo", "44490458"],
["112064", "Mayo Perez, Daniel", "40300838"],
["107378", "Ortiz, Ronaldo Yair", "38093296"],
["129753", "Pacheco, Maico Alexis", "42849687"],
["136172", "Petruf, Joaquín", "43891205"],
["134355", "Pirola, Facundo Dante", "44881140"],
["136278", "Prat, Fermín", "43304853"],
["126665", "Schmit, Lucas Gabriel", "42316124"]]

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:

Flag = input("Ingrese qué desea buscar ('N' Nombre, 'L'


Legajo, 'D' DNI, '' para salir: ")
while Flag :
if Flag == "N" :
Buscado = input("Ingrese DNI o Legajo: ")
print(Buscar_Nombre(Buscado))
elif Flag == "L" :
Buscado = input("Ingrese DNI o Nombre: ")
print(Buscar_Legajo(Buscado))

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)

20. Revisitando otra vez más la asignación (‘ “No de


nuevo!” me decía’)
La asignación en objetos mutables tiene un comportamiento diferente. Si asignamos
un objeto mutable (por ahora una lista) a una nueva variable, esta variable tendrá como
referencia al mismo objeto mutable (no se hace una copia de su valor).

>>> 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.

Cuando al alterar el valor de una referencia se altera el valor de alguna otra,


a eso se lo denomina ”efecto colateral“.

Supongamos ahora que quisiéramos una salida de la tabla ordenada. Haciendo


for e in sorted(Legajos) : print(e)
obtenemos una salida ordenada por el primer campo de cada registro:

['107378', 'Ortiz, Ronaldo Yair', '38093296']


['112064', 'Mayo Perez, Daniel', '40300838']
['117806', 'Aldebert, Gabriel Ivan', '41460080']
['125675', 'Castro, Facundo Nicolás', '42114637']
['126148', 'Cricco, Nicolás', '42652489']
['126665', 'Schmit, Lucas Gabriel', '42316124']
['129753', 'Pacheco, Maico Alexis', '42849687']
['130064', 'Gogg Aller, Francisco Agustin', '43040917']
['131644', 'Guerrero, Juan Ignacio', '43972078']
['134355', 'Pirola, Facundo Dante', '44881140']
['135859', 'Martínez Sonaglioni, Amparo', '44490458']
['136052', 'Amoroso, Sebastian', '44298744']
['136172', 'Petruf, Joaquín', '43891205']
['136278', 'Prat, Fermín', '43304853']
['137310', 'Gomez Lara, Yanella Verónica', '44321437']

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()

Esto da resultado “superficialmente” (se crea una nueva


lista, pero los elementos de la misma son “alias” de los
elementos de la lista anterior).

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)
...

Con esto obtenemos el resultado buscado sin alterar Legajos:

['Aldebert, Gabriel Ivan', '41460080', '117806']


['Amoroso, Sebastian', '44298744', '136052']
['Castro, Facundo Nicolás', '42114637', '125675']
['Cricco, Nicolás', '42652489', '126148']
['Gogg Aller, Francisco Agustin', '43040917', '130064']
['Gomez Lara, Yanella Verónica', '44321437', '137310']
['Guerrero, Juan Ignacio', '43972078', '131644']
['Martínez Sonaglioni, Amparo', '44490458', '135859']
['Mayo Perez, Daniel', '40300838', '112064']
['Ortiz, Ronaldo Yair', '38093296', '107378']
...

Finalmente, para ordenar por DNI hacemos la rotación opuesta:

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

(valor1, valor2, valor3)

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

Es posible desempaquetar una secuencia, asignando a la izquierda tantas variables


como elemen- tos tenga la secuencia. Cada variable tomará el valor del elemento que se
encuentra en la misma posición.

len(secuencia)

Devuelve la cantidad de elementos que contiene la secuencia, 0 si está vacía.

for elemento in secuencia:

Itera uno a uno por los elementos de la secuencia.

elemento in secuencia

Indica si el elemento se encuentra o no en la secuencia

secuencia[i]

Corresponde al valor de la secuencia en la posición i, comenzando desde 0. Si se utilizan


números negativos, se puede acceder a los elementos desde el último (-1) hasta el primero
(-len(secuencia)). En el caso de las tuplas o cadenas (inmutables) sólo puede usarse
para obtener el valor, mientra que en las listas (mutables) puede usarse también para
modificar su valor.

secuencia[i:j:k]

Permite obtener un segmento de la secuencia, desde la posición i inclusive, hasta la


posición j exclusive, con paso k.
En el caso de que se omita i, se asume 0. En el caso de que se omita j, se asume
len(secuencia). En el caso de que se omita k, se asume 1. Si se omiten todos, se obtiene
una copia completa de la secuencia.

14
lista.append(valor)

Agrega un elemento al final de la lista.

lista.insert(posicion, valor)

Agrega un elemento a la lista, en la posición posicion.

lista.remove(valor)

Quita de la lista la primera aparición de elemento, si se encuentra. De no encontrarse


en la lista, se produce un error.

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)

Devuelve la posición de la primera aparición de valor. Si no se encuentra en la lista, se


produce un error.

sorted(secuencia)

Devuelve una lista nueva, con los elementos de la secuencia ordenados.

lista.sort()

Ordena la misma lista.

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 ‘:’

>>> punto = {'x': 2, 'y': 1, 'z': 4}


>>> punto
{'x': 2, 'y': 1, 'z': 4}

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.

materias = {} # o bien materias = dict()


materias["lunes"] = [6103, 7540]
materias["martes"] = [6201]
materias["miércoles"] = [6103, 7540]
materias["jueves"] = []
materias["viernes"] = [6201]

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}

Si asignamos un valor a una clave que ya existe, el valor anterior se pierde.

dicc['string'] = 'def'
>>> dicc
{'número': 2, 'string': 'def', 'tupla': (1, 2, 3), 'lista':
['a', 'b'], 'diccionario': {'x': 2, 'y': 3}}

Si queremos modificar el contenido de algún valor de alguna clave, lo hacemos como


con cualquier objeto mutable.

>>> dicc['string'] = dicc['string']+'hij'


>>> dicc
{'número': 2, 'string': 'defhij', '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.

for dia in materias :


print("El {} tengo que cursar {}".format(dia, materias[dia]))
...
El lunes tengo que cursar [6103, 7540]
El martes tengo que cursar [6201]
El miércoles tengo que cursar [6103, 7540]
El jueves tengo que cursar []
El viernes tengo que cursar [6201]
>>>

17
Es posible, también, obtener los valores como tuplas donde el primer elemento es la
clave y el segundo el valor.

for dia, codigos in materias.items() :


print("El {} tengo que cursar {}".format(dia, codigos))
...
El lunes tengo que cursar [6103, 7540] etc...

El algoritmo que usa Python internamente para buscar un elemento en un


diccionario es distinto al que utiliza para buscar en listas. Para buscar en las
listas, se utiliza un algoritmo de comparación que tarda más a medida que la
lista se hace más larga.
En cambio, para buscar en diccionarios se utiliza un modelo algorítmico llama-
do hashing, que se basa en asociar la clave del elemento con su lugar de memoria
mediante una ecuación matemática. Esto tiene una propiedad muy importante,
y es que sin importar cuántos elementos tenga el diccionario, el tiempo de bús-
queda de los datos asociados a una clave es siempre aproximadamente igual.
Este algoritmo de hashing es la razón por la cual las claves de los diccionarios
deben ser inmutables, ya que la operación numérica del hashing hecha sobre las
claves debe dar siempre el mismo resultado (si se utilizara un valor mutable esto
no sería posible).
Esta función de hashing y el manejo asociado a los diccionarios es en realidad
lo que utiliza Python para generar las asociaciones entre identificadores (nombres
de variables) y sus valores, y permite también fácilmente controlar los diferentes
ámbitos de referenciamiento que se genera durante una ejecución (por ejemplo
durante el llamado a funciones).

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...

Los diccionarios se combinan e interoperan con las demás estructuras secuenciales de


Python de una manera natural pero no se pueden aplicar '+' ni '*' (imaginan por qué?).

>>> cubos = {x : x**3 for x in range(7)}


>>> cubos
{0: 0, 1: 1, 2: 8, 3: 27, 4: 64, 5: 125, 6: 216}

>>> lista = ['a', 'b', 'c']


>>> dicc = {x:'hola '+x for x in lista}
>>> dicc
{'a': 'hola a', 'b': 'hola b', 'c': 'hola c'}

>>> tupla = ((11, "once"), (21, "veintiuno"), (19, "diecinueve"))


>>> dct = dict((y, x) for x, y in tupla)
>>> dct
{'once': 11, 'veintiuno': 21, 'diecinueve':19}

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.

>>> alumnos = ['Lucas', 'Florencia', 'Matías']


>>> notas = [8, 9, 7]
>>> tupla = tuple(zip(alumnos, notas))
# el objeto zip( ) no es asignable!
>>> tupla
(('Lucas', 8), ('Florencia', 9), ('Matías', 7))

>>> tupla = tuple(zip(alumnos, notas, ['a', 'b', 'c']))


# no está limitado a aridad 2
>>> tupla
(('Lucas', 8, 'a'), ('Florencia', 9, 'b'), ('Matías', 7, 'c'))
>>> diccio = dict(zip(alumnos, notas))

19
>>> diccio
{'Lucas': 8, 'Florencia': 9, 'Matías': 7}

Podemos ahora pensar en un miniproyecto: dado un texto cualquiera contar la frecuen-


cia de ocurrencia de cada una de sus palabras.

1. Leemos el texto y lo convertimos en una lista de palabras utilizando el método split


sobre el espacio.

2. Creamos un diccionario vacío (clave = palabra, valor = ocurrencias).

3. Iteramos sobre la lista:

Si el elemento de la lista no está en el diccionario, lo agregamos como clave


nueva y con ocurrencia 1.
Si el elemento de la lista está, sumamos uno a sus ocurrencias.

4. Ordenamos el diccionario y generamos una salida organizada.

texto = input('ingresar texto (" " para terminar): ')

while texto :
dicc = dict()
palabras = texto.split(' ')

for palabra in palabras :


if palabra in dicc : dicc[palabra] += 1
else: dicc[palabra] = 1

dicc = dict(sorted(dicc.items())) # función genérica


for palabra in dicc :
print(palabra, dicc[palabra])
texto = input('ingresar texto (" " para terminar): ')

print('Gracias por usar nuestros servicios')

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)

22.1. Algunos métodos y funciones con diccionarios

{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]

Accede al valor asociado con clave en el diccionario. Falla si la clave no está en el


diccionario.

clave in diccionario

Indica si un diccionario tiene o no una determinada clave.

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.

for clave in diccionario:

Permite recorrer una a una todas las claves almacenadas en el diccionario.

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)

Quita del diccionario la clave y su valor asociado, y devuelve el valor.

diccionario.popitem()

Quita del diccionario el último par clave-valor (puede variar) y devuelve el valor del
par.

diccionario.clear()

Quita todos los pares del diccionario.

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).

>>> conjunto = {2, 'abc', (1,2,3)}

Observar que el conjunto no puede contener objetos mutables (listas, diccionarios,


conjuntos) y tampoco elementos duplicados.

>>> conj = {1,2,3,4,3,2}


>>> conj
{1, 2, 3, 4}

Todas las estructuras secuenciales son “convertibles” a conjuntos utilizando la función


genérica set(.), y un conjunto es convertible a las demás estructuras secuenciales con
las funciones correspondientes. Utilizamos set() (en forma literal) para crear o denotar
conjuntos vacíos (dado que {}) representa un diccionario vacío.

>>> set([1, 2, 3, 3])


{1, 2, 3}

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

Podemos agregar elementos a un conjunto, de a uno con el método add() y de a varios


con el método update() (que recibe tuplas, listas o conjuntos, o también tuplas de tuplas,
etc.).

>>> conj.add(2) # antes de esta sentencia conj estaba vacío


>>> conj
{2}
>>> conj.update([1,2,3,4,5])
>>> conj
{1, 2, 3, 4, 5}
>>> conj.update(['a', 'b'],{(1,2),'c'})
>>> conj
{1, 2, 3, 4, 5, 'a', (1, 2), 'c', 'b'}
# notar que el orden es ‘impredecible’ pero también es irrelevante

Para quitar elementos de un conjunto tenemos los métodos discard() y remove(),


que reciben por parámetro el elemento que se desea quitar. Ambos trabajan de igual
manera, salvo que remove() devuelve un error si se desea quitar un elemento que no
se encuentra en el conjunto. Al igual que con otras estructuras compuestas, tenemos los
métodos pop() y clear().

>>> 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}

>>> for x in A&B : print(x)


...
3
4

23.1. Funciones aplicables a conjuntos

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).

len(), max(), min(), sum()

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&B # B es el (no frozen) set anterior


frozenset({3, 4})

>>> A.add(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'

24. Ordenamiento de secuencias


Ya vimos los ejemplos naturales de ordenamiento de secuencias de valores simples
(listas, tuplas, conjuntos) con la función sorted().
Eventualmente nos sirve para secuencias de tuplas, ordenando por el primer elemento.
Para poder ordenar estas secuencias, sus elementos deben poder ser comparables entre
sí a través del operador de comparación «"(tipos numéricos, strings y tuplas), siempre
entre elementos del mismo tipo de dato. Si en algún caso quisiésemos que esa comparación

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:

>>> Lista = [["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"],
["137310", "Gomez Lara, Yanella Verónica", "44321437"],
["131644", "Guerrero, Juan Ignacio", "43972078"], ... ,
["126665", "Schmit, Lucas Gabriel", "42316124"]]

Definimos entonces funciones para ordenar por legajo, nombre o dni:

>>> def legajo(L) : return L[0]


...
>>> def nombre(L) : return L[1]
...
>>> def dni(L) : return L[2]

Podemos ahora obtener los listados ordenados por el campo deseado:

>>> for x in sorted(Lista, key=legajo) : print(x)


...
['107378', 'Ortiz, Ronaldo Yair', '38093296']
['112064', 'Mayo Perez, Daniel', '40300838']
['117806', 'Aldebert, Gabriel Ivan', '41460080']
['125675', 'Castro, Facundo Nicolás', '42114637']
etc...

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...

>>> for x in sorted(Lista, key=dni) : print(x)


...
['107378', 'Ortiz, Ronaldo Yair', '38093296']
['112064', 'Mayo Perez, Daniel', '40300838']
['117806', 'Aldebert, Gabriel Ivan', '41460080']
['125675', 'Castro, Facundo Nicolás', '42114637']
['126665', 'Schmit, Lucas Gabriel', '42316124']
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.

>>> archi = open('mastermind.py', 'r')


>>> for L in archi : print(L)
...
import random

print("Bienvenidx al Mastermind!")

print("Tenés que adivinar un número de cuatro dÃgitos distintos")

(sigue la escritura del resto del archivo)

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.

UTF-8 (8-bit Unicode Transformation Format) es un encoding que utiliza


símbolos de longitud variable. Está definido como estándar de la Internet Engi-
neering Task Force (IETF). Sus características principales son:

Es capaz de representar cualquier caracter de cualquier lenguaje que sea


representable en Unicode, incluyendo los ASCII de 7 bits en forma trans-
parente.

Usa símbolos de longitud variable (de 1 a 4 bytes por caracter).

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í.

Estas características lo hacen atractivo en la codificación de correos electróni-


cos y páginas web. El IETF requiere que todos los protocolos de Internet indiquen
qué codificación utilizan para los textos y que UTF-8 sea una de las codifica-
ciones contempladas. El Internet Mail Consortium (IMC) recomienda que todos
los programas de correo electrónico sean capaces de crear y mostrar mensajes
codificados utilizando UTF-8.
Intentamos entonces avisarle al handler que utilice UTF-8 como encoding. Es entonces
importante tener en cuenta el formato del archivo a abrir (y averiguar qué otros encodings
existen) en caso de no estar trabajando con archivos ASCII “planos”. En esa llamada a
open hemos utilizado dos parámetros “por posición” y uno “por nombre de parámetro”.

>>> archi = open('mastermind.py', 'r', encoding = 'utf-8')


>>> for l in archi : print(l)
...
import random

print("Bienvenidx al Mastermind!")

print("Tenés que adivinar un número de cuatro dígitos distintos")

intentos = 0 (sigue la escritura del resto del archivo)

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:

>>> archi = open('mastermind.py', 'r', encoding = 'utf-8')


>>> archi
<_io.TextIOWrapper name='mastermind.py' mode='r' encoding='utf-8'>

Esto quiere decir que el archivo es un “objeto no legible” (como son los zip).

>>> archi = open('mastermnd.py', 'r', encoding = 'utf-8')


Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'mastermnd.py'
>>> archi
<_io.TextIOWrapper name='mastermind.py' mode='r' encoding ='utf-8'>

Es decir, si el handler falló, no cambia su estado.

>>> archi = open('mastermind.py', 'r', encoding = 'utf-8')


>>> for L in archi : print(L)
...
import random

print("Bienvenidx al Mastermind!")

print("Tenés que adivinar un número de cuatro dígitos distintos")

intentos = 0 (sigue la escritura del resto del archivo)

>>> for L in archi : print(L)


...
>>> #!!!

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 = open('ejemplo.py', 'r')


>>> for L in archi : print(L)
...
0
1
2
...
9

>>>archi.seek(3,0)
3
>>> for L in archi : print(L)
...
1
2
...
9

25.2. Archivos y estructuras secuenciales

Si queremos copiar el archivo en una estructura, tenemos varios caminos:

1. Copiarlo en una lista utilizando la asignación

>>> data = [L for L in archi] # copiará desde el cursor hasta el final

Este mecanismo es equivalente al uso del método readlines().

>>> data = archi.readlines() # copiará desde el cursor hasta el final.

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ó.

2. Copiarlo en alguna otra estructura secuencial

3. Convertirlo en un diccionario, de manera que (por ejemplo) cada línea tenga su


número como clave. Aquí nos sirve el iterador enumerate() que se asocia a una
variable y le asigna valores enteros como un contador dentro de una estructura
secuencial:

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']
>>>

>>> conj = set(archi) # con el cursor en 0


>>> conj
{'# para determinar si ganaste\n', ' while candidato in codigo:
candidato = random.choice(digitos)\n', 'while not ganaste:\n',
'ganaste = False\n', ' propuesta ... }

>>> tupli = tuple(archi) # con el cursor en 0


>>> tupli
('import random\n', 'print("Bienvenidx al Mastermind!")\n', 'print("Tenés
que adivinar un número de cuatro dígitos distintos")\n', 'intentos = 0\n',
'ganaste ... )

>>> dicci = {I+1:L for I,L in enumerate(archi)}


>>> dicci
{1: 'import random\n', 2: 'print("Bienvenidx al Mastermind!")\n', 3:
'print("Tenés que adivinar un número de cuatro dígitos distintos")\n', 4:
'intentos = 0\n', ... }

Podemos eliminar los \n al final de cada línea (indican a la consola exactamente


eso) utilizando el método .rstrip() sobre strings.

>>> dicci= {I+1:L.rstrip('\n') for I,L in enumerate(archi)}


>>> dicci
{1: 'import random', 2: 'print("Bienvenidx al Mastermind!")', 3:
'print("Tenés ... }

Podríamos haberlo hecho sobre el diccionario anterior por ‘inception’ ?


Ejemplo: Tenemos el siguiente archivo excel guardado en formato CSV.

31
25.3. Cerrar un archivo

Antes de finalizar la ejecución de un programa/script, es necesario cerrar los archivos


que se abrieron, dado que ese comando copia la información existente en los buffers asocia-
dos a los archivos originales (salvo que explícitamente se hubiesen guardado o “salvado”).
Para ello se ejecuta el método .close().

>>> 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.

El handle sigue existiendo pero no nos permite seguir operando.


Una forma más prolija es utilizar la estructura with, que nos permite generar contextos
dentro de nuestro programa/script. En este caso:

with open("mastermind.py", encoding = 'utf-8') as archi :


i = 1 #
for linea in archi :
linea = linea.rstrip("\n")
print("{}: {}".format(i, linea))
i += 1

# Cuando la ejecución sale del bloque 'with', el archivo se cierra


automáticamente.

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.

En cualquiera de los modos se puede agregar un + para pasar a un modo


lectura-escritura. El comportamiento de r+ y de w+ no es el mismo, ya que en
el primer caso se mantiene el archivo completo, y en el segundo el archivo se
vacía. Si un archivo no existe y se lo intenta abrir en modo lectura, se generará
un error; en cambio si se lo abre para escritura, se crea el archivo al momento
de abrirlo. En caso de que no se especifique el modo, los archivos serán abiertos
en modo sólo lectura.

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.

with open("saludito.py", "w") as saludo :


saludo.write("print('Hola Mundo')\n")
saludo.write("# Je! Este programa fue generado por otro programa!\n")

Podemos guardar cualquier estructura de Python en un archivo, solo que si la estructura


no es un string la tenemos que convertir a tal formato utilizando la función str() que ya
conocemos. También podemos interrogar la posición dentro del archivo en la que estamos
con el método tell().

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.

Método sobre un archivo Descripción

Cierra un archivo abierto (no hace nada si el archivo


close()
no está abierto, da error si no hay un handler).
Devuelve un entero denominado file descriptor del
fileno()
archivo.
flush() Vacía el buffer de escritura del archivo.
Lee como máximo n caracteres del archivo. Si n es
read(n)
nulo o vacío, lee hasta el final del archivo.
Lee y retorna una línea del archivo si el parámetro
readline(n=-1) no se especifica o es -1. Lee como máximo n bytes
si se especifica el parámetro.
Lee y retorna una lista de líneas hasta el final del
readlines(n=-1) archivo si el parámetro no se especifica o es -1. Lee
como máximo n bytes si se especifica el parámetro.
Cambia la posición del cursor a offset bytes, en
seek(offset, dónde) referencia a dónde (comienzo, posición actual del
cursor, final).
tell() Retorna la posición (en bytes) del cursor.
Retorna True si el buffer permite ser accedido para
writable()
escritura.
Escribe el string s en el archivo y retorna la cantidad
write(s)
de bytes escrita.
writelines(líneas) Escribe una lista líneas de líneas en el archivo.

34
26. Recursión

Una función se denomina recursiva cuando puede invocarse a sí misma durante su


ejecución. En este caso se denomina recursiva directa (existe la recursión indirecta cuando
una función invoca a otra y viceversa). La definición de funciones recursivas tiene su
sustento matemático en el principio de inducció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)

Evidentemente si la invocación recursiva fuese incondicional, entonces entraríamos en


un lazo infinito, por lo que tenemos que conocer y tener en cuenta los principios de diseño
adecuados para que una función recursiva no caiga en este riesgo:

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

Comparar (y razonar acerca de) la eficiencia de la función anterior respecto de la


siguiente:

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))

Observar que simplificar simplifica la fracción como efecto colateral.

Otro ejemplo revisitado

Recordamos cuando construimos el triángulo


de Pascal (los números combinatorios). Llama-
mos comb(i,j) al número de combinaciones de
i elementos en j lugares (i es “las columnas” y
j “las filas” en caso de escribir estos números en
forma encolumnada).

Una propiedad que observamos es que en general:


(comb(i,j)=comb(i-1,j-1)+ comb(i,j-1).
Esta propiedad (recursiva) deja de cumplirse “fuera” del triángulo (si la columna es
cero (i=0 , o si estamos “en la diagonal” (i==j), en cuyo caso el valor es uno (caso base).

def comb(i,j) :
if i==j or i==1 : return 1
else : return(comb(i-1, j-1) + comb(i, j-1))

26.1. ¿Dónde (y cómo) se mantienen las computaciones interme-


dias?

Si miramos el siguiente segmento de código y su ejecución podemos comprobar que,


pese a tener el mismo nombre, la variable (x de la función (f y la variable (x de la función
(g no tienen nada que ver una con otra. Se refieren a objetos distintos, y modificar una
no modifica a la otra.

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)

Esta sería la ejecución de g():

>>> 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

Este comportamiento lo aceptamos desde un comienzo, pero nunca nos detuvimos a


pensar por qué sucede, y ahora con las funciones recursivas tenemos que comprender
mejor cómo se ejecutan las llamadas a funciones para comprender mejor cuál es la razón
de este comportamiento.
En Python, cada contexto de referenciamiento está representado por un frame (marco),
que contiene toda la información necesaria para desambiguar los valores u objetos a los
que están referenciando cada uno de los identificadores de dicho contexto. Al comenzar la
ejecución del intérprete, el mismo tiene un frame que es el “fondo” o punto de apoyo sobre
el cual se van colocando los frames de las diferentes funciones a medida que éstas se van
llamando. El marco de cada función contiene los nombres de cada una de las variables
y parámetros, y el “lugar” para asociar a estos los valores u objetos correspondientes.
Veamos en nuestro ejemplo:

Estado inicial antes de llamar a la función g: no hay frames


ni variables declaradas

Se produce el llamado a g: se asigna el valor 1 al parámetro


x (el valor del argumento en la llamada).

Se crea una nueva variable (local a g) y se le asigna su valor.


Se ejecuta el print.

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).

Se crea una nueva variable (local a f). Esta variable tiene


un nombre igual a otra (y por lo tanto la “pisa”).
Se ejecuta el print.

f termina (y su frame desaparece!). Se asigna un nuevo valor


a x y se ejecuta el print.

Si a su vez la función f llamase a otra función h durante su ejecución, el frame de h se


depositaría por encima de los frames existentes. Es fácil ver que los frames se gestionan
como una pila: el último en ser creado se deposita encima de los demás, y cuando su
función asociada termina, es retirado. Por ello a esta estructura se la denomina “pila de
activaciones”.
También es importante ver que el nombre de la función es irrelevante en cada frame,
solo importa el orden en el cual los frames se crean y eliminan. Si una función es recursiva,
y durante su ejecución se invoca a sí misma, entonces habrán dos (o más) frames de dicha
función, que representan dos instancias distintas de su activación.
Cuidado especial hay que tener cuando se llama a una función con un argumento
mutable (ver el ejemplo de la función simplificar más arriba). En este caso, en la
función invocada no se crea un objeto nuevo asociado al valor recibido por parámetro,
sino que el parámetro pasa a ser una referencia al mismo objeto que el argumento
recibido. Por lo tanto cualquier cambio a dicho parámetro dentro de la función, modifica
el valor del argumento recibido fuera de ella (por efecto colateral).

27. Programación orientada por objetos


Los objetos son estructuras que permiten diseñar y desarrollar algoritmos y programas,
con ventajas que facilitan aún más lo hecho con las estructuras de datos y las funciones
que hemos visto hasta ahora. Como tal, la Programación Orientada por Objetos (OOP) es
un “paradigma” que involucra algunos cambios importantes respecto de nuestra forma
de programar.

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.

27.1. Definiendo nuevas clases

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"

print('Creando un objeto Punto2D con x={} y={}'.format(x,y))


self.x = x
self.y = y

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.

(El print es totalmente supérfluo, lo agregamos solamente para poder ver


cuándo se ejecuta el constructor).

>>> 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.

>>> q=Punto2D('pepe', (1,2,3))


Creando un objeto Punto2D con x=pepe y=(1, 2, 3)
>>> q.y
(1, 2, 3)

Si queremos impedir que esto suceda, debemos agregar validaciones al constructor,


Verificaremos que los valores pasados para x e y sean numéricos, utilizando una función
validar_numero.

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))

Observar que el objeto es creado aunque no se le asignen valores a sus atributos:

>>> 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.

def distancia(self, otro) :


"""Devuelve la distancia entre ambos puntos."""
dx = self.x - otro.x
dy = self.y - otro.y
return (dx * dx + dy * dy) ** 0.5

>>> 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:

def restar(self, otro) :


"""Devuelve la resta entre dos puntos."""
return Punto2D(self.x - otro.x, self.y - otro.y)

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

def distancia(self, otro) :


"""Devuelve la distancia entre ambos puntos."""
return self.restar(otro).norma()

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"

def __init__(self, nom, ed, rz):


self.nombre = nom
self.edad = ed
self.raza = rz

>>> Mascota_de_Marina = Perro('Nina',8,'Toy')`


>>> Mascota_de_Marina.edad
8
>>> Mascota_de_Marina.__class__.especie
'cánido'

Igualmente a lo que ocurre con los parámetros de las funciones, los métodos pueden
tener valores por defecto

def __init__(self, nom, ed, rz=’mestizo’):


self.nombre = nom
self.edad = ed
self.raza = rz

>>> Mascota_de_Klaus = Perro('Bobby',0.5)


>>> Mascota_de_Klaus.raza
'mestizo'

27.3. Métodos “especiales”

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.

Volviendo a nuestros Punto2D, quisiéramos sobrecargar la suma y la resta:

>>> 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'

def __add__(self, otro) :


"""Devuelve la suma de ambos puntos."""
return Punto2D(self.x + otro.x, self.y + otro.y)

def __sub__(self, otro):


"""Devuelve la resta de ambos puntos."""
return Punto2D(self.x - otro.x, self.y - otro.y)

>>> 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)

27.4. Métodos para comparar objetos

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.

def __lt__(self, otro):


"""Compara dos Puntos2D según sus normas."""
return self.norma() < otro.norma()

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:

Indica que se comienza a definir una clase con el nombre indicado.

def __init__(self, ...):

Define el método constructor de la clase. En general, dentro del constructor se estable-


cen los valores iniciales (defaults) de todos los atributos.

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

Permite obtener o modificar el valor de un atributo de la instancia.

def metodo(self, ...)

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(...)

Invoca al método metodo de la clase de la cual variable es un objeto. El primer pará-


metro que se le pasa a metodo será self.

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).

def __add__(self, otro):, def __sub__(self, otro):

Métodos especiales para sobrecargar los operadores + y - respectivamente. Reciben


las dos instancias sobre las que se debe operar, debe devolver una nueva instancia con el
resultado.

def __lt__(self, otro):

Método especial para permitir la comparación de objetos mediante sobrecarga de los


operadores < y >. Recibe las dos instancias a comparar. Debe devolver True si self es
comparativamente “menor” a otro de acuerdo a cómo se defina en la clase.

def __le__(self, otro):

Método especial para permitir la comparación de objetos mediante la sobrecarga de los


operadores <= y >=. Devuelve True si self es comparativamente “menor o igual” a otro.

def __eq__(self, otro):

Método especial para permitir la comparación de objetos mediante sobrecarga de los


operadores == y !=. Devuelve True si self y otro son comparativamente “iguales”.

28. Alteración del flujo natural del control


Si bien la gran mayoría de los problemas de programación pueden resolverse con las
estructuras de control que ya hemos visto, en algunos casos es imposible o demasiado
forzado expresar el comportamiento esperado de un programa solo con ellas. Por dicho
motivo se introducen algunos mecanismos adicionales.

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:

Flag = input(" bla bla ")


while Flag :
if Flag == "N" : hacer algo
elif Flag == "L" : hacer otra cosa
... etc.
Flag = input(" bla bla de nuevo ")

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.

while Pos < len(lista_enteros) and not Lo_Encontre :


if lista_enteros[Pos] == Buscado :
Lo_Encontre = True
else:
Pos = Pos + 1

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

while Pos < len(lista_enteros) :


if lista_enteros[Pos] == Buscado : break
else: Pos = Pos + 1

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.

28.2. Try – except – finally

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

En caso de necesitarlo, podemos atrapar el tipo de excepción generada (por ejemplo,


para devolverlo a una función llamadora y que ésta determine cómo manejar el error).

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

En otros contextos la situación es “semánticamente” inadecuada, entonces nosotros


deseamos específicamente generar una excepción (por ejemplo para que en otra parte del
código esa excepción esté “atrapada” por un try-except).

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

Con esta definición de la clase, el método constructor no se creará un triángulo si se


ejecuta el raise (se abandona la función sin terminar de ejecutarla).
De esa manera podemos decidir realizar o no la construcción del triángulo desde el
mismo método constructor y sin tener que validar los parámetros antes:

L1 = int(input('Ingresar lado uno del triángulo: '))


L2 = int(input('Ingresar lado dos del triángulo: '))
L3 = int(input('Ingresar lado tres del triángulo: '))
try:
T = Triangulo(L1,L2,L3)
except Exception N ex:
print('Ocurrió una excepción de tipo {}'. format(type(ex).__name__))

28.3. Algunas de las excepciones más comunes

exception ArithmeticError

La clase base para las excepciones predefinidas que se generan para varios errores
aritméticos: OverflowError, ZeroDivisionError, FloatingPointError.

exception BufferError

Se genera cuando en un buffer no se puede realizar una operación dada.

exception AttributeError

Se genera cuando se produce un error en una referencia de atributo o la asignación


falla. (Cuando un objeto no admite referencias de atributos o asignaciones de atributos
en absoluto, se genera TypeError)

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

Se genera cuando la instrucción import tiene problemas al intentar cargar un módulo.


También se produce cuando la from list en from ... import tiene un nombre que no
se puede encontrar.

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

Se genera cuando no se encuentra una clave de asignación (diccionario) en el conjunto


de claves existentes (mapa).

exception KeyboardInterrupt

Se genera cuando el usuario pulsa la tecla de interrupción (normalmente Control-C o


Delete). Durante la ejecución, se realiza una comprobación de interrupciones con regu-
laridad.

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

Se genera cuando no se encuentra un nombre local o global. Esto se aplica sólo a


nombres no calificados. El valor asociado es un mensaje de error que incluye el nombre
que no se pudo encontrar. El atributo name se puede establecer utilizando un argumento
de solo palabra clave para el constructor. Cuando se establece, representa el nombre de
la variable a la que se intentó acceder.

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

Se genera cuando el resultado de una operación aritmética es demasiado grande para


ser representado.

exception RecursionError

Esta excepción se eleva cuando el intérprete detecta que se excede la profundidad


máxima de recursión.

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 SyntaxError(message, details)

Se genera cuando el analizador encuentra un error de sintaxis. El atributo str() de la


instancia de excepción retorna solo el mensaje de error.

exception IndentationError

Clase base para errores de sintaxis relacionados con sangría incorrecta.

exception SystemError

Se genera cuando el intérprete encuentra un error interno, pero la situación no parece


tan grave como para abandonar la ejecución. El valor asociado es una cadena que indica
qué salió mal (a bajo nivel).

exception TypeError

Se genera cuando una operación o función se aplica a un objeto de tipo inapropiado.


El valor asociado es una cadena que proporciona detalles sobre la falta de coincidencia
de tipos.

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

Se genera cuando el segundo argumento de una operación de división o módulo es cero.


El valor asociado es una cadena que indica el tipo de operandos y la operación.

55

También podría gustarte