Introducción a la programación en lenguaje Python#
Imagen creada por Andreas Martini. Tomada del sitio Python für alle.
En esta clase usted aprenderá las características lexicográficas, sintácticas y semánticas básicas del lenguaje Python.
Sitio oficial del curso Computación científica en Python.
¿Qué es Python?#
Python, la «nueva ola en la computación científica». Esta es la afirmación plasmada en el artículo «Why Python Is the Next Wave in Earth Sciences Computing publicado en el volumen 100 No. 1 del boletín de la American Meteorological Society.
<< Entonces, ¿por qué tanto alboroto por Python? Tal vez haya escuchado acerca de Python por parte de un compañero de trabajo,
escuchó una referencia a este lenguaje de programación en una presentación en una conferencia,
o siguió un enlace desde una página sobre computación científica, pero se preguntará qué beneficios adicionales ofrece
el lenguaje Python dado el conjunto de potentes funciones computacionales.
Herramientas que ya tienen las ciencias de la tierra. Este artículo expondrá el caso de que Python
es la próxima ola en la computación de ciencias de la Tierra por una sencilla razón:
Python permite a los usuarios hacer más y mejor ciencia. Veremos las características del
lenguaje y los beneficios de esas características.
Este artículo describirá cómo estas características proporcionan habilidades en computación científica
que actualmente tienen menos probabilidades de estar disponibles con las herramientas existentes, y destacarán
el creciente apoyo para Python en las ciencias de la Tierra,...>>
Python es un lenguaje que tiene un diseño y soporte por parte de la comunidad científica tal que, se ajusta muy bien a las necesidades de desarrollo de soluciones computacionales.
En el artículo «10 Reasons Python Rocks for Research (And a Few Reasons it Doesn’t)» podrán encontrar algunas ventajas y desventajas que tiene este lenguaje para este fin. Incluso, la revista Nature dedicó uno de sus artículos al uso de Python en la ciencia titulado Interactive notebooks: Sharing the code.
Está claro que un solo lenguaje de programación por si solo no puede resolver todos los problemas, pero uno de los puntos positivos que tiene Python es su capacidad de trabajar de forma cooperativa con otros lenguajes. O sea, no es un lenguaje cerrado sino integrador. Este es el espíritu que nos motiva para el curso: introducirnos en el uso de esta herramienta de manera que todos podamos en conjunto hacer más ciencia y mejor.
Lenguaje compilado vs interpretado#
Proceso de compilación de un lenguaje compilado (C, C++, Fortran):
Al compilar un lenguaje el compilador recibe una secuencia de líneas de código de forma íntegra usualmente en un fichero a través del IDE de programación utilizado.
El proceso de compilación puede ser descrito en los siguientes pasos sucesivos, en este caso para un lenguaje dependiente del contexto:
Análisis lexicográfico: Permite analizar el código línea por línea aislando los conjuntos de caracteres que tengan algún sentido para el lenguaje definido. Estos pueden ser números, etiquetas, palabras clave, signos de creación de ambiente etc.
Análisis sintáctico: Cada lenguaje tiene definida una gramática. Esto se refiere a una serie de reglas que deben seguirse al escribir los números, etiquetas, palabras clave, etc. En este punto el compilador analiza si lo que está escrito lexicográficamente correcto también hace un uso correcto de dicha gramática.
Análisis semántico: En este caso se asume que el código está bien escrito de acuerdo al lenguaje por lo que el próximo paso es analizar si además tiene sentido. Este es el caso en el que se verifican los tipos de los objetos definidos, no inicializaciones de variables, etc.
Generación del código de máquina: Llegado a este punto el programa escrito es un programa válido del lenguaje. Como implicación se traduce cada una de las partes del código (lenguaje de alto nivel) a un lenguaje que la computadora puede entender con instrucciones precisas directamente especificadas para la arquitectura de hardware de la misma. Usualmente este lenguaje de bajo nivel es llamado «Ensamblador».
Proceso de compilación de lenguaje interpretado:
En le caso de un lenguaje interpretado el intérprete hace los análisis y generación de código antes mencionados en tiempo real línea de código por línea de código.
Python es un lenguaje interpretado, pero en lugar de compilar el código directamente a código de máquina lo hace a un lenguaje intermedio llamado bytecode. Por ejemplo, la siguiente instrucción
a = b.c()
es transformada a una cadena de bytes que cuando es desensamblada se escribe de la siguiente forma:
load 0 (b);
load_str 'c';
get_attr;
call_function 0;
store 1 (a);
Pudiera parece que el lenguaje bytecode es más ineficiente dado que requiere más código, pero de hecho es mucho más eficiente. La clave está en que es mucho más simple y fácil de entender por la máquina virtual de Python.
El hecho de utilizar un lenguaje intermedio y una máquina virtual hace que el lenguaje Python sea multi-plataforma siguiendo un concepto parecido al de lenguajes como Java y C#.
Si le da curiosidad saber cómo se ven los códigos de Python en bytecode puede usted usar la biblioteca estándar de Python implementada en el módulo dis.
Lenguaje estáticamente versus dinámicamente tipado#
Un lenguaje estáticamente tipado se distingue porque el tipo de todas las variables es conocido en tiempo de ejecución. Lo usual en este tipo de lenguajes es que el programador tenga que especificar claramente cuál es el tipo correspondiente a cada variable en el código. Como ejemplo pudiéramos mencionar lenguajes como Java, C, C++, C#, Fortran, etc.
Existen otros lenguajes que también son estáticamente tipados, pero que el propio compilador aplica una inferencia de tipo con la cual es capaz de deducir el tipo de las variables. Por ejemplo, OCaml, Haskell, Scala, Kotlin, etc.
En el caso de los lenguajes dinámicamente tipados los tipos son asociados con valores en tiempo de corrida (tiempo real) y no con variables o campos. Esto implica que es posible que una variable tome diferentes tipos a lo largo de la ejecución del programa de forma dinámica. Ejemplos de estos lenguajes son Perl, Ruby, PHP, JavaScript, Python, MATLAB, Octave, etc.
Casi todos los lenguajes basados en scripts son dinámicos. Esto también tiene el inconveniente de que no es posible en la mayoría de los casos para el intérprete detectar errores en el código hasta que el mismo es ejecutado.
Léxico, sintaxis y semántica#
Números, booleanos y cadenas.
Asignación de valores a variables.
Operaciones aritméticas.
Condicioneales.
Indentación del lenguaje.
Estructuras de control
Los tipos de datos de Python#
Python como todo lenguaje de programación consta con tipos de datos para representar la información. Aunque es un lenguaje fuertemente tipado hace uso de un tipado dinámico.
Que sea fuertemente tipado implica que casi todo lo que está implementado en Python posee un tipo de dato o de objeto asignado.
En el caso de tipado dinámico se refiere a que las variables no poseen un tipo estático, sino que el mismo puede cambiar de acuerdo a los intereses del programador.
Estas características hacen que Python sea relativamente fácil de usar y flexible.
Los tipos de datos de Python más importantes son:
Tipo de dato |
Descripción |
|---|---|
|
Representa cadenas de caracteres (string). |
|
Representa a un número entero (integer). |
|
Representa un número de punto flotante. |
|
Representa un número complejo. |
|
Representa un valor booleano verdadero o falso. |
|
Representa un valor nulo. |
Una cadena de caracteres se define usando ' ' o " ".
Aunque no es posible mezclarlos.
import math
# Display latex inline
from IPython.display import Latex, display
"Hola!"
'Hola!'
"Hola"
'Hola'
>>> "Hola'
File "<ipython-input-3-269476aa49fa>", line 1
"Hola'
^
SyntaxError: EOL while scanning string literal
Existen ambas formas para poder expresar cadenas como la siguiente
print('Hola en inglés se escribe "Hello"!')
Hola en inglés se escribe "Hello"!
En el caso de los números enteros y de punto flotante se tienen que
int
int
float
float
print("Entero", 2, int)
print("Flotante", 1e-45, float)
Entero 2 <class 'int'>
Flotante 1e-45 <class 'float'>
El caso de los números complejos será tratado en la clase #2, pero como adelanto:
print(2 + 4j, type(2 + 4j))
(2+4j) <class 'complex'>
El valor booleano es muy simple:
True
True
print(False, bool)
False <class 'bool'>
Por último se tiene el valor nulo
None
print(None, type(None))
None <class 'NoneType'>
Variables y asignación de valores#
Las variables se crean nombrándolas con una etiqueta y asignándole un valor.
var = <valor>
Como es un lenguaje dinámico entonces no es necesario declarar la variable de un tipo determinado aunque esto no significa que no tenga tipo. De hecho, siempre se encuentra tipada.
a = "texto"
type(a)
str
a = 2
type(a)
int
b = a
print(b, type(b))
2 <class 'int'>
También se puede hacer una multi-asignación de valores a diferentes variables como sigue
c, d = 2, 3
print(c)
print(d)
2
3
Expresiones aritméticas y booleanas#
A partir de la mezcla de variables y constantes es posible crear expresiones aritméticas o booleanas. Veamos algunos ejemplos
a = 2 + 2
print(a)
4
b = 20 / a
print(b)
5.0
El operador % permite obtener el resto de la división del primer operando entre el segundo.
print(3 % 3)
print(4 % 3)
print(5 % 3)
print(6 % 3)
0
1
2
0
También se pueden combinar. En el siguiente ejemplo se verifica si la expresión aritmética del miembro izquierdo es divisible entre 0.
Destacar el operador == el cual establece una comparación booleana entre dos operandos.
23 + 7 * 2 % 2 == 0
False
Otros operadores lógicos y de comparación son
Operador |
Descripción |
|---|---|
|
Realiza la operación lógica y que resulta positiva solo en el caso en que ambos operandos sean positivos. |
|
Realiza la operación lógica o que resulta negativa solo si ambos operandos son negativos. |
|
Invierte el valor de verdad de la expresión. |
|
Resulta positivo si los dos operandos que compara son diferentes. |
|
Operador «mayor que». |
|
Operador «mayor igual que». |
|
Operador «menor que». |
|
Operador «menor igual que». |
|
Operador de igualdad. |
Indentación de espacios en Python#
En lenguajes como C++ o C el código es ordenado de forma que los ambientes (scopes en inglés)
son en su mayoría establecidos por las definiciones de partes del lenguaje (features en inglés) como
funciones y clases, y delimitadores como {}.
En el lenguaje Python también se utilizan las definiciones de funciones aunque hace uso de los espacios de forma particular. Los ambientes de código se encuentra cuidadosamente indentados de forma jerárquica y anidada. De esta manera el código queda estrictamente ordenado y estéticamente «lindo». Normalmente el compilador reconoce 4 espacios o 1 tab como la creación del nuevo ambiente.
Veamos un ejemplo:
>>> 1 print('1 espacio inicial => ERROR de sintáxis!')
File "<ipython-input-21-28de08d8f4fe>", line 1
1 print('1 espacio inicial => ERROR de sintáxist!')
^
SyntaxError: invalid syntax
Por otra parte, no es posible crear ambientes vacíos (sin código efectivo contenido en ellos)
print("4 Espacios al inicio => Nuevo ambiente vacío => ERROR de sintáxis!")
File "a.py", line 1
print('4 Espacios al inicio => Nuevo ambiente vacío => ERROR de sintáxis!')
^
IndentationError: unexpected indent
¿Cuándo utilizar la indentación?
Precisamente las estructuras de control definidas en el lenguaje crean nuevos ambientes para los cuales se utilizará la indentación.
Estructuras de control#
Las estructuras de control permiten controlar el flujo de ejecución del código. Las más utilizadas son las estructuras condicionales, los bucles y los métodos, además de las clases, pero este contenido no será tratado en este curso.
Estructuras condicionales#
Para establecer una condición en lenguaje Python se utiliza la siguiente sintaxis:
if <expresión booleana 1>:
....<código 1>
elif <expresión booleana 2>:
....<código 2>
else:
....<código 3>
En el caso en que la expresión booleana #1 sea verdadera entonces se ejecuta el bloque de código #1 y luego continúa el flujo del programa.
En caso contrario, entonces se evalúa la expresión booleana #2.
De ser verdadera entonces se ejecuta el bloque de código #2.
En caso de no ser verdaderas las dos primeras condiciones entonces se ejecuta el código #3.
Veamos un ejemplo:
Sea la función \(|x| = \begin{cases} x ,& x ≡ 0 (3) \\ x^2 ,& x \equiv 1 (3) \\ x^3 ,& x \equiv 2 (3) \end{cases}\)
entonces podría representarse en código de la siguiente forma
x = 54
if x % 3 == 0:
result = x
elif x % 3 == 1:
result = x**2
else:
result = x**3
print(result)
54
Una manera abreviada de representar una condición es en forma de operador ternario:
<variable 1> = <expresión 1> if <expresión booleana> else <expresión 2>
En este caso, la asignación del valor a la variable #1 está condicionada por la expresión booleana.
Si es verdadera entonces se asignará el valor resultante de la expresión #1 y de lo contrario el valor resultante de la expresión #2.
x = 3.5
x_bound = x if 0 <= x <= 5 else 0.0
print(x_bound)
3.5
Estructuras iterativas#
Las estructuras iterativas que están definidas en el lenguaje Python son los bucles while y el for.
El bucle while tiene la siguiente sintaxis:
while <expresión booleana>:
....<código>
En este caso se repite el bloque de código interno siempre que la expresión booleana sea verdadera.
Es importante asegurarse de que en algún momento el ciclo terminará ya sea a causa de que la expresión booleana sea falsa o que imperativamente se ordene hacerlo desde dentro del bloque de código.
De no hacerlo seguirá repitiendo el bloque de código de forma infinita o hasta que agote la memoria.
Veamos en el siguiente ejemplo cómo utilizar este tipo de bucle para calcular el MCD de dos números
m = 32
n = 12
d = min(m, n)
while m % d != 0 or n % d != 0:
d -= 1 # Atención con esta instrucción!
print(d)
4
El bucle for tiene la siguiente sintaxis:
for <e> in <iterador>:
....<código>
Es importante destacar que esta estructura a pesar de que se parece mucho a su análoga deinida para lenguajes como C++ no está implementada de la misma manera. Precisamente, tiene exactamente la misma funcionalidad que la estructura de control foreach del lenguage C#.
En efecto, la estructura está diseñada para iterar por los elementos de un iterador y ejecutar el bloque de código para la iteración correspondiente al elemento e.
Un iterador no es más que un patrón de diseño «Lazy» el cual implementa un patrón productor-consumidor de elementos de una secuencia (explícita o implícita). Por ejemplo:
for e in 1, 2, 3, 4:
print(e)
1
2
3
4
En este ejemplo se asigna a la variable e uno de los elementos de la secuencia
dada del \(1\) al \(4\), y se ejecuta un código con él.
Quizás el más utilizado de los iteradores es el implementado por la función range
que genera los números en un intervalo determinado \(\left[a,b\right)\).
for k in range(1, 11):
print(k)
1
2
3
4
5
6
7
8
9
10
Esta es una de las razones por las que los ciclos de este tipo en Python son tan costosos computacionalmente hablando y deben de ser evitados siempre que sea posible.
También existen instrucciones para controlar el flujo de las iteraciones:
break: Se utiliza para terminar las iteraciones y redirigir el flujo de ejecución hacia el ambiente «padre» o más próximo que contiene al bucle.continue: Se utiliza para ignorar las restantes líneas de código del bloque decódigoque se está ejecutando en la actual iteración y continuar con la ejecución del código de la próxima iteración.
Declaración de métodos y expresiones lambda#
Un método es una estructura de control que permite encapsular un bloque de código de manera que pueda ser reutilizado. Para ello define una etiqueta para unívocamente identificarlo, parámetros de entrada y un resultado de salida si existen.
Una función es aquel método que siempre devuelve un resultado.
def suma(a, b):
return a + b
En el ejemplo anterior se ha definido una función etiquetada con el nombre suma.
Para ello se usa la palabra reservada del lenguaje def que es una abreviatura de «definition».
Seguidamente de la etiqueta se declaran los parámetros de entrada entre paréntesis y separados por coma,
en este caso a y b.
Para devolver un resultado se utiliza la palabra reservada return y seguidamente la expresión de retorno.
Es importante destacar que el método no tiene necesariamente que retornar un valor.
def imprimir_10_veces(s):
for k in range(10):
print(s)
imprimir_10_veces("Hola")
print(suma(2, 3))
Hola
Hola
Hola
Hola
Hola
Hola
Hola
Hola
Hola
Hola
5
Como se aprecia en el ejemplo anterior los métodos crean su propio ambiente de ejecución a través de la indentación.
Por otra parte, cuando se desea ejecutar el método simplemente se utiliza la etiqueta y se le suministran los parámetros de entrada correspondientes entre paréntesis separados por coma.
Las expresiones lambda permiten definir funciones de forma compacta en una linea o «inline» de forma muy similar a como se hace en MATLAB.
m = lambda x, y: math.sqrt(x**2 + y**2)
display(Latex(r"\sqrt{x^2 + y^2} = %d" % (m(2, 3),)))
Listas, tuplas, conjuntos y diccionarios#
¿Cuál es la diferencia entre las estructuras de datos Lista, Tupla, Conjunto y Diccionarios?
Las listas son estructuras de datos que permiten almacenar elementos y realizar ciertas operaciones sobre ellos que serán descritas debajo. Los elementos en las listas pueden ser modificados, de ahí que se les clasifique como estructuras mutables.
Las tuplas son estructuras de datos que permiten almacenar elementos, pero una vez creadas no pueden ser modificadas, de ahí que se les clasifique como estructuras inmutables.
Los conjuntos son estructuras de datos que representan conjuntos de elementos implementando las operaciones clásicas entre conjuntos. Estas estructuras son mutables.
Los diccionarios son estructuras de datos que permiten relacionar pares de llave y valor correspondiente de una forma tal que es posible obtener nuevamente el valor correspondiente a una llave rápidamente. La implementación hecha para Python es un tipo de Tabla de Hash.
Listas#
Las listas son implementadas por la clase list.
Para construir un objeto list se utiliza el siguiente constructor:
l = list()
Pero como Python es un lenguaje rico en azúcar sintáctica entonces se puede abreviar de la siguiente forma:
l = []
Los [] simbolizan la lista en el lenguaje.
Veamos cuál es el tipo de una lista
type(l)
list
Una lista puede ser puede creada e inicializar la lista al mismo tiempo:
l = [1, 2, 3, 4]
print("l =", l)
l = [1, 2, 3, 4]
Los elementos de una lista pueden ser de cualquier tipo de datos.
l = ["a", 1, True, 30.56, 1e-40]
print("l =", l)
l = ['a', 1, True, 30.56, 1e-40]
Para conocer la cantidad de elementos de una lista se utilizan la función len (proveniente de length en inglés).
Una lista puede ser concatenada con otra lista. Para ello la clase list implementa el operador +.
a = [1, 2, 3, 4]
b = [5, 6, 7, 8]
c = a + b
print(c)
[1, 2, 3, 4, 5, 6, 7, 8]
Otro operador a considerar es la multiplicación de una lista por un número natural [...] * n. El resultado del mismo es repetir los elementos de la lista tantas veces como estipule el otro operando.
a * 2 # Repitiendo la lista a dos veces
[1, 2, 3, 4, 1, 2, 3, 4]
Por supuesto que una lista puede contener otras listas como sus elementos y así de forma recurrente.
a = [1, ["a", "b"], 3]
print(a)
[1, ['a', 'b'], 3]
¿Cómo acceder a los elementos de una lista?
La clase lista implementa el operador de indexación de elementos para poder obtener
un elemento de la lista a través de su índice.
El mismo operador [] es utilizado de la siguiente manera:
print(a[0])
print(a[1])
print(a[2])
1
['a', 'b']
3
Los índices de las listas comienzan en el 0.
Particularmente existe otra forma de indexación brindada por la azúcar sintáctica de Python que resulta muy útil y conveniente. Nos referimos a la indexación inversa.
La indexación inversa permite indexar los elementos de una lista comenzando desde
el último hasta el primero, usando números negativos.
-1 es el índice del último elemento, -2 el del penúltimo elemento y así sigue
descendiendo hasta el primero.
Veamos un ejemplo:
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print("a =", a)
print("a[-1] =", a[-1])
print("a[-1] =", a[-2])
print("a[-1] =", a[-3])
print("...")
print("a[-1] =", a[-10])
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a[-1] = 9
a[-1] = 8
a[-1] = 7
...
a[-1] = 0
Existen formas de seccionar grupos de elementos de una lista usando sus índices y el operador :.
La selección devuelve una nueva lista con los elementos pertenecientes al intervalo
\(\left[a,b\right)\) donde \(a\) y \(b\) son índices.
print("Elementos del 2 al 4")
print(a[2:5])
print("Elementos hasta el 5")
print(a[:6])
print("Elementos después del 2")
print(a[2:])
print("Elementos en índices pares")
print(a[::2])
print("Tres últimos elementos")
print(a[-3:])
print("Todos los elementos menos el último")
print(a[:-1])
print("Elementos en reversa")
print(a[::-1])
Elementos del 2 al 4
[2, 3, 4]
Elementos hasta el 5
[0, 1, 2, 3, 4, 5]
Elementos después del 2
[2, 3, 4, 5, 6, 7, 8, 9]
Elementos en índices pares
[0, 2, 4, 6, 8]
Tres últimos elementos
[7, 8, 9]
Todos los elementos menos el último
[0, 1, 2, 3, 4, 5, 6, 7, 8]
Elementos en reversa
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Una lista implementa también operaciones como
Operación |
Descripción |
|---|---|
|
Inserta un nuevo elemento después del último. |
|
Elimina un elemento de una posición determinada. Por defecto elimina el último. |
|
Devuelve el número de repeticiones de un elemento en una lista. |
|
Devuelve una nueva instancia de la lista. Recordar que las listas son objetos por lo que son manejados por referencia. En este sentido es muy útil poder crear copias de los valores de la lista en una nueva y no de su referencia. |
|
Elimina todos los elementos de una lista. |
|
Devuelve el índice del primero de los elementos de la lista que sea igual al dado. |
|
Inserta un nuevo elemento en una posición de la lista desplazando hacia detrás el que se encontraba en esa posición. |
|
Elimina la primera ocurrencia de un elemento dado. |
|
Revierte el orden de los elementos de la lista (equivalente a |
|
Ordena los elementos de una lista ascendente o descendentemente de a cuerdo a un criterio de orden dado. Por defecto el orden se lleva a cabo ascendentemente y la función usada para dar orden a los elementos es la definida por defecto en el tipo de dato del elemento. |
Veamos algunos ejemplos del uso de estas operaciones.
a = [1, 5, 2, 3, 5, 7, 4, 3, 4, 6, 7, 6, 3, 2, 2]
Eliminar la primera aparición del número \(7\) de la lista.
Agregar el número \(9\) al final.
Insertar en la posición \(10\) el número \(1\).
Eliminar el elemento de la posición \(3\).
¿Cuál es la posición del primer \(6\)?
Ordenar los elementos de la lista de forma ascendente de acuerdo a su cercanía al número \(7\).
a.remove(7)
print(a)
[1, 5, 2, 3, 5, 4, 3, 4, 6, 7, 6, 3, 2, 2]
a.append(9)
print(a)
[1, 5, 2, 3, 5, 4, 3, 4, 6, 7, 6, 3, 2, 2, 9]
a.insert(10, 1)
print(a)
[1, 5, 2, 3, 5, 4, 3, 4, 6, 7, 1, 6, 3, 2, 2, 9]
a.pop(3)
print(a)
[1, 5, 2, 5, 4, 3, 4, 6, 7, 1, 6, 3, 2, 2, 9]
print(a.index(6))
7
b = a.copy() # no es necesario, solo para ilustrar
# Calcular la distancia entre el número 7 y el elemento
# (Solo para números enteros).
def compare(e):
return abs(7 - e) # abs devuelve el valor absoluto de la expresión matemática.
b.sort(key=compare)
print(b)
[7, 6, 6, 5, 5, 9, 4, 4, 3, 3, 2, 2, 2, 1, 1]
Para comprobar si un elemento pertenece a una lista se usa el operador in.
7 in a
True
Las listas también es posible declararlas de forma implícita usando comprensión de listas. Esto también forma parte del azúcar sintáctica del lenguaje. Veamos un ejemplo:
a = [x**2 for x in range(1, 11)]
print(a)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
También se pueden adicionar condicionales
a = [x for x in range(1, 50) if x % 5 == 0]
print(a)
[5, 10, 15, 20, 25, 30, 35, 40, 45]
Tupas#
Las tuplas se crean creando una instancia de la clase Tuple del lenguaje de la siguiente forma.
a = tuple()
Haciendo uso del azúcar sintáctica:
a = ("azul", "verde", "rojo", "blanco")
La misma es de tipo
type(a)
tuple
Se utilizan normalmente para guardar información que no va a variar en el tiempo.
Por tal motivo son bastante sencillas, solo implementan las funciones index y count,
las cuales se usan exactamente de la misma forma que para las listas.
Conjuntos#
Los conjuntos están implementados en la clase set del lenguaje.
Las operaciones entre conjuntos que implementan son las usuales:
unión, intersección y diferencia, además de las propias del lenguaje.
Para crear un conjunto se utiliza el constructor de la clase set
s = set()
Como en el caso de las lista y tuplas, también los conjuntos son creados a partir de azúcar sintáctica:
s = {1, 2, 3, 4}
Y es posible saber cuántos elementos tiene.
len(s)
4
Saber si un elemento pertenece al conjunto.
3 in s
True
Operaciones entre conjuntos
r = {2, 4, 5, 6}
print("Unión entre s y r", s.union(r))
Unión entre s y r {1, 2, 3, 4, 5, 6}
print("Intersección entre s y r", s.intersection(r))
Intersección entre s y r {2, 4}
print("Diferencia entre s y r", s.difference(r))
Diferencia entre s y r {1, 3}
Los conjuntos pueden aceptar nuevos elementos o eliminar elementos contenidos.
s.add(5)
print(s)
{1, 2, 3, 4, 5}
print("Unión entre s y r", s.union(r))
Unión entre s y r {1, 2, 3, 4, 5, 6}
q = {8, 9, 10}
print("Intersección entre s y q", s.intersection(q))
Intersección entre s y q set()
print("¿Son conjuntos disjuntos s y r?\n", s.isdisjoint(r))
¿Son conjuntos disjuntos s y r?
False
print("¿Son conjuntos disjuntos s y q\n", s.isdisjoint(q))
¿Son conjuntos disjuntos s y q
True
Diccionarios#
Para crear un objeto de tipo diccionario se utiliza la clase dict.
d = dict()
Entre los parámetros del constructor de la clase se puede pasar una secuencia (Enumerador, iterador, lista, …) de pares.
d = dict([("a", 1), ("b", 2), ("c", 3)])
print(d)
{'a': 1, 'b': 2, 'c': 3}
Pero también pudiera ser inicializado usando el azúcar sintáctica del lenguaje:
a = {"a": 1, "b": 2, "c": 3}
print(a)
{'a': 1, 'b': 2, 'c': 3}
Para acceder a uno de sus elementos se utiliza la llave asociada al mismo y el operador [].
a["b"]
2
También se utiliza la llave para modificar los elementos.
a["c"] = 4
print(a)
{'a': 1, 'b': 2, 'c': 4}
Si se desea insertar un valor nuevo pudiera hacerse de forma directa usando la nueva llave y el mismo operador
a["f"] = 5
print(a)
{'a': 1, 'b': 2, 'c': 4, 'f': 5}
Para la eliminación se utiliza la operación del la cual libera intencionalmente el espacio en memoria que ocupaban los valores.
del a["c"]
print(a)
{'a': 1, 'b': 2, 'f': 5}
Se pueden obtener las llaves del diccionario en una lista en el mismo orden en que fueron insertadas:
b = {"c": 3, "a": 1, "b": 2}
list(b)
['c', 'a', 'b']
También se pudieran ordenar
sorted(b)
['a', 'b', 'c']
Para actualizar los valores de las llaves de un diccionario, utilice update.
Esta operación me resulta particularmente útil si se utiliza el diccionario como
estructura de dato para el manejo de parámetros de un modelo matemático.
El modelo pudiera tener una definición por defecto de parámetros y permitir una
actualización de los mismos de forma opcional en el momento de la corrida de la simulación.
print(b)
b.update({"c": 20, "d": 30})
print(b)
{'c': 3, 'a': 1, 'b': 2}
{'c': 20, 'a': 1, 'b': 2, 'd': 30}
Para obtener una vista de las llave-valor contenidas en un diccionario se utiliza:
b.items()
dict_items([('c', 20), ('a', 1), ('b', 2), ('d', 30)])
¿Cómo iterar por los elementos de un diccionario?
for k, v in b.items():
print(k, "-->", v)
c --> 20
a --> 1
b --> 2
d --> 30
También los diccionarios pueden ser generados utilizando comprensión de diccionarios
d = {n: n**2 for n in range(5)}
print(d)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
Para una mayor comprensión de las estructuras de datos del lenguaje Python puede usted visitar el sitio Data Structures de la documentación oficial de Python.
Ejercicios de consolidación#
Hacer juntos los ejercicios de nivelación en Python.
print("Hola mundo")
Hola mundo
Ejercicio #1: Es bisiesto
Un año es bisiesto si es divisible entre \(4\), pero no divisible entre \(100\) a excepción que sea divisible entre \(400\).
Implemente el siguiente método:
if 400 % 400 == 0:
print("Es divisible por 400")
Es divisible por 400
def es_bisiesto(a):
return a % 400 == 0 or a % 4 == 0 and a % 100 != 100
print("2000 es bisiesto?", es_bisiesto(2000))
print("2100 es bisiesto?", es_bisiesto(2100))
print("1988 es bisiesto?", es_bisiesto(1988))
print("2022 es bisiesto?", es_bisiesto(2022))
2000 es bisiesto? True
2100 es bisiesto? True
1988 es bisiesto? True
2022 es bisiesto? False
Ejercicios de ciclos#
Ejercicio #2: Imprimir los primeros 10 números enteros
Imprima en la consola los primeros diéz números enteros.
# Implementación:
for k in range(1, 11):
print(k)
1
2
3
4
5
6
7
8
9
10
Ejercicio #3: Patrón creciente
Escriba un código que imprima en la consola el siguiente patrón creciente:
1
1 2
1 2 3
1 2 3 4
. .
. .
. .
1 ...................N
# Implementación:
for m in range(1, 11):
for k in range(1, m + 1):
print(k, end=" ")
print("\n")
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6
1 2 3 4 5 6 7
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10
Ejercicio #4: Suma Implemente un método que calcule la suma de la serie numérica \(S_{N}=\sum\limits_{i=1}^{N}i=\frac{N\left(N+1\right)}{2}\).
def suma(N):
s = 0.0
for k in range(1, N + 1):
s = s + k
return s
print("S_2 =", suma(2), " == ", (2 * 3) / 2)
print("S_2 =", suma(3), " == ", (3 * 4) / 2)
print("S_2 =", suma(4), " == ", (4 * 5) / 2)
print("S_2 =", suma(5), " == ", (5 * 6) / 2)
S_2 = 3.0 == 3.0
S_2 = 6.0 == 6.0
S_2 = 10.0 == 10.0
S_2 = 15.0 == 15.0
Ejercicio #5: Tabla de multiplicación del número \(n\)
Imprima la tabla de multiplicación correspondiente al número \(n\). Por ejemplo:
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
2 x 4 = 8
2 x 5 = 10
2 x 6 = 12
2 x 7 = 14
2 x 8 = 16
2 x 9 = 18
2 x 10 = 20
# Implementación:
for k in range(1, 11):
print(f"2 x {k} = {2 * k}")
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
2 x 4 = 8
2 x 5 = 10
2 x 6 = 12
2 x 7 = 14
2 x 8 = 16
2 x 9 = 18
2 x 10 = 20
Trabajo con estructuras de datos#
Ejercicio #6: Imprime la lista
Dada la lista: l=['1', 2, 'Hola', 4, True],
Imprima sus elementos en la consola uno debajo del otro.
# Implementación:
l = ["1", 2, "Hola", 4, True]
print(f"Lista es: {l}.")
print(f"El tamaño de la lista es: {len(l)}")
# print(l[-1])
for k in range(0, 5):
print(l[k])
Lista es: ['1', 2, 'Hola', 4, True].
El tamaño de la lista es: 5
1
2
Hola
4
True
Ejercicio #7: Suma elementos de la lista
Dada una lista de elementos numérios implemente el siguiente método que devuelva la suma de estos:
def suma_lista(l):
s = 0.0
for k in range(len(l)):
s += l[k]
return s
l = [1, 2, 3, 4, 5]
suma_lista(l)
15.0
Ejercicio #8: Suma y multiplica
Implemente un método que sume si el índice del elemento de la lista es par o multiplique en el caso que sea impar.
def suma_multiplica(l):
s = 0.0
for k in range(len(l)):
if l[k] % 2 == 0:
s = s + l[k]
else:
s = s * l[k]
return s
print("La suma-multiplicación=", suma_multiplica([1, 2, 3, 4, 5, 6]))
La suma-multiplicación= 56.0
Ejercicio #9: Es Palíndromo
Una cadena de caracteres es un palíndromo si constituye la misma palabra al ser leída desde adelante hacia atrás y viceversa. Un ejemplo son las palabras AMA y SOMOS.
Escriba un método que detecte si una palabra es palíndromo o no.
s = "SOMOS"
print(s, s[0], s[-1], s[1], s[-2])
SOMOS S S O O
def es_palindromo(s):
for i in range(int(len(s) / 2 + 1)):
if s[i] != s[-(i + 1)]:
return False
return True
print("Es palíndromo SOMOS: ", es_palindromo("SOMOS"))
print("Es palíndromo AMA: ", es_palindromo("AMA"))
print("Es palíndromo OLLA: ", es_palindromo("OLLA"))
print("Es palíndromo ALLA: ", es_palindromo("ALLA"))
print("Es palíndromo SEVERLAALREVES: ", es_palindromo("SEVERLAALREVES"))
Es palíndromo SOMOS: True
Es palíndromo AMA: True
Es palíndromo OLLA: False
Es palíndromo ALLA: True
Es palíndromo SEVERLAALREVES: True
Ejercicio #10: Es palíndromo recursivo
Implemente una solución como la anterior pero sin hacer uso de ciclos.
def es_palindromo_rec(s):
if len(s) <= 1:
return True
if s[0] != s[-1]:
return False
return es_palindromo_rec(s[1:-1])
print("Es palíndromo SOMOS: ", es_palindromo("SOMOS"))
print("Es palíndromo AMA: ", es_palindromo("AMA"))
print("Es palíndromo OLLA: ", es_palindromo("OLLA"))
print("Es palíndromo ALLA: ", es_palindromo("ALLA"))
print("Es palíndromo SEVERLAALREVES: ", es_palindromo("SEVERLAALREVES"))
Es palíndromo SOMOS: True
Es palíndromo AMA: True
Es palíndromo OLLA: False
Es palíndromo ALLA: True
Es palíndromo SEVERLAALREVES: True
Ejercicio #11: Es número primo
Implemente un método que permita determinar si un número es primo. Un número \(b\) es primo sii solo es divisible por \(1\) o \(b\).
def EsPrimo(b):
if b == 1:
return False
if b == 2:
return True
for d in range(2, int(math.sqrt(b)) + 1):
if b % d == 0:
return False
return True
for b in range(1, 30):
print(f"Es primo {b}?", EsPrimo(b))
Es primo 1? False
Es primo 2? True
Es primo 3? True
Es primo 4? False
Es primo 5? True
Es primo 6? False
Es primo 7? True
Es primo 8? False
Es primo 9? False
Es primo 10? False
Es primo 11? True
Es primo 12? False
Es primo 13? True
Es primo 14? False
Es primo 15? False
Es primo 16? False
Es primo 17? True
Es primo 18? False
Es primo 19? True
Es primo 20? False
Es primo 21? False
Es primo 22? False
Es primo 23? True
Es primo 24? False
Es primo 25? False
Es primo 26? False
Es primo 27? False
Es primo 28? False
Es primo 29? True
Ejercicio #12: Secuencia de Fibonacci
La secuencia de Fibonacci está definida por los números de la siguiente manera:
\(f\left(n\right)=f\left(n-1\right)+f\left(n-2\right)\) teniendo como casos base \(f\left(0\right)=f\left(1\right)=1\).
Implemente la secuencia usando ciclos y sin su uso.
def fibonacci(n):
if n == 0 or n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(1))
print(fibonacci(2))
print(fibonacci(3))
print(fibonacci(4))
print(fibonacci(5))
print(fibonacci(6))
1
2
3
5
8
13
Ejercicio #13: Conjetura de Collatz
Esta conjetura comienza con un número natural. Si este es par entonces se divide por \(2\), de otra forma se multiplica por \(3\) y se le suma \(1\). Seguir de forma recursiva hasta llegar al \(1\).
Implemente un método sin usar ciclos que verifique la conjetura de Collatz dado un valor \(n\in\mathbb{N}\).
def conjetura_collatz(n):
if n == 1:
return
if n % 2 == 0:
conjetura_collatz(int(n / 2))
return conjetura_collatz(n * 3 + 1)
conjetura_collatz(10)
Ejercicio #14: Revertir número
Implemente un algoritmo que invierta los dígitos de un número entero dado (ignorar ceros a la izquierda). Por ejemplo, \(1053\) se convierte en \(3501\).
def revertir_numero(b):
m = 0
n = b
while True:
if n < 10:
break
else:
r = n % 10
n = int(n / 10)
m = 10 * m + r
return 10 * m + n
print("10478353 <> ", revertir_numero(10478353))
print("3487634000 <> ", revertir_numero(3487634000))
10478353 <> 35387401
3487634000 <> 4367843
Ejemplo #15: Contar ocurrencias de un número entero en una lista
Dada una lista de números enteros cuente la cantidad de elementos iguales al dado.
def ocurrencias(l, c):
cont = 0
for i in range(len(l)):
if l[i] == c:
cont += 1
return cont
l = [1, 2, 4, 6, 3, 4, 3, 2, 4, 5, 5, 4, 43, 2, 2, 2, 4]
print(ocurrencias(l, 2))
print(ocurrencias(l, 3))
print(ocurrencias(l, 4))
5
2
5
Ejercicio #16: Has pares
Dadas dos listas l1 y l2 elabore una lista l3 donde sus elementos sean de la siguiente forma:
l3 = [(l1_1, l2_1), (l1_2, l2_2), ... , (l1_i, l2_i), ... , (l1_n, l2_n)]
l1 = [1, 2, 3, 4]
l2 = ["a", "b", "c", "d"]
l3 = [(l1[i], l2[i]) for i in range(len(l1))]
print("Utilizando la compresión de listas =", l3)
print(
"Utilizando la función zip =", [*zip(l1, l2)]
) # Importancia del * para iterar sobre el objeto zip (enumerador)
print("Sin utilizar * =", [zip(l1, l2)])
Utilizando la compresión de listas = [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
Utilizando la función zip = [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
Sin utilizar * = [<zip object at 0x7f0d1c480880>]
Ejercicio #17: Calculador de Pila
Implemente un pequeño compilador de pila para un lenguaje libre del contexto que dada una secuencia de números y operaciones devuelve su resultado. Por simplicidad asuma que la secuencia dada siempre es correcta.
Un compilador de pila se basa lógicamente en una estructura de dato llamada pila (primer elemento que entra a la pila es el último que sale). La idea del funcionamiento es la siguiente:
Los operandos son insertados en la pila
Los operadores también son insertados en la pila
Antes de realizar la inserción en la pila se comprueba si es posible ejecutar alguna de las operaciones. De ser posible, se ejecuta y el resultado vuelve a ser insertado en la pila.
El programa termina cuando se hayan agotado los elementos de la secuencia y la pila esté vacía.
El símbolo de
$significa el final de la secuencia (solo con el propósito de simplificar).
Por ejemplo:
Sea la secuencia s=[1, '+', 2, '*', 3, '$'] y la pila p = [].
Entonces los pasos serían como sigue:
Insertar
1en la pila.Insertar
+en la pila.Insertar
2en la pila.Insertar
*en la pila.Insertar
3en la pila.
Como el símbolo que queda en s es $ entonces todas las operaciones y operandos ya fueron ejecutadas las que queden están en la pila por lo que:
6. Extraer 3, * y 2 de la pila y ejecutar la operación. El resultado colocarlo de vuelta en la pila. p=[1, '+', 6].
7. Extraer 6, + y 1 y efectuar la suma. Colocar el resultado en la pila. p=[7].
8. FIN.
Ejemplos de resultados:
s=[1, '+', 2, '*', 3, '$'] => 7s=[1, '+', 2, '*', 3, '+', 5, '$'] => 12s=[1, '+', 2, '*', 3, '*', 5, '$'] => 31s=[3, '*', 4, '+', 3, '$'] => 15s=[1, '+', 2, '*', 3, '+', 4, '*', 5, '$'] => 27
lista = [1, 2]
print(lista)
lista.append(3)
print(lista)
[1, 2]
[1, 2, 3]
s = [1, "+", 2, "*", 3, "$"]
print(s)
s.pop()
print(s)
s.pop()
print(s)
s.pop()
print(s)
s.pop()
s.pop()
s.pop()
print(s)
[1, '+', 2, '*', 3, '$']
[1, '+', 2, '*', 3]
[1, '+', 2, '*']
[1, '+', 2]
[]
def compilar(s):
# Inicialización de la pila
p = []
# Índice de la secuencia
i = 0
# Ciclo principal para recorrer la secuencia.
# Termina cuando encuentra el caracter final.
while s[i] != "$":
if s[i] == "+":
# El operador + es el de menor prioridad por lo que siempre puede
# ejecutar su antecesor en la pila. Esto es porque si es un * entonces
# tiene más prioridad que él y se debe calcular primero. En el caso que
# sea + entonces tendría la misma prioridad y también puede ser calculado.
# Al final se inserta en el tope de la pila restante.
if len(p) >= 3:
op2 = p.pop() # sacar del tope de la pila el operando 2
o = p.pop() # sacar del tope de la pila el operador (* o +)
op1 = p.pop() # sacar del tope de la pila el operando 1
p.append(o(op1, op2)) # Operar y colocar el resultado en el tope
p.append(lambda a, b: a + b) # adicionar al tope el operador +
elif s[i] == "*":
# El operador * es el de mayor prioridad por lo que siempre tiene que
# colocarse en la pila a esperar que el próximo operando en la secuencia
# sea puesto en la pila y luego ser ejecutado.
p.append(lambda a, b: a * b)
else: # Los números siempre son insertados como operandos en la pila
p.append(s[i])
i += 1
# Ciclo secundario para ejecutar lo que quedó en la pila.
# Lo que queda está en orden de operaciones correcto por lo que simplemente
# se extraen, se evaluan y se coloca nuevamente el resultado hasta
# que quede un solo valor en la pila.
while len(p) > 1:
op2 = p.pop()
o = p.pop()
op1 = p.pop()
p.append(o(op1, op2))
# Imprimir el resultado final del cálculo.
return p[0]
# Comprobando resultados
s = [1, "+", 2, "*", 3, "$"]
p = []
i = 0
while s[i] != "$":
if len(p) >= 3:
op2 = p.pop()
o = p.pop()
op1 = p.pop()
p
i = i + 1
# Secuencia
s1 = [1, "+", 2, "*", 3, "$"]
s2 = [1, "+", 2, "*", 3, "+", 5, "$"]
s3 = [1, "+", 2, "*", 3, "*", 5, "$"]
s4 = [3, "*", 4, "+", 3, "$"]
s5 = [1, "+", 2, "*", 3, "+", 4, "*", 5, "$"]
print("Los resultados obtenidos son:")
print("[1, '+', 2, '*', 3, '$'] =", compilar(s1))
print("[1, '+', 2, '*', 3, '+', 5, '$'] =", compilar(s2))
print("[1, '+', 2, '*', 3, '*', 5, '$'] =", compilar(s3))
print("[3, '*', 4, '+', 3, '$'] =", compilar(s4))
print("[1, '+', 2, '*', 3, '+', 4, '*', 5, '$'] =", compilar(s5))
print("¿Son iguales todos a los dados en el ejemplo?")
Los resultados obtenidos son:
[1, '+', 2, '*', 3, '$'] = 7
[1, '+', 2, '*', 3, '+', 5, '$'] = 12
[1, '+', 2, '*', 3, '*', 5, '$'] = 31
[3, '*', 4, '+', 3, '$'] = 15
[1, '+', 2, '*', 3, '+', 4, '*', 5, '$'] = 27
¿Son iguales todos a los dados en el ejemplo?