Introducción a Python#

Tipos de Datos#

Uno de los elementos más importantes para definir un lenguaje de programación es la definición de los tipos de datos que ese lenguaje va a utilizar. Esos tipos de datos pueden ser desde muy simples como números o caracteres hasta hasta datos mucho más complejos como gráficos, archivos y demás.

A las variables hay que darles un nombre, y este es uno de los tres problemas más complejos de la informática:

There are only two hard things in Computer Science: cache invalidation and naming things. Phil Kartlon

De acuerdo a la guía de estilo de Python, el lenguaje adopta el estilo snake_case, es decir que se identifican los datos con nombres en minúscula, separados por el guión bajo _.

Por supuesto, está prohibido usar las palabras reservadas como nombres de datos:

and del for is raise assert if else elif from lambda return break global not try class except or while continue exec import yield def finally in print

Tipos numéricos, operaciones con tipos numéricos#

Números reales y enteros#

a = 7.584 # número real

print(a)
print(type(a))

b = 8 # número entero

print(b)
print(type(b))

c = 10 + 5j

print(c)
print(type(c))
7.584
<class 'float'>
8
<class 'int'>
(10+5j)
<class 'complex'>

Cast

d = float(b) # cast a float

print(d)
print(type(d))

e = int(a) # cast a int

print(e)
print(type(e))

f = complex(a)

print(f)
print(type(f))

print(f.real)
print(f.imag)
8.0
<class 'float'>
7
<class 'int'>
(7.584+0j)
<class 'complex'>
7.584
0.0

Python realiza un cast dinámico al realizar una operación. Por ejemplo, al sumar un float y un int, el int es promovido a float antes de sumar

d = a + b

print(d, type(d))

d = a - c

print(d, type(d))
15.584 <class 'float'>
(-2.4160000000000004-5j) <class 'complex'>
f = a*b

print(a, type(a))
print(b, type(b))
print(f, type(f))
7.584 <class 'float'>
8 <class 'int'>
60.672 <class 'float'>

En la división de dos enteros (usando /), se obtiene un float. Nota: esto no ocurria en Python 2.x, donde la división de dos enteros usando /, daba un entero.

Para recuperar la división de enteros, uso //.

a = 3
b = 2

c = a/b # <---- en python 2.x, la división de enteros con / da un entero

print(c, type(c))

c2 = a//b

print(c2, type(c2))
1.5 <class 'float'>
1 <class 'int'>

Potencias:

h = 3**2

print(h, type(h))

h1 = 3.0**2

print(h1, type(h1))

h2 = 3**2.0

print(h2, type(h2))

h3 = 3.0**2.0

print(h3, type(h3))
9 <class 'int'>
9.0 <class 'float'>
9.0 <class 'float'>
9.0 <class 'float'>

Modulo:

a = 11
b = 3

c = a%b

print(c, type(c))

a = 11.42
b = 3.21

c = a%b

print(c, type(c))
2 <class 'int'>
1.79 <class 'float'>

Otras operaciones con números:

from math import *

c = sqrt(9)

print(c)

d = floor(27.25)

print(d)

f = log(1000)

print(f)

f = cos(0)

print(f)
3.0
27
6.907755278982137
1.0

Constantes:

print(pi)
print(e)
3.141592653589793
2.718281828459045
math.sqrt(-1) # <--- no
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[10], line 1
----> 1 math.sqrt(-1)

NameError: name 'math' is not defined

Existe un modulo aparte para operaciones con complejos

import cmath

cmath.sqrt(-1)
z = 3.0 + 3.0j

print(z.real)

print(z.conjugate())
import math

print(cmath.phase(z)) 

print(math.degrees(cmath.phase(z)))

print(abs(z))

print(cmath.polar(z))

Tipos lógicos (bool)#

T = True
F = False

print(F)
print(type(F))
print(T*F)
print(2*T)
print("Hola", 23)
a = 28.2

b = a

print(b)

# # print('mayor/menor a: ', a < b)
c = 99.8
a = c

print(a)
print(b)
# print('igual a: ', a == b)
# print('distinto a: ', a != b)

# print('es: ', a is b)
# print('no es: ', a is not b)

Strings#

Un string es una secuencia de caracteres de cualquier tipo (letras, números, símbolos,…) que se definen usando comillas (simple o dobles.

a = "Este es un ejemplo de un string"

print(a)
print(type(a))
a
print(a)
b = 'donde podemos usar comillas simples'
c = "o usar comillas dobles"

print(b, c)

Podemos concatenar strings usando +

d = a + b + c + "y que podemos concatenar usando el signo '+', por ejemplo" 

print(d)
f = 'También podemos usar "comillas dobles" dentro de comillas simple'
print(f)
g = "o 'al revés', como en este caso"
print(g)

Podemos agregar caracteres de formato:

h = "Siempre quise aprender\ncomo poner caracteres de formato\nen un string de Python.\n"

print(h)

j = "Supongamos que queremos hacer una lista que contenga los elementos:\n\t * elemento 1\n\t * elemento 2\n\t * and so on..."

print(j)
print('%s' % h)
print('%r' % h)

Un ejemplo de docstring:

m = '''
Acá puedo poner cuantas lineas 
quiera, sin preocuparme si son dos 
lineas, cuantro lineas, 6 lineas...
¿habrá algún límite?
'''



print(m)

Indexado (en strings)#

Un string es un ejemplo de ‘contenedor’ de elementos (en este caso, caracteres), a los cuales se puede acceder mediante el índice que indica su posición:

my_string = 'Hola mundo'

print(my_string[0])
print(my_string[4])
print(my_string[5])
print(my_string[10]) # mi índice no puede ser mayor a la cantidad de elementos

Se pueden utilizar índices negativos para acceder a los elementos, referidos al último. Es decir, el -1 es el último, el -2 es el penúltimo, y así por el estilo:

print(my_string[-1])
print(my_string[-2])
print(my_string[-10])

Slicing#

Puedo acceder a un subconjunto de elementos, utilizando una técnica conocida como “slicing”, donde, dado un string a, obtengo el subconjunto como a[inicio:fin:paso], (donde todos los argumentos son opcionales):

print(my_string[1:6])

sub_string = my_string[6:10]
print(sub_string)

print(my_string[0] + sub_string)
print(my_string[0:5])
print(my_string[:5])

print(my_string[5:10])
print(my_string[5:])
print(my_string[::2])
print(my_string[1::2])
print(my_string[::-2])

Algunos métodos de strings#

string_2 = "se viene el estallido, de mi guitarra, y tu gobierno, también"

print(string_2)

print(string_2.capitalize())

print(string_2.upper())

string_3 = string_2.upper()

print(string_3.lower())
k = string_2.find('est')

print(k)

print(string_2[k:])

N = string_2.count('i')

print(N)

N = string_2.count('ta')

print(N)
string_separado = string_2.split(',')

print(len(string_separado))

string_4 = string_2.replace(', ', '\n')

print(string_4)

string_separado_en_lineas = string_4.splitlines()

print(string_separado_en_lineas)
string_de_numeros = '123456789asfdsfds'

print(string_de_numeros.isnumeric())
print(string_de_numeros.isalpha())
print(string_de_numeros.isalnum())

Listas#

my_list = [4, 8, 15, 16, 23, 42]
print(type(my_list))

print(my_list)
<class 'list'>
[4, 8, 15, 16, 23, 42]

Las listas tambien son secuencias, por lo que el indexado y slicing funcionan como ya hemos visto con los strings

my_list[0]
4
my_list[::-1]
[42, 23, 16, 15, 8, 4]

Y pueden contener cualquier tipo de objetos:

nombre = 'Jack'

lista_heterogenea = [108, 'Dharma', my_list, nombre]
print(lista_heterogenea)
print(lista_heterogenea[2][0])
[108, 'Dharma', [4, 8, 15, 16, 23, 42], 'Jack']
4

Se pueden agregar elementos a una lista

lista_heterogenea.append('Shepard')
lista_heterogenea
[108, 323, [4, 8, 15, 16, 23, 42], 'Jack', 'Shepard', 'Shepard']

O se pueden reemplazar elementos

lista_heterogenea[1] = 323
lista_heterogenea
[108, 323, [4, 8, 15, 16, 23, 42], 'Jack', 'Shepard']

Incluso se pueden hacer asignaciones de secuencias sobres “slices”

lista_heterogenea[0:2] = ['A', 'B', 'C', 'D']     # notar que no hace falta que el valor tenga el mismo tamaño que el slice
lista_heterogenea

Otras operaciones entre listas:

print(4 in my_list)
print(4 not in my_list)

L = my_list + lista_heterogenea

print(L)

L = 3*my_list

print(L)

print(sum(my_list))

print(len(my_list))
print(min(my_list))
print(max(my_list))

print(lista_heterogenea.index('B'))

print(lista_heterogenea.count(42))
print(lista_heterogenea)
print(lista_heterogenea.count([4, 8, 15, 16, 23, 42]))
my_list = [4,8,15,16,23,42]
print(my_list)
otra_lista = my_list[::-1]
print(otra_lista)

print(my_list)

my_list.reverse()

print(my_list)
del my_list[1:3]

print(my_list)
list1 = [0,1,2]

list2 = list1

list2[0] = 8

print(list1)
print(list2)

Tuplas#

Las tuplas son iguales a las listas, con la diferencia es que las listas son mutables mientras las tuplas no.

una_tupla = ('Benjamin', 108, my_list, ('Linus', my_list[0]))
print(type(una_tupla))
print(una_tupla[1:3])
una_tupla
una_tupla.append('B')
una_tupla[-1] = 'osooo'
una_tupla.reverse()

Diccionarios#

La diccionarios son otro tipo de estructuras de alto nivel que ya vienen incorporados. A diferencia de las secuencias, los valores no están en una posición sino bajo una clave: son asociaciones clave:valor

stock = {'Manzanas': 1, 'Peras': 10, 'Bananas': 5, 'Frutillas': 'Fuera de temporada'} 

stock

Accedemos al valor a traves de un clave

stock['Frutillas'] = 20

stock
stock['Sandía'] = 1

stock
del stock['Sandía']

stock
list(stock.items())[1][0]
list(stock.items())
list(stock.keys())
stock.values()

Control de flujo#

Una de las herramientas fundamentales de la programación es poder controlar los efectos sobre el programa cuando se cumplen ciertas condiciones, o poder repetir en forma sencilla una cantidad de operaciones. Estas instrucciones se disponen en estructuras que permiten manejar el flujo de un programa, y están presentes en todos los lenguajes de programación. Las más fundamentales se llaman prácticamente igual en la mayoría de los lenguajes.

Un aspecto importante que presentan las estructuras de flujo en Python es que requieren de indentación, esto es, dejar espacios de forma tal de que la estructura pueda quedar bien definida. Otra de las discusiones eternas de la computación es cuántos espacios se utilizan para indentar. La respuesta correcta en este curso es 4.

Condicionales#

Nota = 7
if Nota >= 8:
    print ("Aprobó cómodo, felicidades!")
elif 6 <= Nota < 8:
    print ("Bueno, al menos aprobó!")
elif 4 <= Nota < 6 :
    print ("Bastante bien, pero no le alcanzó")
else:
    print("Lo esperamos después de las vacaciones!")

En un if, la conversión a tipo boolean es implícita. El tipo None (vacío), el 0, una secuencia (lista, tupla, string) (o conjunto o diccionario, que ya veremos) vacía siempre evalua a False. Cualquier otro objeto evalua a True.

respuesta = 'lala'

if respuesta:
    print('respondió!')
else:
    print('no respondió!')

Iteraciones#

Loop for

for i in [0,1,2,3]:
    print(i,i+1)
provincias_patagonicas = ["Tierra del Fuego", "Santa Cruz", "Chubut", "Neuquén", "Río Negro", "La Pampa"]

for provincia in provincias_patagonicas:
    print(provincia)
sumatoria = 0

for elemento in [1, 2, 3.6]:
    sumatoria += elemento
    
sumatoria

Iteradores#

Veremos un tipo nuevo, llamado range, que es un iterador. Se crea mediante cualquiera de los siguientes llamados:

  • range(stop)

  • range(start, stop, step)

r = range(10)

print(r)
print(type(r))
r = list(range(10))

print(r)
print(type(r))
p = list(range(2,12,2))

print(p)
p = list(range(20,10,-2))

print(p)
for elemento in range(10):
    print(elemento)
for (posicion, valor) in enumerate([4, 3, 19]):
    
    print('El valor de la posicion', posicion, 'es', valor)
temp_min = [-3.2, -2, 0, -1, 4, -5, -2, 0, 4, 0]
temp_max = [13.2, 12, 13, 7, 18, 5, 11, 14, 10 , 10]

for t1, t2 in zip(temp_min, temp_max):
  print('La temperatura mínima fue', t1, 'y la máxima fue', t2)

Control de flujo de loops#

Se puede salir del loop usando break

sumatoria = 0

for elemento in range(1000):
    
    if elemento > 100:
        break
        
    sumatoria = sumatoria + elemento
    
sumatoria, elemento
sumatoria = 0

for elemento in range(101):
    
    sumatoria += elemento
    
sumatoria, elemento

Se puede omitir un iteración usando continue

sumatoria = 0

for elemento in range(20):
    
    if elemento % 2:
        continue
        
    print(elemento, end=', ')
    sumatoria = sumatoria + elemento
    
sumatoria

Un ejemplo práctico, consiste en crear una lista a través de un loop:

cuadrados = []

for i in range(-3,15,1):
    cuadrados.append(i**2)
    
print(cuadrados)

Loop while

a = 0
while a < 10:
    print(a)
    a += 1
n = 1
while True:
    n = n + 1
    print(n, 'elefantes se balanceaban sobre la tela de una araña')
    continuar = input('Desea invitar a otro elefante?')
    if continuar.lower() == 'no':
        break

Imprimiendo con formato#

Si bien Python es bastante astuto al imprimir usando la instrucción print, se puede dar formato a aquello que uno quiere imprimir. Hay una variedad de maneras de obtener una impresión agradable. Veamos un par de casos.

Usando %#

Se utiliza el símbolo % para definir el formato de cada variable. Veamos algunos ejemplos

s = 42
print("s (decimal): %d" % s)
print("s (decimal): %10.3d" % s)
print("s (octal)  : %o" % s)
print("s (hexadecimal)  : %x" % s)
s (decimal): 42
s (decimal):        042
s (octal)  : 52
s (hexadecimal)  : 2a
from math import *

golden_ratio = (1 + sqrt(5))/2
print(golden_ratio)
print("Golden Ratio = %5.2f" % golden_ratio) # un campo de 5 caracteres, con dos decimales
print("Golden Ratio = %5.3f Golden Ratio/100 = %8.3e" % (golden_ratio,golden_ratio/100)) # un campo de 5 caracteres, con tres decimales, notación exponencial
1.618033988749895
Golden Ratio =  1.62
Golden Ratio = 1.618 Golden Ratio/100 = 1.618e-02

Usando format#

print("s (decimal): {0:d}" .format(s))
print("s (decimal): {0:10d}".format(s))
print("s (octal)  : {0:o}".format(s))
print("s (hexadecimal)  : {0:x}".format(s))
s (decimal): 42
s (decimal):         42
s (octal)  : 52
s (hexadecimal)  : 2a
print(golden_ratio)
print("Golden Ratio = {0:5.2f}" .format(golden_ratio)) # un campo de 5 caracteres, con dos decimales
print("Golden Ratio = {0:5.3f} Golden Ratio/100 = {1:8.3e}" .format(golden_ratio,golden_ratio/100)) # un campo de 5 caracteres, con tres decimales, notación exponencial
1.618033988749895
Golden Ratio =  1.62
Golden Ratio = 1.618 Golden Ratio/100 = 1.618e-02

El uso de format también se llama posicional

print("Golden Ratio = {1:5.3f} Golden Ratio/100 = {0:8.3e}" .format(golden_ratio,golden_ratio/100)) # un campo de 5 caracteres, con tres decimales, notación exponencial
# un campo de 5 caracteres, con tres decimales, notación exponencial
Golden Ratio = 0.016 Golden Ratio/100 = 1.618e+00