.pyc
que es lo que realmente se ejecuta)El hola mundo es tan sencillo como escribir una línea:
print('Hola Mundo')
Hola Mundo
Python cuenta con las siguientes características destacables referentes a las variables:
Por convención las variables se nombran en minúsculas y con guiones bajos para separar palabras.
variable_zero = 0
Existen 3 tipos básicos de variables:
int
(sin límite de tamaño)float
. P.ej. 0.26, 0.1e-3,…complex
. P.ej. 5+7jstr
bytes
bytearray
list
tuple
range
bool
: True, FalseTambién hay otros tipos predefinidos:
dict
)# A continuación unos ejemplos. Ah! esto es un comentario :)
i = 2
i2 = 0xf
f = 2.339
c = 1+2j
# Para obtener el tipo de una variable cualquiera siempre podemos recurrir a type()
print(i, type(i))
print(i2, type(i2))
print(f, type(f))
print(c, type(c))
2 <class 'int'> 15 <class 'int'> 2.339 <class 'float'> (1+2j) <class 'complex'>
Operaciones con tipos numéricos
print('Suma: 1+1 =', 1+1)
print('Resta: 1-1 =', 1-1)
print('Multiplicación: 2*2 =', 2*2)
print("División: 5/2 =", 5/2) # En v2 el resultado no es el esperado
print("División: 4.0/2 =", 4.0/2) # El resultado es siempre un float si dividendo o divisor son float
print("División entera: 5//2 =", 5//2)
print("Módulo o resto entero: 5%2 =", 5%2)
print('Potencia: 2**3 =', 2**3) # Ojo, el _^_ es un XOR a nivel de bit
print("Valor absoluto: abs(-1) =", abs(-1))
Suma: 1+1 = 2 Resta: 1-1 = 0 Multiplicación: 2*2 = 4 División: 5/2 = 2.5 División: 4.0/2 = 2.0 División entera: 5//2 = 2 Módulo o resto entero: 5%2 = 1 Potencia: 2**3 = 8 Valor absoluto: abs(-1) = 1
import math
print("Truncado de decimales: math.trunc(f) =", math.trunc(f))
print("Redondeo con n decimales: round(f, 2) =", round(f, 2))
print("Redondeo a número entero por abajo: math.floor(f) =", math.floor(f))
print("Redondeo a número entero por arriba: math.ceil(f) =", math.ceil(f))
Truncado de decimales: math.trunc(f) = 2 Redondeo con n decimales: round(f, 2) = 2.34 Redondeo a número entero por abajo: math.floor(f) = 2 Redondeo a número entero por arriba: math.ceil(f) = 3
print("Comparación 1 < 2 <= 2 =", 1 < 2 <= 2)
Comparación 1 < 2 <= 2 = True
print("AND a nivel de bit: 5&1 =", 5&1)
print("AND a nivel de bit: 5|2 =", 5|2)
print("XOR a nivel de bit: 5^1 =", 5^1)
print("NOT a nivel de bit: ~1 =", ~1)
print("Desplazamiento dcho: 5>>1 =", 5>>1)
print("Desplazamiento izdo: 2<<1 =", 2<<1)
AND a nivel de bit: 5&1 = 1 AND a nivel de bit: 5|2 = 7 XOR a nivel de bit: 5^1 = 4 NOT a nivel de bit: ~1 = -2 Desplazamiento dcho: 5>>1 = 2 Desplazamiento izdo: 2<<1 = 4
Las cadenas de caracteres son un tipo de secuencia con texto definido entre comillas simples o dobles. Se pueden incluir caracteres especiales escapándolos con \
. Se trata de objetos inmutables e iterables.
# Distintos tipos de cadenas de caracteres
s = "string"
unicode = u"äóè"
raw = r"\n"
multiline = """primera linea
y ésto se verá en otra"""
print(s, type(s))
print(unicode, type(unicode))
print(raw, type(raw))
print(multiline, type(multiline))
string <class 'str'> äóè <class 'str'> \n <class 'str'> primera linea y ésto se verá en otra <class 'str'>
Operaciones con cadenas de caracteres
print("Longitud: len(s) =", len(s))
print("Concatenación: s + raw =", s + raw) # mejor evitarlo
print("Repetición: raw*3 =", raw*3)
print("Búsqueda: 'tt' not in s =", "tt" not in s)
print("Elemento en posición i: s[2] =", s[2]) # los strings pueden ser tratados como listas
print("Elemento en posición -i: s[-1] =", s[-1])
print("Subcadena: s[0:2] =", s[0:2])
print("Subcadena con salto: s[0:6:2] =", s[0:6:2])
Longitud: len(s) = 6 Concatenación: s + raw = string\n Repetición: raw*3 = \n\n\n Búsqueda: 'tt' not in s = True Elemento en posición i: s[2] = r Elemento en posición -i: s[-1] = g Subcadena: s[0:2] = st Subcadena con salto: s[0:6:2] = srn
print("Primera aparición de subcadena:", s.index("ing"))
print("Número de apariciones de subcadena:", s.count("ing"))
print("Elemento mínimo: min(s) =", min(s))
print("Elemento máximo: max(s) =", max(s))
Primera aparición de subcadena: 3 Número de apariciones de subcadena: 1 Elemento mínimo: min(s) = g Elemento máximo: max(s) = t
print("Cadena en minúsculas:", s.lower())
print("Cadena en mayúsculas:", s.upper())
print("La cadena empieza por s?:", s.startswith(s))
print("La cadena acaba por s?:", s.endswith(s))
print("La cadena contiene la subcadena 'raw' en la posición:", s.find('raw'))
Cadena en minúsculas: string Cadena en mayúsculas: STRING La cadena empieza por s?: True La cadena acaba por s?: True La cadena contiene la subcadena 'raw' en la posición: -1
print("Cadena reemplazando s por sss:", s.replace('s', 'sss'))
print("Cadena dividida con split:", s.split('r'))
print("Cadena re-unida con join:", '-'.join(s))
print("Lista convertida en cadena con join:", ' '.join(['a', 'b', 'c']))
print("Cadena sin espacios en los extremos:", ' espacios everywhere '.strip())
Cadena reemplazando s por sss: ssstring Cadena dividida con split: ['st', 'ing'] Cadena re-unida con join: s-t-r-i-n-g Lista convertida en cadena con join: a b c Cadena sin espacios en los extremos: espacios everywhere
Formateo de cadenas de caracteres
print("{} no es {}".format('aquí', 'allí'))
print("{0} no es {1}, sólo es {0}".format('aquí', 'allí'))
print("{aqui} no es {alli}; es {aqui}".format(aqui='Móstoles', alli='Madrid'))
aquí no es allí aquí no es allí, sólo es aquí Móstoles no es Madrid; es Móstoles
Formateo mejorado usando f-strings, con evaluación de expresiones (>= Py3.6):
aqui = 'Móstoles'
f'Me encanta {aqui.upper()} desde {1980+1}'
'Me encanta MÓSTOLES desde 1981'
True
, False
bool
es subclase del tipo int
and
, or
, not
(textuales)==
, !=
, <
, >
, <=
, >=
b = True
print(b, type(b))
True <class 'bool'>
Operaciones con booleanos
print("not True:", not True)
print("True and not True or False:", True and not True or False)
print("True != False:", True != False)
not True: False True and not True or False: False True != False: True
print("True + True:", True + True)
print("2 == True:", 2 == True)
True + True: 2 2 == True: False
Se considera False
lo siguiente...
print(bool(None)) # None (pasado a booleano) equivale a False
print(0 == True) # El 0 de cualquier tipo numérico equivale a False
print(bool("")) # La cadena vacía equivale a False
print(bool({})) # El diccionario vacío equivale a False
print(bool([])) # La secuencia vacía equivale a False
False False False False False
Ojo con esto:
print(bool(-2))
True
La constante None
equivale a null en otros lenguajes
print(None, type(None))
print(0 is None) # Para comparar con None se usa is en lugar de ==
None <class 'NoneType'> False
Asignación de valores múltiple:
x, y = 4, 5
Intercambio de valores sencillo:
x, y = y, x
print(x, y)
5 4
Conversión entre tipos explícita:
print('float(2) =', float(2))
print('int(2.5) =', int(2.5))
print('2*str(2) =', 2*str(2))
float(2) = 2.0 int(2.5) = 2 2*str(2) = 22
Existen muchos tipos de colecciones en Python (por ejemplo las cadenas de caracteres son secuencias, que a su vez son colecciones). Las colecciones más usadas para albergar datos de cualquier tipo son:
Una lista (objeto list
) es una colección de elementos mutable, ordenada e iterable; el equivalente a arrays o vectores en otros lenguajes. Puede contener varios tipos de datos distintos a la vez, o incluso listas anidadas.
Creación:
l = [9, True, "texto", [1, 2]] # se pueden crear con corchetes o con la función list()
print("l =", l)
l = [9, True, 'texto', [1, 2]]
Para acceder a sus elementos usaremos los típicos corchetes. Como particularidades:
[inicio:fin]
o [inicio:fin:salto]
. Si se omite el inicio o el fin se cogerá desde/hasta el extremo. También se puede usar esto para modificar una parte de la lista.NOTA: La selección y el slicing aplica también al resto de cadenas en Python; tuplas y cadenas de caracteres.
print("l[0] =", l[0])
print("l[-1][-2] =", l[-1][-2])
print("l[:2] =", l[:2])
print("l[::2] =", l[::2])
print("l[::-1] =", l[::-1]) # inversa
l[0] = 9 l[-1][-2] = 1 l[:2] = [9, True] l[::2] = [9, 'texto'] l[::-1] = [[1, 2], 'texto', True, 9]
También podemos crear listas con range
. En Python 3 usamos list(range())
para obtener una lista porque range es un generador (en Python 2 no lo era).
print(list(range(5))) # rango de 0 a 5 (5 no incluido)
print(list(range(1,5))) # rango de 1 a 5 (5 no incluido)
print(list(range(1,5,3))) # rango de 1 a 5, cogiendo 1 de cada 3 (5 no incluido)
[0, 1, 2, 3, 4] [1, 2, 3, 4] [1, 4]
Manipulaciones más comunes sobre listas (recordemos que son mutables):
l = [9, True, 'texto', [1, 2]]
print('Lista:', l)
Lista: [9, True, 'texto', [1, 2]]
# Reemplazamos elementos
l[0:3] = [0, 1, 2]
print('Lista después de reemplazos:', l)
print('Elementos de la lista:', *l) # Con *<lista> obtenemos los elementos
Lista después de reemplazos: [0, 1, 2, [1, 2]] Elementos de la lista: 0 1 2 [1, 2]
# Añadimos elementos al final
l.append(123)
l.append(456)
l.append(789)
print('Lista después de appends:', l)
Lista después de appends: [0, 1, 2, [1, 2], 123, 456, 789]
# Añadimos un elemento en una posición concreta desplazando el resto
l.insert(0, 111)
print('Lista después de insert:', l)
Lista después de insert: [111, 0, 1, 2, [1, 2], 123, 456, 789]
# Eliminamos el último elemento y el primero.
print('l.pop() =>', l.pop())
print('l.pop(0) =>', l.pop(0))
print('Lista después de los pop():', l)
l.pop() => 789 l.pop(0) => 111 Lista después de los pop(): [0, 1, 2, [1, 2], 123, 456]
# También podemos eliminar elemento con del (admite l[:])
del l[len(l)-1]
print('Lista después de del:', l)
Lista después de del: [0, 1, 2, [1, 2], 123]
# Eliminamos la primera aparición de un elemento concreto
l.remove(123)
print('Lista después de remove:', l)
Lista después de remove: [0, 1, 2, [1, 2]]
# Podemos concatenar listas
print('Resultado de concatenación:', l + [9, 8])
print('Lista l después de concatenación:', l)
Resultado de concatenación: [0, 1, 2, [1, 2], 9, 8] Lista l después de concatenación: [0, 1, 2, [1, 2]]
# O extender una lista con otra (se modifica la original). Es más rápido que concatenar
l.extend(l)
print('Lista l después de extend consigo misma:', l)
Lista l después de extend consigo misma: [0, 1, 2, [1, 2], 0, 1, 2, [1, 2]]
# Comprobamos existencia de un elemento
print(' 0 in l?:', 0 in l)
print(' Count(0):', l.count(0))
print(' index(1):', l.index(1))
# Longitud de la lista
print(' len(l):', len(l))
0 in l?: True Count(0): 2 index(1): 1 len(l): 8
Podemos ordenar listas de cadenas o numéricas, usando list.sort()
(ordena el original) o sorted(list)
(devuelve copia ordenada)
NOTA: sort()
es un poco más rápido y consume menos memoria; aunque sólo vale para listas, mientras que sorted()
acepta cualquier iterable.
l_str = ['ab', 'aa']
l_str.sort()
print('Lista de cadenas ordenada: ', l_str)
l_num = [2, 3, 1]
print('Lista numérica ordenada: ', sorted(l_num))
Lista de cadenas ordenada: ['aa', 'ab'] Lista numérica ordenada: [1, 2, 3]
Las tuplas (objetos tuple
) son exactamente como las listas pero inmutables; una vez creadas no se pueden modificar. Proporcionan mayor eficiencia para usos más básicos.
Para definirlas usaremos paréntesis (o nada) en lugar de corchetes. Para tuplas de 1 elemento es necesario poner una coma al final, para diferenciarlo de un elemento básico. Todo lo explicado para lectura de listas es también aplicable para tuplas; para acceder a sus elementos usaremos también corchetes.
t1 = (1, 2, True, "python")
print(t1)
(1, 2, True, 'python')
Podemos definir tuplas con 1 sólo elemento de dos maneras:
t2 = (1, )
t3 = 1,
print(t2, type(t2))
print(t3, type(t3))
(1,) <class 'tuple'> (1,) <class 'tuple'>
Podemos extraer o desempaquetar los valores de la tupla a variables con una asignación múltiple:
t1a, t1b, t1c, t1d = t1
print(t1a, t1b, t1c, t1d)
print(*t1) # desempaquetado automático como argumento de una función
1 2 True python 1 2 True python
t1a, *t1b, t1c = t1
print(t1a, t1b, t1d)
1 [2, True] python
Existe una versión ampliada de las tuplas: los objetos namedtuple
. Nos permiten asignar nombres a los elementos, para su posterior acceso. Se pueden usar como clases sencillas inmutables:
from collections import namedtuple
Persona = namedtuple('Persona', ['name', 'age', 'height'])
persona = Persona('Andrew', 45, 174)
persona.name
'Andrew'
Los diccionarios (dict
) son colecciones mutables y sin orden de tipo mapeado que relacionan claves y valores; lo que se conoce en otros lenguajes como mapa. Se declara con llaves y una sucesión de pares clave : valor. La clave puede ser de cualquier tipo inmutable (incluidas las tuplas) pero única; mientras que el valor puede ser de cualquier tipo.
A los valores se accede por medio de las claves. En este caso no se puede hacer slicing.
# Creación:
d = {"Love Actually": "Richard Curtis", "Kill Bill": "Tarantino"}
# Introducción / Modificación de elemento:
d["Kill Bill"] = "Quentin Tarantino"
print('type(d)', type(d))
# Recorremos todos los elementos e imprimimos los pares clave-valor con formato
for movie, director in d.items():
print('Movie "{}" from director: {}'.format(movie, director))
type(d) <class 'dict'> Movie "Love Actually" from director: Richard Curtis Movie "Kill Bill" from director: Quentin Tarantino
# Obtención de claves, valores e items
print("Keys:", list(d.keys()))
print("Values:", list(d.values()))
print("Items:", list(d.items()))
Keys: ['Love Actually', 'Kill Bill'] Values: ['Richard Curtis', 'Quentin Tarantino'] Items: [('Love Actually', 'Richard Curtis'), ('Kill Bill', 'Quentin Tarantino')]
# Comprobación de la existencia de una clave
print("Love Actually in d =", "Love Actually" in d)
Love Actually in d = True
# Obtener el valor perteneciente a una clave. EVITAR! si no existe la clave tendremos un KeyError
print("d['Love Actually'] =", d["Love Actually"])
# Obtener el valor perteneciente a una clave evitando excepción si no se encuentra.
print('d.get("Love Actual") = ', d.get("Love Actual"))
# Permite definir valor por defecto para ese caso
print('d.get("Love Actual", "No encontrado") = ', d.get("Love Actual", "No encontrado"))
d['Love Actually'] = Richard Curtis d.get("Love Actual") = None d.get("Love Actual", "No encontrado") = No encontrado
# Introducción de un nuevo elemento, sólo si la clave no existía con anterioridad
d.setdefault("Pulp Fiction", "Quentin Tarantino")
print('d.get("Pulp Fiction") after first setdefault() = ', d.get("Pulp Fiction"))
d.setdefault("Pulp Fiction", "Quentin Tarantinoak")
print('d.get("Pulp Fiction") after second setdefault() = ', d.get("Pulp Fiction"))
d.get("Pulp Fiction") after first setdefault() = Quentin Tarantino d.get("Pulp Fiction") after second setdefault() = Quentin Tarantino
# Eliminar una entrada
del d["Pulp Fiction"]
print('d.get("Pulp Fiction") after del = ', d.get("Pulp Fiction"))
d.get("Pulp Fiction") after del = None
d = {1:10, 2:13, 3:12}
print(d)
# Obtener el elemento de un diccionario con max key
print('max(d) =', max(d))
# Obtener el elemento de un diccionario con max value
print('max(d, key=d.get) =', max(d, key=d.get))
{1: 10, 2: 13, 3: 12} max(d) = 3 max(d, key=d.get) = 2
Los conjuntos (set
) son exactamente como las listas pero sin orden. Se crean con el método set()
o usando llaves en lugar de corchetes.
Tienen operaciones propias de los conjuntos que conocemos del mundo real: intersección, unión, diferencia, ...
# Dos formas de crearlos
set1 = {1, 1, 2, 3, "abc"}
set2 = set([0, 1, 2])
print('Set 1:', set1, type(set1))
print('Set 2:', set2, type(set2))
# Ojo! con llaves y vacío sería un diccionario, no un conjunto
dict = {}
print('Dict:', dict, type(dict))
Set 1: {1, 2, 3, 'abc'} <class 'set'> Set 2: {0, 1, 2} <class 'set'> Dict: {} <class 'dict'>
# Añadir elementos
set1.add(4)
set1.add(4) # sin error!
print('Set 1 después de add:', set1)
# Borrar elementos
set1.remove(4)
print('Set 1 después de remove:', set1)
# Borrar elementos sólo si existen (recomendado)
set1.discard(4)
print('Set 1 después de discard:', set1)
Set 1 después de add: {1, 2, 3, 4, 'abc'} Set 1 después de remove: {1, 2, 3, 'abc'} Set 1 después de discard: {1, 2, 3, 'abc'}
# Intersección de conjuntos con &
print("set1 & set2:", set1 & set2)
# Unión con |
print("set1 | set2:", set1 | set2)
# Diferencia con -
print("set1 - set2:", set1 - set2)
print("set2 - set1:", set2 - set1)
# Diferencia simétrica con ^
print("set1 ^ set2:", set1 ^ set2)
set1 & set2: {1, 2} set1 | set2: {0, 1, 2, 3, 'abc'} set1 - set2: {3, 'abc'} set2 - set1: {0} set1 ^ set2: {0, 3, 'abc'}
Por un lado tenemos las sentencias condicionales, que se reducen a dos (el switch en Python se puede emular con un diccionario). Por otro lado están los bucles.
La forma más simple de crear un condicional es con un if
seguido de la condición a evaluar, dos puntos (:
) y en la siguiente línea e indentado, el código a ejecutar en caso de que se cumpla dicha condición.
numero = 1
print("{0}:".format(numero))
if numero < 0:
print("Negativo")
elif numero > 0:
print("Positivo")
else:
print("Cero")
1: Positivo
Es el equivalente al operador ternario ? en otros lenguajes.
var = "par" if (numero % 2 == 0) else "impar"
print(var)
impar
Porción de código que se ejecuta mientras se cumpla una condición.
La instrucción break
nos sirve para salir del bucle. La instrucción continue
nos llevará a la siguiente ejecución del bucle.
edad = 15
while edad < 18:
edad += 1
print("Felicidades, tienes " + str(edad))
Felicidades, tienes 16 Felicidades, tienes 17 Felicidades, tienes 18
Para iterar sobre una secuencia (cadenas, iterables como range(), d.keys(), iterators, etc). Lo bueno es que itera sobre los elementos, no sobre las posiciones.
# Iterando sobre una secuencia
secuencia = ['uno', 'dos', 'tres']
for elemento in secuencia:
print('Elemento {} leído'.format(elemento))
Elemento uno leído Elemento dos leído Elemento tres leído
# Iterando sobre un iterable
for elemento in d.keys():
print(elemento)
type(d.keys())
1 2 3
dict_keys
Podemos iterar con enumerate
para acceder a la vez al elemento y al índice:
for i, elemento in enumerate(secuencia):
print('Elemento {} leído: {}'.format(i, elemento))
Elemento 0 leído: uno Elemento 1 leído: dos Elemento 2 leído: tres
Un iterador es un iterable sobre el que se puede aplicar la función __next__()
para obtener el siguiente elemento, guardando el estado.
# El iterador se crea a partir de un iterable
iterator = iter(d.keys())
print(type(iterator))
<class 'dict_keyiterator'>
# Podemos acceder al siguiente elemento con iterator.__next__() o next(iterator)
print(iterator.__next__())
print(next(iterator))
1 2
# O podemos usarlo directamente en un bucle for, porque un iterador es un iterable
for elemento in iterator:
print("Elemento en for: ", elemento)
Elemento en for: 3
OJO: obtendremos una excepción si intentamos obtener el siguiente elemento y no hay más.
Para manipular los elementos de una secuencia con operaciones como puede ser el borrado, recorreremos una copia del original, de tal forma que al borrar un elemento del original seguirá estando en la copia y el bucle no se saltará nada. Para la copia real podemos hacer copia = secuencia[:]
o copia = <collection>(secuencia)
secuencia = [1, 2, 3]
print(secuencia)
for elemento in secuencia[:]:
secuencia.remove(elemento) # borrado sobre el original
print(secuencia)
[1, 2, 3] [2, 3] [3] []
¿Qué ocurre si no trabajamos con una copia? al borrar el 1º, el 2º pasa a ser el 1º, por lo que no procesamos el 2º
secuencia = [1, 2, 3]
print(secuencia)
for elemento in secuencia:
secuencia.remove(elemento)
print(secuencia)
[1, 2, 3] [2, 3] [2]
En python se usa una construcción try
-except
-else
-finally
para capturar y tratar las excepciones.
# Ejemplo:
try:
num = int("3a")
print("Hecho!")
except (NameError, ValueError) as e:
print("La variable no es correcta")
except:
print("Error")
else:
print("Esto se ejecuta cuando no hay excepciones")
finally:
print("Esto se ejecuta siempre")
La variable no es correcta Esto se ejecuta siempre
Más adelante veremos cómo crear nuestras propias excepciones.
Podemos usar assert
si queremos testear algo y en caso de que no se cumpla automáticamente se lance una excepción de tipo AssertionError
.
assert len(l) > 0, "La secuencia debe contener elementos" # Raises exception if empty
Para lanzar una excepción de forma manual usaremos raise()
:
try:
raise(Exception)
except:
print("Excepción!")
Excepción!
Se conoce así a los fragmentos de código con nombre que devuelven un resultado. Se usa def
para definirlas y return
para devolver valores o tuplas. Si no especificamos un valor de retorno, la función devolverá por defecto None
(el equivalente en Python para null).
# Definimos una función de primer orden, con un valor por defecto para el 2º parámetro
def imprimir1(texto, veces=1):
"""Esta funcion imprime los dos valores pasados
como parametros""" # Docstring: lo que imprime el operador ? de Python o la función help
print(texto*veces)
# Ejecutamos la función de varias formas posibles
imprimir1("hola")
imprimir1("hola ", 2)
res = imprimir1(veces = 2, texto = "hola ")
print(res)
hola hola hola hola hola None
Podemos crear una función con un número variable de parámetros posicionales o sin clave, precediendo el último de un asterisco (*args
). Eso rellenará una tupla con los valores pasados:
def imprimir2(texto, veces=1, *otros):
print(texto*veces, *otros)
imprimir2("hola", 2, "mario", "luis")
holahola mario luis
También existe la opción de poner dos asteriscos (**kwargs
) para recibir un número indeterminado de parámetros con clave. Esto rellenará un diccionario en lugar de una tupla.
def imprimir3(texto, veces=1, **otros):
print(texto*veces, *list(otros.values()))
imprimir3("hola", 2, invitado1 = "mario", invitado2 = "luis")
holahola mario luis
Podemos combinar ambas opciones. Pondremos primero los parámetros posicionales y luego los que van con clave.
def imprimir4(*args, **kwargs):
print(*args, *list(kwargs.values()))
args1 = (1, 2, 3)
args2 = {'a': 11, 'b': 12}
imprimir4(*args1, **args2)
1 2 3 11 12
De forma análoga podemos detallar los argumentos y desempaquetarlos de una tupla o un diccionario al llamar a la función.
def func(a, b):
print(a, b)
t = (0, 1)
func(*t)
d = {'a': 1, 'b': 2}
func(**d)
0 1 1 2
En Python a veces se dice que las variables mutables se pasan a las funciones como referencia, y las inmutables como valor; basándose en que las primeras se pueden modificar dentro de la función teniendo efecto fuera, y las segundas no. Esto puede llevar a confusión. En Python no existe el paso como referencia *per se*. Lo probamos con una función que intente intercambiar 2 variables:
def swap(a, b):
(a, b) = (b, a) # swap sencillo en Python
return (a, b)
# Probamos con variables inmutables
a, b = 1, 2
print(swap(a, b))
print(a, b)
(2, 1) 1 2
# Ahora probamos con variables mutables (listas)
a, b = [1], [2]
print(swap(a, b))
print(a, b)
([2], [1]) [1] [2]
Como podemos comprobar no funciona en ningún caso (las variables se mantienen igual fuera de la función). Pero si queremos conseguir el efecto del paso de argumentos como referencia, tenemos varias opciones:
def append_0(l):
l.append(0)
return l
l=[]
print(append_0(l))
print(append_0(l))
print(l)
[0] [0, 0] [0, 0]
Más adelante veremos funciones de orden superior.
Partimos de que en Python todo es un objeto.
Comparación de objetos
Para comparar el *valor* de 2 objetos usamos el operador ==
, mientras que para comparar su *identidad* usaremos is
# Variables inmutables
a = 257
b = 257
print('a == b: ', a == b)
print('a is b: ', a is b)
a == b: True a is b: False
# Excepción: enteros pequeños (se cachean)
a = 256
b = 256
print('a == b: ', a == b)
print('a is b: ', a is b)
a == b: True a is b: True
# Variables mutables
a = [1]
b = [1]
print('a == b: ', a == b)
print('a is b: ', a is b)
a == b: True a is b: False
Copia de objetos
Cuando hacemos una copia de un objeto inmutable, obtenemos una copia real o "profunda" del mismo.
a = 1
b = a
a += 1
b
1
Cuando hacemos una copia de un objeto mutable en Python, obtenemos una copia de la referencia a su espacio en memoria. Esto se conoce como copia "superficial".
ids = [1, 2, 3]
ids2 = ids
ids.append(4)
print(ids2)
[1, 2, 3, 4]
# Ejemplo de cómo crear una copia real o "profunda" de un objeto mutable
ids = [1, 2, 3]
ids2 = ids.copy() # o ids[:] por ser una lista
ids.append(4)
print(ids2)
[1, 2, 3]
Las clases en Python se declaran como en el siguiente ejemplo. Distinguiremos entre atributos de clase y variables de instancia, y presentamos tres tipos de métodos (de instancia, de clase, estáticos):
class Coche:
"""Abstraccion de los objetos coche.""" # docstring
ruedas = 4 # atributo público de clase, compartido por todas sus instancias
# Método especial (constructor). Función que se ejecuta al instanciar un nuevo objeto de la clase.
def __init__(self, marca):
self.marca = marca # argumento del objeto -> atributo de la instancia
# Métodos de instancia (self,). Acceden a cosas de la instancia por medio de self.( )
def get_marca(self):
return self.marca
def pintar(self, color):
print("Pintar de ", color)
# Método de clase (cls,). Compartido con todas las instancias. Acceden a cosas de la clase por medio de cls.( )
@classmethod
def get_ruedas(cls):
return cls.ruedas
# Método estático (). No tiene acceso a la instancia o el objeto.
@staticmethod
def cerrar():
print("Cerrado")
El primer parámetro de todos los métodos de instancia será self
, aunque no hay que escribirlo al hacer las llamadas, ya que lo pone Python automáticamente.
Los atributos de la clase serán accedidos desde la propia clase como self.variable y se pueden modificar dentro de cualquier función.
# Creación de un objeto, instancia de la clase
mi_coche = Coche('Seat')
# Acceso a atributo de clase
print('Ruedas de mi_coche:', mi_coche.ruedas)
print('Ruedas de Coche:', Coche.ruedas)
# Acceso a método de clase
print('Ruedas de mi_coche:', mi_coche.get_ruedas())
print('Ruedas de Coche:', Coche.get_ruedas())
# Acceso a variable de instancia
print("Marca de mi_coche:", mi_coche.get_marca()) # Coche.get_marca() => error
print("Marca de mi_coche:", mi_coche.marca)
# Acceso a método estático
mi_coche.cerrar()
Coche.cerrar()
print(type(Coche)) #Object
print(type(mi_coche)) #Coche
Ruedas de mi_coche: 4 Ruedas de Coche: 4 Ruedas de mi_coche: 4 Ruedas de Coche: 4 Marca de mi_coche: Seat Marca de mi_coche: Seat Cerrado Cerrado <class 'type'> <class '__main__.Coche'>
Para comprobar si un objeto tiene un atributo concreto tenemos la función hasattr()
. Para obtener su valor usaremos getattr()
. Estos métodos son muy útiles cuando no conocemos el nombre del atributo a priori o cuando estamos dentro de un bucle accediendo a varios atributos.
print(hasattr(mi_coche, 'ambientador'))
print(getattr(mi_coche, 'ruedas'))
False 4
Los métodos especiales son aquellos métodos privados que Python nos proporciona para cada objeto, y cuyo nombre empieza y acaba por __
. Los podemos usar de forma interna. A continuación una lista de los métodos especiales disponibles en una clase de nueva creación.
class NuevaClase:
pass
# Podemos imprimir las funciones de cualquier objeto con dir()
print(dir(NuevaClase))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
Para evitar inicializaciones y liberar memoria en una tarea típica (por ejemplo leer de un fichero) se usa la orden with
. Python ejecutará el método __enter__()
del objeto obtenido antes del bloque a continuación, y __exit__()
al acabar dicho bloque. Un ejemplo con el objeto file que ya tiene esos métodos implementados:
try:
with open('file.txt', 'r') as f:
for line in f:
print(line)
except FileNotFoundError:
pass
Para indicar que una clase hereda de otra se coloca el nombre de la clase padre entre paréntesis cuando declaramos la clase hija.
En Python lo más destacado es que se permite la herencia múltiple, por lo que no son necesarias las interfaces como en otros lenguajes.
La clase hija hereda los atributos y las funciones de las clases padre referenciadas (pudiendo sobrescribirlos), y si hay alguna función cuyo nombre se repita en varias clases padre, tendrá preferencia la primera clase que aparece en la declaración.
Si la clase hija no define un método __init__()
, se llamará automáticamente al de la clase padre; aunque lo adecuado es definirlo y llamarlo explícitamente.
# Clase padre
class Instrumento:
def __init__(self, nombre):
self.nombre = nombre
def get_nombre(self):
return self.nombre
# Clase hija
class Bateria(Instrumento):
def __init__(self, nombre, platillos):
super().__init__(nombre) # si sólo hay una clase padre
# Instrumento.__init__(self, nombre) # si hay más de una clase padre
self.platillos = platillos
mi_bateria = Bateria("batera", 13)
mi_bateria.get_nombre()
'batera'
Podremos comprobar en cualquier momento si un objeto pertenece realmente a la clase hija o a la padre usando type
:
if isinstance(mi_bateria, Instrumento):
print('isinstance: Instrumento')
if isinstance(mi_bateria, Bateria):
print('isinstance: Bateria')
if type(mi_bateria) is Instrumento:
print('type: Instrumento')
if type(mi_bateria) is Bateria:
print('type: Bateria')
isinstance: Instrumento isinstance: Bateria type: Bateria
No existe sobrecarga de métodos en la misma clase debido al tipado dinámico de Python. De intentarse usar como en otros lenguajes, el último método definido sobrescribiría los anteriores. Pero podemos conseguir el mismo efecto jugando con parámetros de longitud variable, valores por defecto de los mismos y decoradores.
No existen los modificadores de acceso. Lo que hace Python es considerar privada toda aquella función o variable que empiece por __
(siempre que no acabe igual, siendo entonces una función especial), o protegida en caso de ir precedida por un único _
. En el resto de casos la función o variable será pública.
En Python podemos crear (y lanzar) nuestras propias excepciones. Basta con crear una clase que herede de Exception
o cualquiera de sus hijas. Las excepciones como ya hemos visto antes se crean con raise
y se recogen con except
:
class MiError(Exception):
def __init__(self, valor):
self.valor = valor
def __str__(self):
return "Error " + str(self.valor)
try:
if 22 > 20:
raise MiError(33)
except MiError as e:
print(e) # o por ejemplo pass si no queremos tratarla
Error 33
¿Qué es una metaclase? Pues es una clase cuyas instancias son clases en lugar de objetos. Es decir; si para construir un objeto se usa una clase, para construir una clase se utiliza una metaclase (por defecto type
).
mi_coche = type('Coche',(),{'gasolina':3})
print(mi_coche.gasolina)
print(type(mi_coche))
3 <class 'type'>
Los módulos son entidades que permiten organizar y dividir lógicamente nuestro código cuando tenemos programas demasiado grandes. Los ficheros son su equivalente en el mundo físico.
Para usar la funcionalidad definida en un módulo, tendremos que importarlo con import
+ nombre del módulo sin extensión de fichero. Esto no sólo deja la funcionalidad disponible, sino que ejecuta dicho módulo. Podemos escribir varios módulos separados por comas en la instrucción import.
Para usar funciones de los módulos importados, habrá que antecederlas del nombre del módulo y un punto. O podemos usar from [module] import [function]
para importar el objeto al espacio de nombres actual y así ahorrarnos escribir el nombre del módulo. También es posible usar from [module] import *
pero se desaconseja.
El atributo __doc__
nos sirve para documentar el módulo.
Opciones de importación
from math import * # NO!!!!! importa todas las funciones del módulo
import math # importa el módulo; ejecución como math.sqrt()
import math as M # importa el módulo usando alias; ejecución como M.sqrt()
from math import sqrt, cos # SI!!!!! importa funciones concretas del módulo
Para importar módulos en otro directorio distinto al nuestro, deberemos tenerlos disponibles en la variable PYTHONPATH. Podemos consultar el contenido del path en python ejecutando lo siguiente:
import sys
print(sys.path)
['C:\\Users\\yago_\\Dropbox\\DEV\\projects\\notebooks\\python3-101', 'C:\\Users\\yago_\\Miniconda3\\envs\\py37\\python37.zip', 'C:\\Users\\yago_\\Miniconda3\\envs\\py37\\DLLs', 'C:\\Users\\yago_\\Miniconda3\\envs\\py37\\lib', 'C:\\Users\\yago_\\Miniconda3\\envs\\py37', '', 'C:\\Users\\yago_\\Miniconda3\\envs\\py37\\lib\\site-packages', 'C:\\Users\\yago_\\Miniconda3\\envs\\py37\\lib\\site-packages\\win32', 'C:\\Users\\yago_\\Miniconda3\\envs\\py37\\lib\\site-packages\\win32\\lib', 'C:\\Users\\yago_\\Miniconda3\\envs\\py37\\lib\\site-packages\\Pythonwin', 'C:\\Users\\yago_\\Miniconda3\\envs\\py37\\lib\\site-packages\\IPython\\extensions', 'C:\\Users\\yago_\\.ipython']
Podemos imprimir las funciones y atributos de un módulo usando dir()
print(dir(math))
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']
Los módulos (ficheros) son también objetos, por lo que pueden tener sus atributos y sus métodos. El atributo __name__
se usa de forma habitual en un módulo para comprobar si se está ejecutando el módulo como importado, o bien como programa principal (en cuyo caso __name__
será igual a "__main__", pudiendo ejecutar condicionalmente una parte del código):
print("Ésto se imprime siempre")
if __name__ == "__main__":
print("Ésto se imprime sólo cuando la ejecución no es mediante import")
Ésto se imprime siempre Ésto se imprime sólo cuando la ejecución no es mediante import
Los paquetes sirven para organizar los módulos. En realidad ambos son tipos especiales de módulos (module
). Los paquetes se representan físicamente como directorios.
Para hacer que python trate un directorio como un paquete es necesario crear un fichero __init__.py
dentro del mismo. En dicho fichero se definen elementos que pertenezcan al paquete, aunque basta con meter un módulo en el directorio para que esté disponible.
Como se trata de módulos, para tenerlos disponibles se usa import
:
# import paquete.subpaquete.modulo
# modulo.func()
La programación funcional es un paradigma en el que el código se basa casi en su totalidad en funciones, entendiendo el concepto de función según su definición matemática, y no como los simples subprogramas de los lenguajes imperativos que podamos haber visto hasta ahora. El concepto de variables desaparece, y las funciones no tienen efectos colaterales; el resultado de ejecutar una función 2 veces con la misma entrada será el mismo, con todas las ventajas que eso supone.
Python cuenta con varias características de este paradigma.
Las funciones en Python son de primera clase o de orden superior. Como todo en Python las funciones son objetos: pueden asignarse a una variable o guardarse en una estructura, y pueden pasarse como parámetro a otras funciones, o devolverse como resultado de las mismas.
# Ejemplo
def crear_suma(x):
def suma(y):
return x + y
return suma
suma_10 = crear_suma(10)
print(suma_10(3))
13
# Ejemplo más práctico
def saludar(lang):
def saludar_es():
print("Hola")
def saludar_en():
print("Hello")
def saludar_it():
print("Ciao")
lang_func = {"es": saludar_es,
"en": saludar_en,
"it": saludar_it}
return lang_func[lang]
f = saludar("es") # devuelve una función
f() # ejecutamos la función
# Podríamos simplificar escribiendo: saludar("es")()
Hola
Podemos pasar nuestras funciones de orden superior como argumentos a las funciones del core map
y filter
, u otras como reduce
. Estas funciones nos permiten precindir de los bucles típicos de otros lenguajes.
Devuelve una secuencia (objeto map) con el resultado de aplicar una función a cada elemento de un iterable (o varios iterables, uno a uno). Si se pasan como parámetros n iterables, la función tendrá que aceptar n argumentos. Si alguna de las secuencias es más pequeña que las demás, el valor que le llega a la función para posiciones mayores que el tamaño de dicha secuencia será None.
# Ejemplo
def cuadrado(n):
return n ** 2
l = [1, 2, 3]
l2 = map(cuadrado, l)
print(l2)
print(list(l2))
<map object at 0x000001E1C3BB6508> [1, 4, 9]
# Ejemplo con 2 iterables
def concat_zip(a, b):
return a + b
x = map(concat_zip, ('apple', 'banana', 'cherry'), ('orange', 'lemon', 'pineapple'))
print(list(x))
['appleorange', 'bananalemon', 'cherrypineapple']
# Ejemplo: convertir lista de números en lista de caracteres
print(list(map(str, l)))
['1', '2', '3']
Devuelve una secuencia con los elementos del iterable para los que la función devuelve True
.
# Ejemplo
def es_par(n):
return (n % 2.0 == 0)
l = [1, 2, 3, 4, 5, 6, 7]
f = filter(es_par, l)
print(f)
print(list(f))
<filter object at 0x000001E1C3BBE9C8> [2, 4, 6]
Devuelve el resultado (un valor) de ir aplicando una función a pares de elementos de un iterable. La función aceptará 2 parámetros; el primero es el valor acumulado de la ejecución anterior (initial si es la primera) y el segundo es el elemento actual del iterable. En Python 3 forma parte de functools
from functools import reduce
def multiplica(x, y):
return x * y
print(reduce(multiplica, [1, 2, 3, 4, 5]))
120
Las funciones lambda son funciones anónimas o temporales, que no podrán ser referenciadas más adelante.
Se construyen mediante el operador lambda, los parámetros de la función separados por comas (SIN paréntesis), dos puntos (:) y el código de la función.
# Ejemplo simple
print((lambda x: x % 2)(5))
1
# Ejemplo con filter
lista = [1, 2, 3]
print(list(filter(lambda n: n % 2.0 == 0, lista)))
# La función lambda equivale a:
def lambda_function(n):
return n % 2.0 == 0
[2]
# Ejemplo con sort (función que ordena un diccionario atendiendo a una clave):
points = [{"x": 2, "y": 3}, {"x": 4, "y": 1}]
points.sort(key=lambda i: i["y"]) # [{"y":1, ..}, {"y":3, ..}]
print(points)
[{'x': 4, 'y': 1}, {'x': 2, 'y': 3}]
Construcción que permite crear listas a partir de otras listas. También es aplicable a otros iterables, aunque su uso más habitual es con listas.
Su estructura puede seguir dos patrones:
[function(x) for x in iterable [if condition]]
[function(x) if condition [else operation2] for x in iterable]
Cada una de estas construcciones consta de una expresión que determina cómo modificar el elemento de la lista original, seguida de una o varias cláusulas for y opcionalmente una o varias cláusulas if.
Ejemplos
print(l)
[1, 2, 3, 4, 5, 6, 7]
# Ejemplo equivalente a map
print([n ** 2 for n in l])
[1, 4, 9, 16, 25, 36, 49]
# Ejemplo equivalente a filter
print([n for n in l if n % 2.0 == 0])
[2, 4, 6]
# Ejemplo con if-else
print([n if n % 2 == 0 else 0 for n in l]) # cambia los impares por 0
[0, 2, 0, 4, 0, 6, 0]
# Ejemplo con doble for para obtener las combinaciones que suman 10
print(sum([1 if l[i] + l[j] == 10 else 0 for i in range(len(l)) for j in range(i+1,len(l))]))
2
Comprensión de conjuntos
nombres = ['jaime', 'yago', 'iago', 'tiago', 'diego', 'jacobo', 'iacobus', 'santiago']
longitudes = {len(nombre) for nombre in nombres}
print(longitudes)
{4, 5, 6, 7, 8}
Comprensión de diccionarios
name_lengths = {nombre: len(nombre) for nombre in nombres}
print(name_lengths)
{'jaime': 5, 'yago': 4, 'iago': 4, 'tiago': 5, 'diego': 5, 'jacobo': 6, 'iacobus': 7, 'santiago': 8}
Los generadores son una herramienta de programación perezosa, con una expresión similar a la de la comprensión de listas (de hecho se escriben igual que éstas pero usando paréntesis en lugar de corchetes). La diferencia es que no devuelven una lista, sino un generador.
Un generador es un tipo especial de función que genera bajo demanda valores sobre los que iterar. Para devolver el siguiente valor sobre el que iterar se usa la palabra clave yield
en lugar de return. Para iterar sobre el generador se usa por ejemplo un for...in
Como no se llega a crear una lista en memoria, sino que se generan valores y se consumen, estamos ahorrando recursos; algo que notaremos con grandes cantidades de datos. No obstante podemos crear una lista a partir de un generador gracias a la función list()
.
# Ejemplo
def mi_generador(n, m, s):
while(n <= m):
yield n
n += s
for n in mi_generador(0, 7, 2):
print(n)
0 2 4 6
Una clausura (closure) es un mecanismo para llamar a una función interna que tiene acceso al scope de su función contenedora
# Ejemplo de función para calcular la media de una serie
def construir_calculadora_media():
series = [] # variable local en el ámbito de la función outer, accesible desde la inner
def calcular_media(valor):
series.append(valor)
return sum(series)/len(series)
return calcular_media
calcular_media = construir_calculadora_media() # clausura de la función inner
print(calcular_media(10))
print(calcular_media(15))
print(calcular_media(20))
10.0 12.5 15.0
Un decorador es una función que recibe otra función como parámetro y extiende el comportamiento de aquella sin modificarla. Devuelve una función interna como resultado.
Puede verse como un recubrimiento para funciones; útil por ejemplo para debugging, ejecución con reintentos, tratamiento de excepciones, etc.
# Ejemplo
def mi_decorador(funcion):
def nueva(*args):
print("Llamada a la función", funcion.__name__)
return funcion(*args)
return nueva
def imprimir5(texto):
print(texto)
mi_decorador(imprimir5)("hola!")
Llamada a la función imprimir5 hola!
Si queremos algo más limpio y que el decorador se aplique siempre a la función, lo escribiremos como una anotación delante:
@mi_decorador
def imprimir6(texto):
print(texto)
imprimir6("hola!")
Llamada a la función imprimir6 hola!