Python es uno de esos lenguajes que uso para automatizar tareas, consultar servicios, procesar datos y resolver problemas cotidianos. En esta entrada he reunido una chuleta extensa para consultar rápidamente sintaxis, métodos y bibliotecas sin tener que buscar cada función por separado.
El contenido sigue la organización de la chuleta original: colecciones, tipos, sintaxis, sistema, datos, programación avanzada, bibliotecas y multimedia. Los bloques de código se conservan como referencia, incluidos los nombres de funciones, parámetros y ejemplos. Muchas líneas con marcadores como <list> o <path> son esquemas explicativos, no instrucciones que puedan ejecutarse literalmente.
La base de esta recopilación es Python Cheatsheet de gto76. Esta adaptación conserva la estructura original y traduce al español notas y comentarios; conviene consultar el proyecto original y su licencia antes de redistribuir el contenido. Las bibliotecas externas pueden cambiar sus interfaces entre versiones.
#Contenido
ToC = {
'1. Collections': [List, Dictionary, Set, Tuple, Range, Enumerate, Iterator, Generator],
'2. Types': [Type, String, Regular_Exp, Format, Numbers, Combinatorics, Datetime],
'3. Syntax': [Args, Inline, Import, Decorator, Class, Duck_Types, Enum, Exception],
'4. System': [Exit, Print, Input, Command_Line_Arguments, Open, Path, OS_Commands],
'5. Data': [JSON, Pickle, CSV, SQLite, Bytes, Struct, Array, Memory_View, Deque],
'6. Advanced': [Threading, Operator, Match_Stmt, Logging, Introspection, Coroutines],
'7. Libraries': [Progress_Bar, Plots, Tables, Curses, GUIs, Scraping, Web, Profiling],
'8. Multimedia': [NumPy, Image, Animation, Audio, Synthesizer, Pygame, Pandas, Plotly]
}
Punto de entrada
if __name__ == '__main__': # Omite la línea siguiente si el archivo se ha importado.
main() # Ejecuta la función `main()`.
Listas
<el> = <list>[index] # El primer índice es 0 y el último, -1. Permite asignaciones.
<list> = <list>[<slice>] # O bien: <list>[from_inclusive : to_exclusive : ±step]
<list>.append(<el>) # O bien: <list> += [<el>]
<list>.extend(<collection>) # O bien: <list> += <collection>
<list>.sort() # Ordena de menor a mayor.
<list>.reverse() # Invierte la lista original.
<list> = sorted(<collection>) # Devuelve una lista nueva ordenada.
<iter> = reversed(<list>) # Devuelve un iterador en orden inverso.
sum_of_elements = sum(<collection>)
elementwise_sum = [sum(pair) for pair in zip(list_a, list_b)]
sorted_by_second = sorted(<collection>, key=lambda el: el[1])
sorted_by_both = sorted(<collection>, key=lambda el: (el[1], el[0]))
flatter_list = list(itertools.chain.from_iterable(<list>))
product_of_elems = functools.reduce(lambda out, el: out * el, <collection>)
list_of_chars = list(<str>)
- Para más información sobre
sorted(),min()ymax(), consulta sortable. - El módulo operator proporciona las funciones
itemgetter()ymul(), que permiten hacer lo mismo que las lambda expresiones anteriores.
<int> = len(<list>) # Devuelve el número de elementos. También funciona con otras colecciones.
<int> = <list>.count(<el>) # Cuenta las apariciones del elemento. También: `if <el> in <coll>: ...`.
<int> = <list>.index(<el>) # Devuelve el índice de la primera aparición o lanza ValueError.
<el> = <list>.pop() # Elimina y devuelve el último elemento, o el del índice indicado.
<list>.insert(<int>, <el>) # Inserta el elemento en el índice y desplaza los siguientes.
<list>.remove(<el>) # Elimina la primera aparición o lanza ValueError.
<list>.clear() # Elimina todos los elementos. También funciona con diccionarios y conjuntos.
Diccionarios
<view> = <dict>.keys() # Vista de claves que refleja los cambios.
<view> = <dict>.values() # Vista de valores que refleja los cambios.
<view> = <dict>.items() # Vista de parejas clave-valor que refleja los cambios.
value = <dict>.get(key, default=None) # Devuelve el valor predeterminado si falta la clave.
value = <dict>.setdefault(key, default=None) # Devuelve y guarda el valor predeterminado si falta la clave.
<dict> = collections.defaultdict(<type>) # Devuelve un diccionario cuyo valor predeterminado es `<type>()`.
<dict> = collections.defaultdict(lambda: 1) # Devuelve un diccionario cuyo valor predeterminado es 1.
<dict> = dict(<collection>) # Crea un diccionario a partir de pares clave-valor.
<dict> = dict(zip(keys, values)) # Crea un diccionario a partir de dos colecciones.
<dict> = dict.fromkeys(keys [, value]) # Crea un diccionario a partir de una colección de claves.
<dict>.update(<dict>) # Añade elementos y sustituye los que tengan claves coincidentes.
value = <dict>.pop(key) # Elimina el elemento o lanza `KeyError` si la clave no existe.
{k for k, v in <dict>.items() if v == value} # Devuelve el conjunto de claves asociadas al valor.
{k: v for k, v in <dict>.items() if k in keys} # Filtra el diccionario por claves.
Counter
>>> from collections import Counter
>>> counter = Counter(['blue', 'blue', 'blue', 'red', 'red'])
>>> counter['yellow'] += 1
>>> print(counter)
Counter({'blue': 3, 'red': 2, 'yellow': 1})
>>> counter.most_common()[0]
('blue', 3)
Conjuntos
<set> = set() # `{}` devuelve un diccionario.
<set>.add(<el>) # O bien: <set> |= {<el>}
<set>.update(<collection> [, ...]) # O bien: <set> |= <set>
<set> = <set>.union(<coll.>) # O bien: <set> | <set>
<set> = <set>.intersection(<coll.>) # O bien: <set> & <set>
<set> = <set>.difference(<coll.>) # O bien: <set> - <set>
<set> = <set>.symmetric_difference(<coll.>) # O bien: <set> ^ <set>
<bool> = <set>.issubset(<coll.>) # O bien: <set> <= <set>
<bool> = <set>.issuperset(<coll.>) # O bien: <set> >= <set>
<el> = <set>.pop() # Lanza KeyError si está vacío.
<set>.remove(<el>) # Lanza KeyError si no existe.
<set>.discard(<el>) # No lanza ninguna excepción.
Conjunto inmutable (frozenset)
- Es inmutable y hashable.
- Esto significa que puede utilizarse como clave en un diccionario o como elemento de un conjunto (
set).
<frozenset> = frozenset(<collection>)
Tuplas
Una tupla es una secuencia inmutable y hashable, siempre que todos sus elementos también sean hashables.
<tuple> = () # Tupla vacía.
<tuple> = (<el>,) # O bien: <el>,
<tuple> = (<el_1>, <el_2> [, ...]) # O bien: <el_1>, <el_2> [, ...]
Tupla con nombres
Subclase de tupla que permite acceder a sus elementos mediante nombres, además de índices.
>>> from collections import namedtuple
>>> Point = namedtuple('Point', 'x y')
>>> p = Point(1, y=2); p
Point(x=1, y=2)
>>> p[0]
1
>>> p.x
1
>>> getattr(p, 'y')
2
Rangos
Immutable y hashable secuencia de enteros.
<range> = range(stop) # range(to_exclusive)
<range> = range(start, stop) # range(from_inclusive, to_exclusive)
<range> = range(start, stop, ±step) # range(from_inclusive, to_exclusive, ±step_size)
>>> [i for i in range(3)]
[0, 1, 2]
Enumeración
for i, el in enumerate(<coll>, start=0): # Devuelve iterator de `(index+start, <el>)` tuplas.
...
Iteradores
<iter> = iter(<collection>) # `iter(<iter>)` devuelve unmodified iterator.
<iter> = iter(<function>, to_exclusive) # A secuencia de return valores hasta 'to_exclusive'.
<el> = next(<iter> [, default]) # Lanza StopIteration or devuelve 'valor predeterminado' on fin.
<list> = list(<iter>) # Devuelve una lista con los elementos restantes del iterador.
Herramientas para iteradores
import itertools as it
<iter> = it.count(start=0, step=1) # Devuelve updated valor endlessly. Acepta floats.
<iter> = it.repeat(<el> [, times]) # Devuelve elemento endlessly or 'times' times.
<iter> = it.cycle(<collection>) # Repite the secuencia endlessly.
<iter> = it.chain(<coll>, <coll> [, ...]) # Empties colecciones en orden (figuratively).
<iter> = it.chain.from_iterable(<coll>) # Empties colecciones dentro de a colección en orden.
<iter> = it.islice(<coll>, to_exclusive) # Solo devuelve primero 'to_exclusive' elementos.
<iter> = it.islice(<coll>, from_inc, …) # `to_exclusive, +step_size`. índices puede ser None.
Generadores
- Cualquier función que contenga una instrucción
yielddevuelve un generador. - Los generadores y los iteradores pueden utilizarse de manera intercambiable.
def count(start, step):
while True:
yield start
start += step
>>> counter = count(10, 2)
>>> next(counter), next(counter), next(counter)
(10, 12, 14)
Tipos
- Todo en Python es un objeto.
- Cada objeto tiene un tipo.
- Los términos tipo y clase son sinónimos en Python.
<type> = type(<el>) # O bien: <el>.__class__
<bool> = isinstance(<el>, <type>) # O bien: issubclass(tipo(<el>), <type>)
>>> type('a'), 'a'.__class__, str
(<class 'str'>, <class 'str'>, <class 'str'>)
Algunos tipos no tienen nombres integrados y hay que importarlos:
from types import FunctionType, MethodType, LambdaType, GeneratorType, ModuleType
Clases base abstractas
Cada clase base abstracta (ABC) especifica un conjunto de subclases virtuales. Estas clases son reconocidas por isinstance() e issubclass() como subclases de la ABC, aunque realmente no lo sean. Una ABC también puede decidir manualmente si una clase específica es su subclase virtual, generalmente basándose en qué métodos ha implementado dicha clase. Por ejemplo, la ABC Iterable busca el método __iter__(), mientras que la ABC Collection busca __iter__(), __contains__() y __len__().
>>> from collections.abc import Iterable, Collection, Sequence
>>> isinstance([1, 2, 3], Iterable)
True
| Tipo | Iterable | Collection | Sequence |
|---|---|---|---|
| list, range, str | ✓ | ✓ | ✓ |
| dict, set | ✓ | ✓ | |
| iter | ✓ |
>>> from numbers import Number, Complex, Real, Rational, Integral
>>> isinstance(123, Number)
True
| Tipo | Number | Complex | Real | Rational | Integral |
|---|---|---|---|---|---|
| int | ✓ | ✓ | ✓ | ✓ | ✓ |
| fractions.Fraction | ✓ | ✓ | ✓ | ✓ | |
| float | ✓ | ✓ | ✓ | ||
| complex | ✓ | ✓ | |||
| decimal.Decimal | ✓ |
Cadenas de texto
Immutable secuencia de caracteres.
<str> = <str>.strip() # Strips todos los whitespace caracteres desde ambos ends.
<str> = <str>.strip('<chars>') # Strips proporcionados caracteres. También lstrip/rstrip().
<list> = <str>.split() # Divide on uno o más whitespace caracteres.
<list> = <str>.split(sep=None, maxsplit=-1) # Divide on 'sep' str at most 'maxsplit' times.
<list> = <str>.splitlines(keepends=False) # On [\n\r\f\v\x1c-\x1e\x85\u2028\u2029] y \r\n.
<str> = <str>.join(<coll_of_strings>) # Une elementos using cadena como un separator.
<bool> = <sub_str> in <str> # Comprueba si cadena contains the substring.
<bool> = <str>.startswith(<sub_str>) # Pasa tupla de cadenas for varios options.
<int> = <str>.find(<sub_str>) # Devuelve inicio índice del primero match or -1.
<int> = <str>.index(<sub_str>) # Igual, pero lanza ValueError si there's no match.
<str> = <str>.lower() # Cambia the case. También upper/capitalize/title().
<str> = <str>.replace(old, new [, count]) # Sustituye 'old' con 'nuevo' at most 'count' times.
<str> = <str>.translate(<table>) # Usa `str.maketrans(<dict>)` to generate tabla.
<str> = chr(<int>) # Convierte int to Unicode carácter.
<int> = ord(<str>) # Convierte Unicode carácter to int.
- Usa
'unicodedata.normalize("NFC", <str>)'en cadenas como'Motörhead'antes de compararlas con otras, porque la'ö'puede almacenarse como uno o dos caracteres. 'NFC'convierte esos caracteres a un solo carácter, mientras que'NFD'los convierte en dos.
Métodos de comprobación
<bool> = <str>.isdecimal() # True si todos los caracteres son dígitos decimales Unicode.
<bool> = <str>.isdigit() # True si todos son dígitos, incluidos los superíndices.
<bool> = <str>.isnumeric() # True si todos son caracteres numéricos, incluidas fracciones.
<bool> = <str>.isalnum() # True si todos son letras o números Unicode.
<bool> = <str>.isprintable() # True si todos son caracteres imprimibles.
<bool> = <str>.isspace() # True si todos son espacios en blanco, tabulaciones o saltos de línea.
Expresiones regulares
funciones for regular expression coincidentes.
import re
<str> = re.sub(r'<regex>', new, text, count=0) # Substitutes todos los occurrences con 'nuevo'.
<list> = re.findall(r'<regex>', text) # Devuelve todos los occurrences as cadenas.
<list> = re.split(r'<regex>', text, maxsplit=0) # Add brackets around regex to keep coincide con.
<Match> = re.search(r'<regex>', text) # Primero occurrence del pattern or None.
<Match> = re.match(r'<regex>', text) # Searches solo al principio del text.
<iter> = re.finditer(r'<regex>', text) # Devuelve todos los occurrences as Match objetos.
- Los literales de cadena "raw" (crudos) no interpretan secuencias de escape, permitiéndonos usar secuencias de escape específicas de regex que causan un
SyntaxWarningen cadenas normales (desde Python 3.12). - El argumento
'new'dere.sub()puede ser una función que acepta un objeto Match y devuelve un cadena. - El argumento
'flags=re.IGNORECASE'puede usarse con todas las funciones. - El argumento
'flags=re.MULTILINE'hace que'^'y'$'coincidan con el inicio/final de cada línea. - El argumento
'flags=re.DOTALL'hace que el punto'.'también acepte el salto de línea'\n'. 're.compile(<regex>)'devuelve un objeto Pattern con los métodossub(),findall(), etc.
Objeto de coincidencia
<str> = <Match>.group() # Devuelve el completo match. También group(0).
<str> = <Match>.group(1) # Devuelve part dentro de the primero brackets.
<tuple> = <Match>.groups() # Devuelve todos los bracketed parts.
<int> = <Match>.start() # Devuelve inicio índice del match.
<int> = <Match>.end() # Devuelve exclusive fin índice del match.
Secuencias especiales
r'\d' # Coincide con un dígito decimal Unicode (0-9, ०-९, ٠-٩, etc.).
r'\w' # Coincide con una letra, un número Unicode o un guion bajo (_).
r'\s' # Coincide con espacios en blanco, tabulaciones y saltos de línea.
- De forma predeterminada, decimal caracteres, alphanumerics y whitespaces desde todos los alphabets are matched salvo que
'flags=re.ASCII'argumento is used. - It restricts special secuencia coincide con to
'[\x00-\x7f]'(the primero 128 caracteres) y también prevents'\s'desde accepting'[\x1c-\x1f]'(the so-called separator caracteres). - Usa a capital letter for negation (todos los non-ASCII caracteres will be matched cuando used in combination con ASCII flag).
Formato
<str> = f'{<el_1>}, {<el_2>}' # Las llaves {} también pueden contener expresiones.
<str> = '{}, {}'.format(<el_1>, <el_2>) # O bien: '{0}, {a}'.format(<el_1>, a=<el_2>)
<str> = '%s, %s' % (<el_1>, <el_2>) # Formato antiguo de estilo C. Se recomienda usar f-strings.
Ejemplo
>>> Person = collections.namedtuple('Person', 'name height')
>>> person = Person('Jean-Luc', 187)
>>> f'{person.name} is {person.height / 100} meters tall.'
'Jean-Luc is 1.87 meters tall.'
Opciones generales
{<el>:<10} # '<el> '
{<el>:^10} # ' <el> '
{<el>:>10} # ' <el>'
{<el>:.<10} # '<el>......'
{<el>:0} # '<el>'
- Los objetos se representan (renderizan) usando
'format(<elemento>, <opciones>)'.- Las opciones pueden generarse dinámicamente:
f'{<elemento>:{<opciones>}}'.- Añadir
'='a la expresión la antepone al resultado:f'{1+1=}'devuelve'1+1=2'.- Añadir
'!r'a la expresión convierte el objeto a cadena llamando a su métodorepr().
Cadenas
{'abcde':10} # 'abcde '
{'abcde':10.3} # 'abc '
{'abcde':.3} # 'abc'
{'abcde'!r:10} # "'abcde' "
Números
{123456:10} # ' 123456'
{123456:10,} # ' 123,456'
{123456:10_} # ' 123_456'
{123456:+10} # ' +123456'
{123456:=+10} # '+ 123456'
{123456: } # ' 123456'
{-123456: } # '-123456'
Números de coma flotante
{1.23456:10.3} # ' 1.23'
{1.23456:10.3f} # ' 1.235'
{1.23456:10.3e} # ' 1.235e+00'
{1.23456:10.3%} # ' 123.456%'
Comparación de tipos de presentación:
Esta tabla muestra cómo Python formatea diferentes magnitudes de un número según el tipo (f, e, %) sin especificar precisión extra.
| Valor (float) | {<float>} (General) |
{<float>:f} (Fijo) |
{<float>:e} (Científico) |
{<float>:%} (Porcentaje) |
|---|---|---|---|---|
| 0.000056789 | '5.6789e-05' | '0.000057' | '5.678900e-05' | '0.005679%' |
| 0.00056789 | '0.00056789' | '0.000568' | '5.678900e-04' | '0.056789%' |
| 0.0056789 | '0.0056789' | '0.005679' | '5.678900e-03' | '0.567890%' |
| 0.056789 | '0.056789' | '0.056789' | '5.678900e-02' | '5.678900%' |
| 0.56789 | '0.56789' | '0.567890' | '5.678900e-01' | '56.789000%' |
| 5.6789 | '5.6789' | '5.678900' | '5.678900e+00' | '567.890000%' |
| 56.789 | '56.789' | '56.789000' | '5.678900e+01' | '5678.900000%' |
Esta tabla muestra el efecto de limitar la salida a 2 decimales o dígitos significativos.
| Valor (float) | {<float>:.2} |
{<float>:.2f} |
{<float>:.2e} |
{<float>:.2%} |
|---|---|---|---|---|
| 0.000056789 | '5.7e-05' | '0.00' | '5.68e-05' | '0.01%' |
| 0.00056789 | '0.00057' | '0.00' | '5.68e-04' | '0.06%' |
| 0.0056789 | '0.0057' | '0.01' | '5.68e-03' | '0.57%' |
| 0.056789 | '0.057' | '0.06' | '5.68e-02' | '5.68%' |
| 0.56789 | '0.57' | '0.57' | '5.68e-01' | '56.79%' |
| 5.6789 | '5.7' | '5.68' | '5.68e+00' | '567.89%' |
| 56.789 | '5.7e+01' | '56.79' | '5.68e+01' | '5678.90%' |
'{<float>:g}'es equivalente a'{<float>:.6}'pero eliminando los ceros finales sobrantes y usando notación científica (exponente) a partir de'1e+06'.- Cuando es posible redondear tanto hacia arriba como hacia abajo con la misma distancia, se elige el resultado cuyo último dígito sea par. Esto hace que
'{6.5:.0f}'sea'6'y'{7.5:.0f}'sea'8'. - Esta regla solo afecta a números que pueden representarse exactamente mediante un float (
.5,.25, …).
Enteros
{90:c} # 'Z'. Unicode carácter con valor 90.
{90:b} # '1011010'. número 90 in binary.
{90:X} # '5A'. número 90 in uppercase hexadecimal.
Números
<int> = int(<float/str/bool>) # O bien: math.floor(<float>)
<float> = float(<int/str/bool>) # O bien: <int/float>e±<int>
<complex> = complex(real=0, imag=0) # O bien: <int/float> ± <int/float>j
<Fraction> = fractions.Fraction(0, 1) # O bien: Fraction(numerator=0, denominator=1)
<Decimal> = decimal.Decimal(<str/int>) # O bien: Decimal((sign, digits, exponent))
'int(<str>)'y'float(<str>)'lanzan unValueErrorsi las cadenas están mal formadas.- Los números decimales (
Decimal) se almacenan de forma exacta, a diferencia de la mayoría de los floats, donde'1.1 + 2.2 != 3.3'. - Los floats pueden compararse con:
'math.isclose(<float>, <float>)'. - La precisión de las operaciones con decimales se establece con:
'decimal.getcontext().prec = <int>'.
Explicación:
¿Por qué esto es importante?
- El peligro de los strings mal formados
Si intentas convertir algo que no es un número (ej. int("abc") o int("1.5")), Python detendrá el programa con un error. Siempre es bueno usar un bloque try/except si no estás seguro de lo que viene del usuario.
- La "mentira" de los Floats
Los ordenadores no guardan los decimales como nosotros. Guardan potencias de 2. Esto causa que 1.1 + 2.2 de en realidad 3.3000000000000003.
- Decimal: Es una librería especial (
from decimal import Decimal). Úsala siempre para dinero o cálculos financieros donde cada céntimo cuenta, porque ahí1.1 + 2.2sí es exactamente3.3.
3. math.isclose()
Como los floats fallan por millonésimas, nunca deberías usar if a == b:. En su lugar, usas math.isclose(a, b), que le pregunta a Python: "¿Son estos dos números lo suficientemente parecidos como para decir que son el mismo?".
- Controlar la precisión
Con los Decimal, tú mandas. Puedes decidir cuántos dígitos significativos quieres en todo tu programa. Si pones la precisión en 2, cualquier operación matemática se recortará a dos dígitos.
Funciones básicas
<num> = pow(<num>, <num>) # O bien: <number> <number>
<num> = abs(<num>) # <float> = abs(<complex>)
<num> = round(<num> [, ±ndigits]) # `round(126, -1) == 130`
Matemáticas
from math import e, pi, inf, nan, isinf, isnan # `<el> == nan` is always False.
from math import sin, cos, tan, asin, acos, atan # También: degrees, radians.
from math import log, log10, log2 # Log can accept base as second arg.
Estadística
from statistics import mean, median, variance # También: stdev, quantiles, groupby.
Aleatoriedad
from random import random,randint,choice # También: shuffle, gauss, triangular, seed.
<float> = random() # Un float dentro de [0, 1).
<int> = randint(from_inc, to_inc) # Un int dentro de [from_inc, to_inc].
<el> = choice(<sequence>) # Mantiene la secuencia intacta.
Binario y hexadecimal
<int> = ±0b<bin> # O bien: ±0x<hex>
<int> = int('±<bin>', 2) # O bien: int('±<hex>', 16)
<int> = int('±0b<bin>', 0) # O bien: int('±0x<hex>', 0)
<str> = bin(<int>) # Devuelve '[-]0b<bin>'.
Operadores bit a bit
<int> = <int> & <int> # y (0b1100 & 0b1010 == 0b1000).
<int> = <int> | <int> # Or (0b1100 | 0b1010 == 0b1110).
<int> = <int> ^ <int> # Xor (0b1100 ^ 0b1010 == 0b0110).
<int> = <int> << n_bits # Left shift. Usa >> for right.
<int> = ~<int> # Not. También -<int> - 1.
Combinatoria
import itertools as it
>>> list(it.product([0, 1], repeat=3))
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1),
(1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]
>>> list(it.product('abc', 'abc')) # a b c
[('a', 'a'), ('a', 'b'), ('a', 'c'), # a x x x
('b', 'a'), ('b', 'b'), ('b', 'c'), # b x x x
('c', 'a'), ('c', 'b'), ('c', 'c')] # c x x x
>>> list(it.combinations('abc', 2)) # a b c
[('a', 'b'), ('a', 'c'), # a . x x
('b', 'c')] # b . . x
>>> list(it.combinations_with_replacement('abc', 2)) # a b c
[('a', 'a'), ('a', 'b'), ('a', 'c'), # a x x x
('b', 'b'), ('b', 'c'), # b . x x
('c', 'c')] # c . . x
>>> list(it.permutations('abc', 2)) # a b c
[('a', 'b'), ('a', 'c'), # a . x x
('b', 'a'), ('b', 'c'), # b x . x
('c', 'a'), ('c', 'b')] # c x x .
Fecha y hora
Python incluye el módulo datetime, que permite trabajar con fechas, horas, intervalos de tiempo y zonas horarias.
Es especialmente útil cuando necesitamos registrar eventos, calcular diferencias entre fechas, programar tareas, procesar logs o almacenar marcas de tiempo en bases de datos.
El módulo proporciona cuatro clases principales:
| Clase | Descripción | Ejemplo |
|---|---|---|
date |
Representa una fecha sin hora. | 2026-10-21 |
time |
Representa una hora sin fecha. | 14:30:00 |
datetime |
Combina una fecha y una hora. | 2026-10-21 14:30:00 |
timedelta |
Representa una duración o diferencia entre fechas. | 2 días, 3 horas |
Estos objetos son inmutables: una vez creados, no podemos modificar directamente sus atributos. Las operaciones que cambian una fecha o una hora devuelven un objeto nuevo.
También son hashables, lo que permite utilizarlos como claves de diccionarios o elementos de conjuntos.
Importar las clases necesarias
from datetime import date, time, datetime, timedelta, timezone
from zoneinfo import ZoneInfo
Desde Python 3.9, ZoneInfo forma parte de la biblioteca estándar y permite trabajar con zonas horarias reales, incluidas sus reglas de horario de verano.
También existe la biblioteca externa python-dateutil, que incorpora funciones adicionales para interpretar fechas y gestionar zonas horarias.
pip install python-dateutil
from dateutil.tz import tzlocal, gettz
Para los ejemplos principales de esta guía utilizaremos datetime y ZoneInfo, sin depender de bibliotecas externas.
Crear fechas, horas e intervalos
<D> = date(year, month, day) # Crea una fecha válida entre los años 1 y 9999.
<T> = time(hour=0, minute=0, second=0) # Crea una hora. Admite microsecond, tzinfo y fold.
<DT> = datetime(year, month, day, hour=0) # Crea fecha y hora. Admite minutos, segundos y zona horaria.
<TD> = timedelta(weeks=0, days=0, hours=0) # Crea una duración. También admite minutos y segundos.
Estas expresiones son esquemas de referencia, no código ejecutable directamente.
Ejemplo práctico
from datetime import date, time, datetime, timedelta
fecha = date(2026, 10, 21)
hora = time(14, 30, 0)
fecha_hora = datetime(2026, 10, 21, 14, 30)
duracion = timedelta(days=2, hours=3)
print(fecha) # 2026-10-21
print(hora) # 14:30:00
print(fecha_hora) # 2026-10-21 14:30:00
print(duracion) # 2 days, 3:00:00
Podemos acceder individualmente a los componentes de una fecha:
print(fecha_hora.year) # 2026
print(fecha_hora.month) # 10
print(fecha_hora.day) # 21
print(fecha_hora.hour) # 14
print(fecha_hora.minute) # 30
Si intentamos crear una fecha que no existe, Python genera un ValueError.
fecha = date(2026, 2, 30) # ValueError: el 30 de febrero no existe.
Obtener el día de la semana
El método weekday() devuelve el día de la semana como un número entero.
El lunes corresponde al 0 y el domingo al 6.
fecha = date(2026, 10, 21)
print(fecha.weekday())
# 2
En este caso devuelve 2 porque el 21 de octubre de 2026 es miércoles.
| Día | weekday() |
|---|---|
| Lunes | 0 |
| Martes | 1 |
| Miércoles | 2 |
| Jueves | 3 |
| Viernes | 4 |
| Sábado | 5 |
| Domingo | 6 |
También existe isoweekday(), que utiliza valores del 1 al 7.
print(fecha.isoweekday()) # 3
Fecha y hora actuales
Python permite obtener la fecha y hora del sistema mediante diferentes métodos.
<D> = date.today() # Devuelve la fecha local actual.
<DTn> = datetime.now() # Devuelve fecha y hora locales sin zona horaria.
<DTn> = datetime.today() # Otra forma de obtener fecha y hora locales sin zona horaria.
<DTa> = datetime.now(<tzinfo>) # Devuelve fecha y hora actuales con la zona indicada.
Ejemplo práctico
from datetime import date, datetime, timezone
hoy = date.today()
ahora = datetime.now()
ahora_utc = datetime.now(timezone.utc)
print("Fecha:", hoy)
print("Hora local:", ahora)
print("Hora UTC:", ahora_utc)
La salida dependerá del momento de ejecución y de la configuración horaria del sistema.
Cuando necesitamos trabajar con fechas que posteriormente se almacenarán en una base de datos o se compartirán entre servidores, es recomendable utilizar fechas con zona horaria explícita.
Por ejemplo:
ahora_utc = datetime.now(timezone.utc)
Así obtenemos una fecha y hora que identifica un instante concreto en UTC.
Extraer la fecha o la hora de un datetime
Podemos obtener únicamente la fecha o la hora a partir de un objeto datetime.
dt = datetime(2026, 10, 21, 14, 30, 45)
print(dt.date()) # 2026-10-21
print(dt.time()) # 14:30:45
Hay una diferencia entre time() y timetz() cuando trabajamos con zonas horarias:
from datetime import datetime
from zoneinfo import ZoneInfo
dt = datetime(2026, 10, 21, 14, 30, tzinfo=ZoneInfo("Europe/Madrid"))
print(dt.time()) # 14:30:00
print(dt.timetz()) # 14:30:00+02:00
date(): devuelve la fecha.time(): devuelve la hora, sin conservartzinfo.timetz(): devuelve la hora conservando la información de zona horaria.
Fechas naive y aware: ¿cuál es la diferencia?
Este es uno de los conceptos que conviene tener claros cuando trabajamos con fechas en Python.
Los objetos time y datetime pueden ser de dos tipos:
- Naive (sin zona horaria): no incluyen información suficiente para identificar de forma inequívoca un instante concreto.
- Aware (con zona horaria): incluyen información horaria que permite interpretar el instante respecto a UTC.
Ejemplo: datetime naive
from datetime import datetime
fecha = datetime(2026, 10, 21, 14, 30)
print(fecha)
# 2026-10-21 14:30:00
print(fecha.tzinfo)
# None
Sabemos que son las 14:30, pero no sabemos si esa hora corresponde a Madrid, Londres o Nueva York.
Python no asigna automáticamente una zona horaria a ese objeto. Algunas funciones lo interpretarán como hora local del sistema, pero esa interpretación depende de la operación utilizada.
Ejemplo: datetime aware
from datetime import datetime
from zoneinfo import ZoneInfo
fecha = datetime(
2026, 10, 21, 14, 30,
tzinfo=ZoneInfo("Europe/Madrid")
)
print(fecha)
# 2026-10-21 14:30:00+02:00
Ahora sí sabemos que corresponde a la zona horaria de Madrid y que en esa fecha tiene un desplazamiento de dos horas respecto a UTC.
En aplicaciones que trabajan con servidores, bases de datos o servicios repartidos por distintas regiones, utilizar objetos aware evita muchas ambigüedades.
Zonas horarias
Una zona horaria define cómo se relaciona la hora local de una región con UTC. No debemos confundir una zona horaria real con un desplazamiento fijo.
Por ejemplo, Madrid utiliza normalmente:
- UTC+01:00 durante el horario de invierno.
- UTC+02:00 durante el horario de verano.
Podemos representar un desplazamiento fijo mediante timezone, pero para gestionar automáticamente los cambios de horario es preferible utilizar ZoneInfo.
<tzinfo> = timezone.utc # Zona UTC, sin cambios estacionales.
<tzinfo> = timezone(<timedelta>) # Zona con desplazamiento fijo respecto a UTC.
<tzinfo> = ZoneInfo("Europe/Madrid") # Zona real con reglas históricas y estacionales.
<DTa> = <DT>.astimezone(<tzinfo>) # Convierte un datetime a otra zona horaria.
Ejemplo: convertir UTC a la hora de Madrid
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
fecha_utc = datetime(2026, 10, 21, 12, 30, tzinfo=timezone.utc)
fecha_madrid = fecha_utc.astimezone(ZoneInfo("Europe/Madrid"))
print(fecha_utc)
# 2026-10-21 12:30:00+00:00
print(fecha_madrid)
# 2026-10-21 14:30:00+02:00
Aunque las horas sean diferentes, ambos objetos representan exactamente el mismo instante.
Diferencia entre astimezone() y replace(tzinfo=...)
Estos dos métodos pueden parecer similares, pero realizan operaciones diferentes.
astimezone() convierte una fecha y hora a otra zona horaria, conservando el instante real.
fecha_utc = datetime(2026, 10, 21, 12, 30, tzinfo=timezone.utc)
fecha_madrid = fecha_utc.astimezone(ZoneInfo("Europe/Madrid"))
print(fecha_madrid)
# 2026-10-21 14:30:00+02:00
En cambio, replace(tzinfo=...) sustituye la información de zona horaria sin modificar los valores del reloj.
fecha = datetime(2026, 10, 21, 12, 30)
fecha_madrid = fecha.replace(tzinfo=ZoneInfo("Europe/Madrid"))
print(fecha_madrid)
# 2026-10-21 12:30:00+02:00
En este segundo caso estamos indicando que las 12:30 corresponden a Madrid, no convirtiendo las 12:30 UTC a la hora de Madrid.
Regla práctica: utiliza astimezone() para convertir instantes entre zonas. Utiliza replace(tzinfo=...) únicamente cuando quieras asignar una zona a una hora local y tengas claro qué instante representa.
Horario de verano y atributo fold
Los cambios de horario pueden producir situaciones especiales.
Cuando se adelanta el reloj, algunas horas locales dejan de existir. Cuando se retrasa, una misma hora puede aparecer dos veces.
Por ejemplo, durante el cambio al horario de invierno en Madrid, el 25 de octubre de 2026, las 02:30 aparecen dos veces:
- Una vez con desplazamiento UTC+02:00.
- Otra vez con desplazamiento UTC+01:00.
Python utiliza el atributo fold para distinguir ambas situaciones.
from datetime import datetime
from zoneinfo import ZoneInfo
madrid = ZoneInfo("Europe/Madrid")
primera = datetime(2026, 10, 25, 2, 30, tzinfo=madrid, fold=0)
segunda = datetime(2026, 10, 25, 2, 30, tzinfo=madrid, fold=1)
print(primera)
# 2026-10-25 02:30:00+02:00
print(segunda)
# 2026-10-25 02:30:00+01:00
Aunque ambas muestran las 02:30, corresponden a dos instantes diferentes.
El valor fold=0 identifica la primera aparición y fold=1 la segunda.
Este detalle resulta especialmente útil al analizar logs o eventos registrados durante cambios de horario.
Convertir texto en fechas
A menudo recibimos fechas como cadenas de texto procedentes de un CSV, una API o una base de datos.
Python permite convertirlas en objetos date, time y datetime.
<D/T/DT> = D/T/DT.fromisoformat(<str>) # Convierte una cadena ISO compatible en fecha u hora.
<DT> = datetime.strptime(<str>, '<format>') # Convierte una cadena utilizando un formato definido.
<D/DT> = D/DT.fromordinal(<int>) # Crea una fecha desde su número ordinal gregoriano.
<DTn> = datetime.fromtimestamp(<float>) # Convierte Unix timestamp a hora local sin tzinfo.
<DTa> = datetime.fromtimestamp(<float>, tz=<tzinfo>) # Convierte timestamp con zona horaria.
Utilizar fromisoformat()
El formato ISO 8601 es muy habitual para intercambiar fechas entre aplicaciones.
from datetime import date, datetime
fecha = date.fromisoformat("2026-10-21")
fecha_hora = datetime.fromisoformat("2026-10-21T14:30:00")
print(fecha)
# 2026-10-21
print(fecha_hora)
# 2026-10-21 14:30:00
También podemos incluir el desplazamiento respecto a UTC:
fecha = datetime.fromisoformat("2026-10-21T14:30:00+02:00")
print(fecha.tzinfo)
# UTC+02:00
Este último objeto contiene un desplazamiento fijo. No incorpora automáticamente las reglas históricas de una zona como Europe/Madrid.
Si la cadena no representa una fecha válida o un formato admitido, Python genera ValueError.
Utilizar strptime()
Cuando recibimos fechas con un formato personalizado podemos utilizar strptime().
Por ejemplo, imaginemos que una aplicación nos entrega fechas de la siguiente forma:
21/10/2026 14:30:00
Podemos convertirlas indicando cómo están organizados sus componentes:
from datetime import datetime
texto = "21/10/2026 14:30:00"
fecha = datetime.strptime(texto, "%d/%m/%Y %H:%M:%S")
print(fecha)
# 2026-10-21 14:30:00
Los símbolos %d, %m, %Y, %H, %M y %S indican qué representa cada parte de la cadena.
Unix timestamp: tiempo desde 1970
En sistemas Linux, APIs y bases de datos es habitual trabajar con marcas de tiempo Unix (Unix timestamps).
Un timestamp representa la cantidad de segundos transcurridos desde el 1 de enero de 1970 a las 00:00:00 UTC, sin contar los segundos intercalares.
from datetime import datetime, timezone
fecha = datetime.fromtimestamp(0, tz=timezone.utc)
print(fecha)
# 1970-01-01 00:00:00+00:00
También podemos transformar una fecha en un timestamp:
fecha = datetime(2026, 10, 21, 12, 30, tzinfo=timezone.utc)
timestamp = fecha.timestamp()
print(timestamp)
# 1792585800.0
Cuando trabajamos con timestamps, resulta recomendable indicar explícitamente la zona horaria para evitar que Python interprete una fecha naive como hora local del sistema.
Convertir fechas en cadenas
La operación inversa consiste en transformar un objeto date o datetime en una cadena de texto.
<str> = <D/T/DT>.isoformat() # Convierte el objeto a una cadena compatible con ISO 8601.
<str> = <D/T/DT>.strftime('<format>') # Devuelve una cadena con el formato especificado.
<int> = <D/DT>.toordinal() # Devuelve el ordinal de la fecha.
<float> = <DT>.timestamp() # Devuelve los segundos desde la época Unix.
Ejemplo con isoformat()
from datetime import datetime
fecha = datetime(2026, 10, 21, 14, 30, 45)
print(fecha.isoformat())
# 2026-10-21T14:30:45
print(fecha.isoformat(sep=" "))
# 2026-10-21 14:30:45
El argumento sep corresponde a datetime.isoformat(). No se utiliza del mismo modo en date.isoformat() ni en time.isoformat().
Personalizar el formato con strftime()
Podemos crear representaciones personalizadas para mostrar fechas en informes, interfaces web o registros.
from datetime import datetime
fecha = datetime(2026, 10, 21, 14, 30)
print(fecha.strftime("%d/%m/%Y"))
# 21/10/2026
print(fecha.strftime("%Y-%m-%d %H:%M"))
# 2026-10-21 14:30
print(fecha.strftime("%H:%M:%S"))
# 14:30:00
Tabla de formatos más utilizados
| Código | Significado | Ejemplo |
|---|---|---|
%Y |
Año con cuatro cifras. | 2026 |
%y |
Año con dos cifras. | 26 |
%m |
Mes numérico. | 10 |
%d |
Día del mes. | 21 |
%H |
Hora en formato 24 horas. | 14 |
%I |
Hora en formato 12 horas. | 02 |
%M |
Minutos. | 30 |
%S |
Segundos. | 45 |
%f |
Microsegundos. | 123456 |
%a |
Nombre abreviado del día. | Wed |
%A |
Nombre completo del día. | Wednesday |
%b |
Nombre abreviado del mes. | Oct |
%B |
Nombre completo del mes. | October |
%p |
Indicador AM/PM. | PM |
%z |
Desplazamiento respecto a UTC. | +0200 |
%Z |
Nombre de la zona horaria. | CEST |
Los nombres de días y meses dependen de la configuración regional (locale) del sistema.
Por ejemplo, con una configuración regional española, %A puede devolver miércoles en lugar de Wednesday.
Ejemplo de formato con zona horaria
from datetime import datetime
texto = "2025-08-14 23:39:00.00 +0200"
dt = datetime.strptime(texto, "%Y-%m-%d %H:%M:%S.%f %z")
resultado = dt.strftime("%d/%m/%Y %H:%M:%S %z")
print(resultado)
# 14/08/2025 23:39:00 +0200
Los códigos %z y %Z tienen funciones diferentes:
%zrepresenta el desplazamiento numérico respecto a UTC.%Zrepresenta el nombre de la zona horaria cuando está disponible.
Por ejemplo, +0200 indica que la hora está adelantada dos horas respecto a UTC, pero no identifica por sí solo una zona geográfica concreta.
Aritmética con fechas y horas
Python permite sumar, restar y comparar fechas utilizando los objetos date, datetime y timedelta.
La clase timedelta representa una duración.
Podemos crear intervalos en semanas, días, horas, minutos, segundos, milisegundos y microsegundos.
from datetime import timedelta
duracion = timedelta(days=2, hours=3, minutes=30)
print(duracion)
# 2 days, 3:30:00
Internamente, timedelta normaliza la duración utilizando días, segundos y microsegundos.
Sumar y restar días
from datetime import date, timedelta
fecha = date(2026, 10, 21)
manana = fecha + timedelta(days=1)
ayer = fecha - timedelta(days=1)
print(manana) # 2026-10-22
print(ayer) # 2026-10-20
También podemos sumar horas o minutos a un datetime.
from datetime import datetime, timedelta
fecha = datetime(2026, 10, 21, 14, 30)
nueva_fecha = fecha + timedelta(hours=2, minutes=15)
print(nueva_fecha)
# 2026-10-21 16:45:00
Calcular diferencias entre fechas
Restar dos objetos date o datetime devuelve un timedelta.
from datetime import date
inicio = date(2026, 10, 21)
fin = date(2026, 11, 1)
diferencia = fin - inicio
print(diferencia.days)
# 11
Si necesitamos obtener el total de segundos de un intervalo utilizamos total_seconds().
from datetime import timedelta
duracion = timedelta(days=1, hours=2)
print(duracion.total_seconds())
# 93600.0
No debemos confundir total_seconds() con el atributo seconds.
El atributo seconds contiene únicamente la parte de segundos normalizada dentro del día, mientras que total_seconds() devuelve la duración completa.
Operaciones con timedelta
duracion = timedelta(hours=3)
print(duracion * 2)
# 6:00:00
print(duracion / 2)
# 1:30:00
print(abs(timedelta(hours=-3)))
# 3:00:00
También podemos dividir dos intervalos para obtener una proporción:
a = timedelta(hours=6)
b = timedelta(hours=2)
print(a / b)
# 3.0
Comparaciones de fechas
Podemos utilizar los operadores habituales de comparación:
from datetime import date
fecha1 = date(2026, 10, 21)
fecha2 = date(2026, 10, 25)
print(fecha1 < fecha2) # True
print(fecha1 == fecha2) # False
print(fecha1 > fecha2) # False
Las comparaciones entre objetos datetime naive y aware necesitan especial atención.
No podemos ordenar directamente un datetime naive y otro aware:
from datetime import datetime, timezone
sin_zona = datetime(2026, 10, 21, 14, 30)
con_zona = datetime(2026, 10, 21, 14, 30, tzinfo=timezone.utc)
# Genera TypeError:
# print(sin_zona < con_zona)
Antes de compararlos, debemos determinar qué zona representa la fecha naive y tratar ambos objetos de forma coherente.
Los cambios de horario también afectan a los cálculos
Si restamos dos objetos datetime aware que comparten exactamente el mismo objeto tzinfo, Python calcula la diferencia utilizando sus valores locales y no tiene en cuenta el cambio de desplazamiento horario.
Por ejemplo, en un cambio al horario de invierno, las diferencias entre horas locales y tiempo real transcurrido pueden no coincidir.
Si necesitamos medir el tiempo real entre dos instantes, lo más seguro es convertir ambos a UTC antes de realizar la resta.
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
madrid = ZoneInfo("Europe/Madrid")
inicio = datetime(2026, 10, 25, 1, 30, tzinfo=madrid)
fin = datetime(2026, 10, 25, 3, 30, tzinfo=madrid)
# Diferencia entre las horas locales.
print(fin - inicio)
# 2:00:00
# Tiempo real transcurrido.
inicio_utc = inicio.astimezone(timezone.utc)
fin_utc = fin.astimezone(timezone.utc)
print(fin_utc - inicio_utc)
# 3:00:00
En este ejemplo han transcurrido tres horas reales porque durante ese intervalo el reloj se retrasó una hora.
Este comportamiento debe tenerse en cuenta al calcular duraciones de sesiones, eventos y trabajos programados durante cambios de horario.
Referencia rápida de operaciones
<bool> = <D/DT> > <D/DT> # Compara fechas compatibles. También admite ==, <, <= y >=.
<TD> = <D/DT> - <D/DT> # Calcula la diferencia entre dos fechas u horas.
<D/DT> = <D/DT> + <TD> # Suma una duración a una fecha u hora.
<D/DT> = <D/DT> - <TD> # Resta una duración.
<TD> = <TD> * <float> # Multiplica una duración por un número.
<TD> = <TD> / <float> # Divide una duración entre un número.
<float> = <TD> / <TD> # Calcula la proporción entre dos duraciones.
<float> = <TD>.total_seconds() # Devuelve la duración completa en segundos.
Los ejemplos anteriores son esquemas de consulta y no representan todas las combinaciones de operaciones admitidas.
Significado de las abreviaturas
| Abreviatura | Significado |
|---|---|
D |
Objeto date (fecha). |
T |
Objeto time (hora). |
DT |
Objeto datetime (fecha y hora). |
DTn |
Objeto datetime naive, sin zona horaria. |
DTa |
Objeto datetime aware, con zona horaria. |
TD |
Objeto timedelta (duración). |
tzinfo |
Información de zona horaria. |
fold |
Distingue horas repetidas durante cambios de horario. |
Resumen de funciones más utilizadas
| Función o método | Utilidad |
|---|---|
date.today() |
Obtiene la fecha local actual. |
datetime.now() |
Obtiene fecha y hora locales. |
datetime.now(timezone.utc) |
Obtiene fecha y hora actuales en UTC. |
datetime.fromisoformat() |
Convierte una cadena ISO a datetime. |
datetime.strptime() |
Interpreta una fecha usando un formato. |
datetime.strftime() |
Convierte una fecha en texto personalizado. |
datetime.isoformat() |
Genera una representación ISO. |
datetime.timestamp() |
Convierte una fecha en timestamp Unix. |
datetime.fromtimestamp() |
Convierte un timestamp Unix en fecha. |
datetime.astimezone() |
Convierte un datetime a otra zona horaria. |
datetime.date() |
Extrae la fecha de un datetime. |
datetime.time() |
Extrae la hora sin conservar la zona horaria. |
datetime.timetz() |
Extrae la hora conservando tzinfo. |
timedelta() |
Representa un intervalo de tiempo. |
timedelta.total_seconds() |
Obtiene la duración total en segundos. |
ZoneInfo() |
Representa una zona horaria geográfica con sus reglas. |
weekday() |
Devuelve el día de la semana entre 0 y 6. |
En proyectos donde trabajamos con servidores, registros de eventos o bases de datos, suelo preferir almacenar los instantes en UTC y convertirlos a la zona horaria correspondiente cuando necesito mostrarlos.
Así evito depender de la configuración regional del servidor y reduzco los problemas provocados por los cambios de horario.
Para duraciones o intervalos, utilizo timedelta; para fechas y horas que representan instantes concretos, prefiero objetos datetime aware.
Argumentos
En la llamada a una función
func(<positional_args>) # func(0, 0)
func(<keyword_args>) # func(x=0, y=0)
func(<positional_args>, <keyword_args>) # func(0, y=0)
En la definición de una función
def func(<nondefault_args>): ... # def func(x, y): ...
def func(<default_args>): ... # def func(x=0, y=0): ...
def func(<nondefault_args>, <default_args>): ... # def func(x, y=0): ...
- Los valores por defecto se evalúan cuando la función se encuentra por primera vez en el ámbito (scope).
- ¡Cualquier mutación de un valor por defecto mutable persistirá entre invocaciones!
Explicación:
El "error" del principiante
En Python, los argumentos por defecto no se crean de nuevo cada vez que llamas a la función. Se crean una sola vez cuando el programa lee la definición de la función (def...).
¿Por qué es un problema con objetos mutables?
Si usas una lista o un diccionario como valor por defecto, todos los que llamen a esa función compartirán la misma instancia del objeto.
Ejemplo clásico del error:
def añadir_item(item, lista=[]): # La lista se crea una sola vez aquí
lista.append(item)
return lista
print(añadir_item(1)) # Resultado: [1]
print(añadir_item(2)) # Resultado: [1, 2] <-- ¡El 1 se quedó guardado!
Usa el código con precaución.
¿Cómo se soluciona?
La convención en Python es usar None como valor por defecto y crear el objeto dentro de la función:
def añadir_item(item, lista=None):
if lista is None:
lista = [] # Creamos una lista nueva cada vez que sea None
lista.append(item)
return lista
Usa el código con precaución.
En resumen: Nunca utiliza [] o {} directamente en los argumentos de una función si planeas modificarlos, porque se convertirán en una especie de "memoria" compartida entre todas las llamadas.
Operadores de desempaquetado
En la llamada a una función
El Splat (*) expande una colección en argumentos posicionales, mientras que el Splatty-Splat (``) expande un diccionario en argumentos de palabra clave (keyword arguments).
args = (1, 2)
kwargs = {'x': 3, 'y': 4, 'z': 5}
func(*args, kwargs)
Es lo mismo que:
func(1, 2, x=3, y=4, z=5)
En la definición de una función
El Splat (*) combina cero o más argumentos posicionales en una tupla, mientras que el Splatty-Splat (``) combina cero o más argumentos de palabra clave en un diccionario.
def add(*a):
return sum(a)
>>> add(1, 2, 3)
6
Combinaciones de argumentos legales:
def f(*args): ... # f(1, 2, 3)
def f(x, *args): ... # f(1, 2, 3)
def f(*args, z): ... # f(1, 2, z=3)
def f(kwargs): ... # f(x=1, y=2, z=3)
def f(x, kwargs): ... # f(x=1, y=2, z=3) | f(1, y=2, z=3)
def f(*args, kwargs): ...# f(x=1,y=2,z=3) | f(1,y=2,z=3) | f(1,2,z=3) | f(1,2,3)
def f(x, *args, kwargs): # f(x=1,y=2,z=3) | f(1,y=2,z=3) | f(1,2,z=3) | f(1,2,3)
def f(*args, y, kwargs): # f(x=1, y=2, z=3) | f(1, y=2, z=3)
def f(*, x, y, z): ... # f(x=1, y=2, z=3)
def f(x, *, y, z): ... # f(x=1, y=2, z=3) | f(1, y=2, z=3)
def f(x, y, *, z): ... # f(x=1, y=2, z=3) | f(1, y=2, z=3) | f(1, 2, z=3)
Otros usos
<list> = [*<coll.> [, ...]] # O bien: lista(<collection>) [+ ...]
<tuple> = (*<coll.>, [...]) # O bien: tupla(<collection>) [+ ...]
<set> = {*<coll.> [, ...]} # O bien: conjunto(<collection>) [| ...]
<dict> = {<dict> [, ...]} # O bien: <dict> | ... (since 3.9)
head, *body, tail = <coll.> # Head or tail puede ser omitted.
Expresiones en línea
Funciones lambda
<func> = lambda: <return_value> # A único statement función.
<func> = lambda <arg_1>, <arg_2>: <return_value> # También permite valor predeterminado argumentos.
Comprensiones
<list> = [i+1 for i in range(10)] # O bien: [1, 2, ..., 10]
<iter> = (i for i in range(10) if i > 5) # O bien: iter([6, 7, 8, 9])
<set> = {i+5 for i in range(10)} # O bien: {5, 6, ..., 14}
<dict> = {i: i*2 for i in range(10)} # O bien: {0: 0, 1: 2, ..., 9: 18}
>>> [l+r for l in 'abc' for r in 'abc'] # Inner loop is en el right side.
['aa', 'ab', 'ac', ..., 'cc']
Map, filter y reduce
from functools import reduce
<iter> = map(lambda x: x + 1, range(10)) # O bien: iter([1, 2, ..., 10])
<iter> = filter(lambda x: x > 5, range(10)) # O bien: iter([6, 7, 8, 9])
<obj> = reduce(lambda out, x: out + x, range(10)) # O bien: 45
Any y all
<bool> = any(<collection>) # Is `bool(<el>)` True for cualquier el?
<bool> = all(<collection>) # True for todos los? También True si vacío.
Expresión condicional
<obj> = <exp> if <condition> else <exp> # Solo se evalúa una de las expresiones.
>>> [a if a else 'zero' for a in (0, 1, 2, 3)] # `any([0, '', [], None]) == False`
['zero', 1, 2, 3]
Tuplas con nombres, enumeraciones y dataclasses
from collections import namedtuple
Point = namedtuple('Point', 'x y') # Crea una tupla's subclass.
point = Point(0, 0) # Devuelve its instance.
from enum import Enum
Direction = Enum('Direction', 'N E S W') # Crea un enum.
direction = Direction.N # Devuelve its member.
from dataclasses import make_dataclass
Player = make_dataclass('Player', ['loc', 'dir']) # Crea una clase.
player = Player(point, direction) # Devuelve its instance.
Importaciones
Mecanismo que permite que el código de un archivo esté disponible para otro archivo.
import <module> # Importa a built-in or '<module>.py'.
import <package> # Importa a built-in or '<package>/__init__.py'.
import <package>.<module> # Importa a built-in or '<package>/<module>.py'.
- Un paquete es una colección de módulos, pero también puede definir sus propios objetos.
- En un sistema de archivos, esto corresponde a un directorio de archivos Python con un script de inicialización opcional.
- Ejecutar
import <paquete>no proporciona acceso automático a los módulos del paquete a menos que se importen explícitamente en su script de inicialización. - La ubicación del archivo que se pasa al comando
pythonsirve como raíz de todas las importaciones locales. - Para importaciones relativas, usa
from .[…][<pkg/module>[.…]] import <obj>.
Clausuras
En Python, se produce un cierre cuando una función anidada hace referencia a un valor de su función contenedora y, posteriormente, la función contenedora devuelve la función anidada.
def get_multiplier(a):
def out(b):
return a * b
return out
>>> multiply_by_3 = get_multiplier(3)
>>> multiply_by_3(10)
30
- Cualquier valor al que se haga referencia desde dentro de varias funciones anidadas se comparte.
Aplicación parcial
from functools import partial
<function> = partial(<function> [, <arg_1> [, ...]])
>>> def multiply(a, b):
... return a * b
>>> multiply_by_3 = partial(multiply, 3)
>>> multiply_by_3(10)
30
- La propiedad
partialtambién resulta útil cuando se necesita pasar una función como argumento, ya que permite definir sus argumentos de antemano. - Algunos ejemplos son:
defaultdict(<func>),iter(<func>, to_exc)y el métodofield(default_factory=<func>)de la clase de datos.
Variables no locales
Si una variable se asigna a cualquier parte del ámbito, se considera una variable local, a menos que se declare como 'global' o 'nonlocal'.
def get_counter():
i = 0
def out():
nonlocal i
i += 1
return i
return out
>>> counter = get_counter()
>>> counter(), counter(), counter()
(1, 2, 3)
Decoradores
- Un decorador toma una función, le agrega alguna funcionalidad y la devuelve.
- Puede ser cualquier callable, pero generalmente se implementa como una función que devuelve un closure.
@decorator_name
def function_that_gets_passed_to_decorator():
...
Ejemplo de depuración
Decorador que imprime el nombre de la función cada vez que se llama a la función.
from functools import wraps
def debug(func):
@wraps(func)
def out(*args, kwargs):
print(func.__name__)
return func(*args, kwargs)
return out
@debug
def add(x, y):
return x + y
Wrapses un decorador auxiliar que copia los metadatos de la función pasada (func) a la función que envuelve (out).- Sin él,
add.__name__devolveríaout.
Caché LRU
Decorador que almacena en caché los valores de retorno de la función. Todos los argumentos de la función deben ser hasheables.
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
return n if n < 2 else fib(n-2) + fib(n-1)
- El tamaño predeterminado de la caché es de 128 valores. Si se pasa
maxsize=None, no tendrá límite. - El intérprete de CPython limita la profundidad de recursión a 1000 por defecto. Para aumentarla, utilice
sys.setrecursionlimit(<int>).
Decorador parametrizado
Un decorador que acepta argumentos y devuelve un decorador normal que acepta una función.
from functools import wraps
def debug(print_result=False):
def decorator(func):
@wraps(func)
def out(*args, kwargs):
result = func(*args, kwargs)
print(func.__name__, result if print_result else '')
return result
return out
return decorator
@debug(print_result=True)
def add(x, y):
return x + y
- Utilizar únicamente
'@debug'para decorar la función add() no funcionaría en este caso, ya que debug recibiría la función add() como un argumento 'print_result'. Sin embargo, los decoradores pueden comprobar manualmente si el argumento recibido es una función y actuar en consecuencia.
Clases
Una plantilla para crear objetos definidos por el usuario.
class MyClass:
def __init__(self, a):
self.a = a
def __str__(self):
return str(self.a)
def __repr__(self):
class_name = self.__class__.__name__
return f'{class_name}({self.a!r})'
@classmethod
def get_class_name(cls):
return cls.__name__
- El valor de retorno de
str()debe ser legible y el derepr()inequívoco. - Si solo se define
repr(), también se utilizará parastr(). - Los métodos decorados con
@staticmethodno recibenselfniclscomo primer argumento.
>>> obj = MyClass(1)
>>> obj.a, str(obj), repr(obj)
(1, '1', 'MyClass(1)')
Expressions that call the str() método:
print(<el>)
f'{<el>}'
logging.warning(<el>)
csv.writer(<file>).writerow([<el>])
raise Exception(<el>)
Expressions that call the repr() método:
print/str/repr([<el>])
print/str/repr({<el>: <el>})
f'{<el>!r}'
Z = dataclasses.make_dataclass('Z', ['a']); print/str/repr(Z(<el>))
>>> <el>
Herencia
class Person:
def __init__(self, name):
self.name = name
class Employee(Person):
def __init__(self, name, staff_num):
super().__init__(name)
self.staff_num = staff_num
varios inheritance:
class A: pass
class B: pass
class C(A, B): pass
MRO determina el orden en que se recorren las clases padre al buscar un método o un atributo:
>>> C.mro()
[<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>]
Anotaciones de tipos
- Añaden sugerencias de tipo a variables, argumentos y funciones (
'def f() -> <type>:'). - Estas sugerencias son utilizadas por verificadores de tipos como mypy, bibliotecas de validación de datos como Pydantic y, últimamente, también por el compilador Cython. Sin embargo, el intérprete CPython no las aplica.
from collections import abc
<name>: <type> [| ...] [= <obj>] # `|` since 3.10.
<name>: list/set/abc.Iterable/abc.Sequence[<type>] [= <obj>] # Since 3.9.
<name>: dict/tuple[<type>, ...] [= <obj>] # Since 3.9.
Clases de datos
Decorador que utiliza variables de clase para generar los métodos especiales init(), repr() y eq().
from dataclasses import dataclass, field, make_dataclass
@dataclass(order=False, frozen=False)
class <class_name>:
<attr_name>: <type>
<attr_name>: <type> = <default_value>
<attr_name>: list/dict/set = field(default_factory=list/dict/set)
- Los objetos pueden hacerse ordenables con
'order=True'e inmutables con'frozen=True'. - Para que un objeto sea hashable, todos sus atributos deben ser hashables y 'frozen' debe ser True.
- La función
field()es necesaria porque<attr_name>: list = []crearía una lista compartida entre todas las instancias. Su argumentodefault_factorypuede ser cualquier callable. - Para atributos de tipo arbitrario, usa
'typing.Any'.
<class> = make_dataclass('<class_name>', <coll_of_attribute_names>)
<class> = make_dataclass('<class_name>', <coll_of_tuples>)
<tuple> = ('<attr_name>', <type> [, <default_value>])
Propiedades
Forma pythónica de implementar métodos getter y setter.
class Person:
@property
def name(self):
return ' '.join(self._name)
@name.setter
def name(self, value):
self._name = value.split()
>>> person = Person()
>>> person.name = '\t Guido van Rossum \n'
>>> person.name
'Guido van Rossum'
Slots
Mecanismo que restringe los objetos a los atributos enumerados en las 'ranuras', reduciendo así su consumo de memoria.
class MyClassWithSlots:
__slots__ = ['a']
def __init__(self):
self.a = 1
Copias
from copy import copy, deepcopy
<object> = copy/deepcopy(<object>)
Tipado pato (duck typing)
Un tipo Duck es un tipo implícito que prescribe un conjunto de métodos especiales. Cualquier objeto que tenga definidos esos métodos se considera miembro de ese tipo Duck.
Comparación
- Si el método
eq()no se ha sobrescrito, devuelveid(self) == id(other), que es lo mismo queself is other. - Esto significa que, por defecto, todos los objetos se comparan como no iguales.
- Solo se llama al método
eq()del objeto de la izquierda, a menos que devuelvaNotImplemented, en cuyo caso se consulta al objeto de la derecha. Se devuelvefalsesi ambos devuelvenNotImplemented. ne()funciona automáticamente en cualquier objeto que tengaeq()definido.
class MyComparable:
def __init__(self, a):
self.a = a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
Objetos hashables
- Los objetos hashables necesitan los métodos
hash()yeq(), y su valor hash nunca debe cambiar. - Los objetos hashables que se comparan como iguales deben tener el mismo valor hash; es decir, el método
hash()predeterminado que devuelveid(self)no funcionará. - Por eso Python hace que las clases no sean hashables automáticamente si solo se implementa
eq().
class MyHashable:
def __init__(self, a):
self._a = a
@property
def a(self):
return self._a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
def __hash__(self):
return hash(self.a)
Ordenación
- Con el decorador 'total_ordering', solo necesita proporcionar
eq()y uno de los métodos especialeslt(),gt(),le()oge(); el resto se generará automáticamente. - Las funciones
sorted()ymin()solo requieren el métodolt(), mientras quemax()solo requieregt(). Sin embargo, es recomendable definirlas todas para evitar confusiones en otros contextos. - Cuando se comparan dos listas, cadenas o clases de datos, sus valores se comparan en orden hasta encontrar un par de valores diferentes. A continuación, se devuelve la comparación de estos dos valores. La secuencia más corta se considera menor en caso de que todos los valores sean iguales.
- Para ordenar una colección de cadenas en orden alfabético, pase
key=locale.strxfrmasorted()después de ejecutarlocale.setlocale(locale.LC_COLLATE, "en_US.UTF-8").
from functools import total_ordering
@total_ordering
class MySortable:
def __init__(self, a):
self.a = a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
def __lt__(self, other):
if isinstance(other, type(self)):
return self.a < other.a
return NotImplemented
Iteradores
- Cualquier objeto que tenga los métodos siguiente() e iter() es un iterador.
- siguiente() debe devolver el siguiente elemento o generar una excepción StopIteration.
- Iter() debe devolver 'self'.
class Counter:
def __init__(self):
self.i = 0
def __next__(self):
self.i += 1
return self.i
def __iter__(self):
return self
>>> counter = Counter()
>>> next(counter), next(counter), next(counter)
(1, 2, 3)
Python cuenta con diversos objetos iteradores:
- Iteradores de secuencia devueltos por la función
iter(), comolist_iteratoryset_iterator. - Objetos devueltos por el módulo
itertools, comocount,repeatycycle. - Generadores devueltos por las funciones generadoras y las expresiones generadoras.
- Objetos de archivo devueltos por la función open(), etc.
Objetos invocables
- Todas las funciones y clases tienen un método call(), por lo tanto, son invocables.
- Para comprobar si un objeto es invocable, utilice
callable(<obj>),isinstance(<obj>, collections.abc.Callable)oisinstance(<obj>, typing.Callable). - Cuando esta guía utiliza
<function>como argumento, se refiere a<callable>.
class Counter:
def __init__(self):
self.i = 0
def __call__(self):
self.i += 1
return self.i
>>> counter = Counter()
>>> counter(), counter(), counter()
(1, 2, 3)
Administrador de contexto
- Las sentencias
withsolo funcionan con objetos que tienen los métodos especialesenter()yexit(). Enter()debe bloquear los recursos y, opcionalmente, devolver un objeto.Exit()debe liberar los recursos.- Cualquier excepción que ocurra dentro del bloque
withse pasa al métodoexit(). - El método
exit()puede suprimir la excepción devolviendo un valor verdadero.
class MyOpen:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename)
return self.file
def __exit__(self, exc_type, exception, traceback):
self.file.close()
>>> with open('test.txt', 'w') as file:
... file.write('Hello World!')
>>> with MyOpen('test.txt') as file:
... print(file.read())
Hello World!
Protocolos de objetos iterables
Iterable
- El único método requerido es
iter(). Debe devolver un iterador de los elementos del objeto. Contains()funciona automáticamente en cualquier objeto que tenga definido el métodoiter().
class MyIterable:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
>>> obj = MyIterable([1, 2, 3])
>>> [el for el in obj]
[1, 2, 3]
>>> 1 in obj
True
Colecciones
- Los únicos métodos necesarios son
iter()ylen().len()debe devolver el número de elementos. - Esta guía rápida en realidad se refiere a
<iterable>cuando usa<collection>. - Decidí no usar el nombre "iterable" porque suena más intimidante y vago que "colección". El único inconveniente de esta decisión es que el lector podría pensar que cierta función no acepta iteradores cuando sí los acepta, ya que los iteradores son los únicos objetos integrados que son iterables pero no son colecciones.
class MyCollection:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
def __len__(self):
return len(self.a)
Secuencias
- Los únicos métodos obligatorios son
getitem()ylen(). getitem()debe devolver un elemento en el índice especificado o generar unIndexError.iter()ycontains()funcionan automáticamente en cualquier objeto que tenga definidogetitem().reversed()funciona automáticamente en cualquier objeto que tenga definidosgetitem()ylen().
class MySequence:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
def __len__(self):
return len(self.a)
def __getitem__(self, i):
return self.a[i]
def __reversed__(self):
return reversed(self.a)
Discrepancias entre las definiciones del glosario y las clases base abstractas:
- El glosario define iterable como cualquier objeto con iter() o getitem() y secuencia como cualquier objeto con getitem() y len(). No define colección.
- Al pasar ABC Iterable a isinstance() o issubclass(), se comprueba si el objeto/clase tiene el método iter(), mientras que ABC colección comprueba iter(), contains() y len().
Clase base abstracta Sequence
- Es una interfaz más completa que la de secuencia básica.
- Al extenderla, genera iter(), contains(), reversed(), índice() y count().
- A diferencia de
'abc.Iterable'y'abc.Collection', no es un tipo duck. Por eso,issubclass(MySequence, abc.Sequence)devolvería False incluso si MySequence tuviera todos los métodos definidos. Sin embargo, reconoce lista, tupla, range, str, bytes, bytearray, array, memoryview y deque, ya que están registrados como subclases virtuales de secuencia.
from collections import abc
class MyAbcSequence(abc.Sequence):
def __init__(self, a):
self.a = a
def __len__(self):
return len(self.a)
def __getitem__(self, i):
return self.a[i]
Tabla de métodos especiales requeridos y disponibles automáticamente:
| Método | Iterable | Collection | Sequence | abc.Sequence |
|---|---|---|---|---|
| iter() | ! | ! | ✓ | ✓ |
| contains() | ✓ | ✓ | ✓ | ✓ |
| len() | ! | ! | ! | |
| getitem() | ! | ! | ||
| reversed() | ✓ | ✓ | ||
| index() | ✓ | |||
| count() | ✓ |
- El método
iter()es necesario para queisinstance(<obj>, abc.Iterable)devuelvaTrue. Sin embargo, cualquier objeto congetitem()funcionará con cualquier código que espere un iterable. - Otras clases ABC extensibles: MutableSequence, conjunto, MutableSet, Mapping, MutableMapping.
- Los nombres de sus métodos necesarios se almacenan en
<abc>.__abstractmethods__.
Enumeraciones
Clase de constantes con nombre llamadas miembros.
from enum import Enum, auto
class <enum_name>(Enum):
<member_name> = auto() # Increment del último numeric valor or 1.
<member_name> = <value> # valores don't have to be hashable.
<member_name> = <el_1>, <el_2> # valores puede ser colecciones (this is a tupla).
- Los métodos reciben el miembro sobre el que fueron llamados como argumento 'self'.
- Acceder a un miembro cuyo nombre coincide con una palabra clave reservada provoca un error de sintaxis.
<member> = <enum>.<member_name> # Devuelve un miembro de la enumeración. Lanza AttributeError.
<member> = <enum>['<member_name>'] # Devuelve un miembro de la enumeración. Lanza KeyError.
<member> = <enum>(<value>) # Devuelve un miembro de la enumeración. Lanza ValueError.
<str> = <member>.name # Devuelve member's name.
<obj> = <member>.value # Devuelve member's valor.
<list> = list(<enum>) # Devuelve enum's members.
<list> = [a.name for a in <enum>] # Devuelve enum's member names.
<list> = [a.value for a in <enum>] # Devuelve enum's member valores.
<enum> = type(<member>) # Devuelve member's enum.
<iter> = itertools.cycle(<enum>) # Devuelve endless iterator de members.
<member> = random.choice(list(<enum>)) # Devuelve un miembro aleatorio.
Expresiones en línea
Cutlery = Enum('Cutlery', 'FORK KNIFE SPOON')
Cutlery = Enum('Cutlery', ['FORK', 'KNIFE', 'SPOON'])
Cutlery = Enum('Cutlery', {'FORK': 1, 'KNIFE': 2, 'SPOON': 3})
Las funciones definidas por el usuario no pueden ser valores, por lo que deben estar encapsuladas:
from functools import partial
LogicOp = Enum('LogicOp', {'AND': partial(lambda l, r: l and r),
'OR': partial(lambda l, r: l or r)})
Excepciones
try:
<code>
except <exception>:
<code>
Ejemplo completo
try:
<code_1>
except <exception_a>:
<code_2_a>
except <exception_b>:
<code_2_b>
else:
<code_2_c>
finally:
<code_3>
- El código dentro del bloque
elsesolo se ejecutará si el bloquetryno generó excepciones. - El código dentro del bloque
finallysiempre se ejecutará (a menos que se reciba una señal). - Todas las variables inicializadas en los bloques ejecutados también son visibles en todos los bloques subsiguientes, así como fuera de la instrucción
try(solo el bloque de función delimita el ámbito). - Para capturar señales, utilice
signal.signal(signal_number, <func>).
Captura de excepciones
except <exception>: ...
except <exception> as <name>: ...
except (<exception>, [...]): ...
except (<exception>, [...]) as <name>: ...
- También captura subclases de la excepción.
- Utilice
'traceback.print_exc()'para imprimir el mensaje de error completo en stderr. - Utilice
'print(<nombre>)'para imprimir solo la causa de la excepción (sus argumentos). - Utilice
'logging.exception(<mensaje>)'para registrar el mensaje pasado, seguido del mensaje de error completo de la excepción capturada. Para más detalles, consulte logging. - Utilice
'sys.exc_info()'para obtener el tipo de excepción, el objeto y el rastreo de la excepción capturada.
Lanzar excepciones
raise <exception>
raise <exception>()
raise <exception>(<el> [, ...])
Re-raising caught excepción:
except <exception> [as <name>]:
...
raise
Objeto de excepción
arguments = <name>.args
exc_type = <name>.__class__
filename = <name>.__traceback__.tb_frame.f_code.co_filename
func_name = <name>.__traceback__.tb_frame.f_code.co_name
line = linecache.getline(filename, <name>.__traceback__.tb_lineno)
trace_str = ''.join(traceback.format_tb(<name>.__traceback__))
error_msg = ''.join(traceback.format_exception(type(<name>), <name>, <name>.__traceback__))
Excepciones integradas
BaseException
├── SystemExit # Se produce al llamar a la función sys.exit().
├── KeyboardInterrupt # Se produce cuando el usuario pulsa Ctrl+C.
└── Exception # Clase base recomendada para las excepciones definidas por el usuario.
├── ArithmeticError # Clase base para errores aritméticos, como ZeroDivisionError.
├── AssertionError # Se produce cuando una expresión assert es falsa.
├── AttributeError # Se produce cuando un objeto no tiene el atributo o método solicitado.
├── EOFError # Se produce cuando input() encuentra el final de la entrada.
├── LookupError # Clase base para errores al buscar elementos en colecciones.
│ ├── IndexError # Se produce cuando un índice está fuera del rango de una secuencia.
│ └── KeyError # Se produce cuando falta una clave en un diccionario o un elemento en un conjunto.
├── MemoryError # Se produce cuando no hay memoria suficiente para completar una operación.
├── NameError # Se produce cuando se utiliza un nombre de variable, función o clase inexistente.
│ └── UnboundLocalError # Se produce al acceder a una variable local antes de asignarle un valor.
├── OSError # Errores del sistema operativo, como FileExistsError o TimeoutError.
│ └── ConnectionError # Errores de conexión, como BrokenPipeError o ConnectionAbortedError.
├── RuntimeError # Se produce cuando un error no encaja en otra categoría específica.
│ ├── NotImplementedError # Se utiliza en métodos abstractos o funcionalidades aún no implementadas.
│ └── RecursionError # Se produce cuando se supera el límite máximo de recursión.
├── StopIteration # Se produce cuando next() intenta obtener un elemento de un iterador agotado.
├── TypeError # Se produce cuando se pasa un argumento de tipo incorrecto a una función.
└── ValueError # Se produce cuando un argumento tiene el tipo correcto, pero un valor no válido.
colecciones y their excepciones:
| Método | List | Set | Dict |
|---|---|---|---|
| getitem() | IndexError |
KeyError |
|
| pop() | IndexError |
KeyError |
KeyError |
| remove() | ValueError |
KeyError |
|
| index() | ValueError |
Useful built-in excepciones:
raise TypeError('Argument is of the wrong type!')
raise ValueError('Argument has the right type but an inappropriate value!')
raise RuntimeError('I am too lazy to define my own exception!')
Excepciones personalizadas
class MyError(Exception): pass
class MyInputError(MyError): pass
Salida del programa
Sale del intérprete elevando la excepción SystemExit.
import sys
sys.exit() # Exits con exit code 0 (success).
sys.exit(<el>) # Imprime to stderr y exits con 1.
sys.exit(<int>) # Exits con the proporcionados exit code.
Salida por pantalla
print(<el_1>, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
- Utilice
file=sys.stderrpara mensajes de error. - Los flujos stdout y stderr almacenan la salida en un búfer hasta que reciben una cadena que contiene '\n' o '\r', el búfer alcanza los 4096 caracteres, se utiliza
flush=Trueo finaliza el programa.
Impresión legible
from pprint import pprint
pprint(<collection>, width=80, depth=None, compact=False, sort_dicts=True)
- Cada elemento se imprime en una línea aparte si la colección supera el número de caracteres especificado.
- Las colecciones anidadas con un nivel de profundidad especificado se imprimen como puntos suspensivos (…).
Entrada del usuario
<str> = input(prompt=None)
- Lee una línea de la entrada del usuario o de la tubería, si está presente (se elimina el salto de línea final).
- Se imprime la cadena de solicitud en la salida estándar antes de leer la entrada.
- Genera un error EOFError cuando el usuario llega al final del archivo (Ctrl+D/Ctrl+Z⏎) o cuando se agota el flujo de entrada.
Argumentos de línea de comandos
import sys
scripts_path = sys.argv[0]
arguments = sys.argv[1:]
Analizador de argumentos
from argparse import ArgumentParser, FileType
p = ArgumentParser(description=<str>) # Devuelve un analizador de argumentos.
p.add_argument('-<short_name>', '--<name>', action='store_true') # Flag (defaults to False).
p.add_argument('-<short_name>','--<name>',type=<type>) # Option (defaults to None).
p.add_argument('<name>', type=<type>, nargs=1) # Mandatory primero argumento.
p.add_argument('<name>', type=<type>, nargs='+') # Mandatory restantes args.
p.add_argument('<name>', type=<type>, nargs='?/*') # Optional argumento/s.
<args> = p.parse_args() # Exits on parsing error.
<obj> = <args>.<name> # Devuelve `<type>(<arg>)`.
- Utilice
help=<str>para establecer la descripción del argumento que se mostrará en el mensaje de ayuda. - Utilice
default=<el>para establecer el valor predeterminado de la opción o del argumento opcional. - Utilice
type=FileType(<mode>)para archivos. Acepta 'encoding', pero 'newline' es None.
Apertura de archivos
Abre el archivo y devuelve el objeto de archivo correspondiente.
<file> = open(<path>, mode='r', encoding=None, newline=None)
encoding=Nonesignifica que se utiliza la codificación predeterminada, que depende de la plataforma. Se recomienda usarencoding="utf-8"siempre que sea posible.newline=Nonesignifica que todas las combinaciones de fin de línea se convierten a '\n' al leer, mientras que al escribir, todos los caracteres '\n' se convierten al separador de línea predeterminado del sistema.newline=""significa que no se realizan conversiones, pero la entrada se divide en fragmentos mediantereadline()yreadlines()en cada '\n', '\r' y '\r\n'.
Modos
'r'- Lectura (predeterminado).'w'- Escritura (truncamiento).'x'- Escritura o error si el archivo ya existe.'a'- Añadir al final.'w+'- Lectura y escritura (truncamiento).'r+'- Lectura y escritura desde el principio.'a+'- Lectura y escritura desde el final.'b'- Modo binario ('br','bw','bx', …).
Excepciones
- El error
'FileNotFoundError'puede producirse al leer con'r'o'r+'. - El error
'FileExistsError'puede producirse al escribir con'x'. - Los errores
'IsADirectoryError'y'PermissionError'pueden producirse por cualquier error. 'OSError'es la clase padre de todas las excepciones enumeradas.
Objeto de archivo
<file>.seek(0) # Mueve al inicio del archivo.
<file>.seek(offset) # Mueve 'desplazamiento' chars/bytes desde the inicio.
<file>.seek(0, 2) # Mueve al fin del archivo.
<bin_file>.seek(±offset, <anchor>) # Anchor: 0 inicio, 1 actual posición, 2 fin.
<str/bytes> = <file>.read(size=-1) # Lee 'tamaño' chars/bytes or hasta EOF.
<str/bytes> = <file>.readline() # Devuelve una línea o una cadena de texto/bytes vacía al llegar al final del archivo.
<list> = <file>.readlines() # Devuelve una lista de las líneas restantes.
<str/bytes> = next(<file>) # Devuelve una línea mediante el búfer; evita mezclar métodos de lectura.
<file>.write(<str/bytes>) # Escribe a cadena or bytes objeto.
<file>.writelines(<collection>) # Escribe a coll. de cadenas or bytes objetos.
<file>.flush() # Flushes write buffer. Ejecuta every 4096/8192 B.
<file>.close() # Cierra the archivo después de flushing write buffer.
- Los métodos no añaden ni eliminan saltos de línea finales, ni siquiera writelines().
Leer texto de un archivo
def read_file(filename):
with open(filename, encoding='utf-8') as file:
return file.readlines()
Escribir texto en un archivo
def write_to_file(filename, text):
with open(filename, 'w', encoding='utf-8') as file:
file.write(text)
Rutas
import os, glob
from pathlib import Path
<str> = os.getcwd() # Devuelve shell's working dir salvo que changed.
<str> = os.path.join(<path>, ...) # Une two or more pathname components.
<str> = os.path.realpath(<path>) # Resuelve symlinks y llama a ruta.abspath().
<str> = os.path.basename(<path>) # Devuelve final component del ruta.
<str> = os.path.dirname(<path>) # Devuelve ruta sin the final component.
<tup.> = os.path.splitext(<path>) # Divide on último period del final component.
<list> = os.listdir(path='.') # Devuelve filenames located at the ruta.
<list> = glob.glob('<pattern>') # Devuelve rutas coincidentes the wildcard pattern.
<bool> = os.path.exists(<path>) # O bien: <Path>.exists()
<bool> = os.path.isfile(<path>) # O bien: <DirEntry/Path>.is_file()
<bool> = os.path.isdir(<path>) # O bien: <DirEntry/Path>.is_dir()
<stat> = os.stat(<path>) # O bien: <DirEntry/Path>.stat()
<num> = <stat>.st_mtime/st_size/… # Modification tiempo, tamaño in bytes, ...
Entradas de directorio
A diferencia de listdir(), scandir() devuelve objetos DirEntry que almacenan en caché información sobre isfile, isdir y, en Windows, también información de estado, lo que aumenta significativamente el rendimiento del código que la requiere.
<iter> = os.scandir(path='.') # Devuelve DirEntry objetos located at the ruta.
<str> = <DirEntry>.path # Devuelve el completo ruta como un cadena.
<str> = <DirEntry>.name # Devuelve final component como un cadena.
<file> = open(<DirEntry>) # Abre the archivo y devuelve una archivo objeto.
Objeto Path
<Path> = Path(<path> [, ...]) # Acepta cadenas, rutas y DirEntry objetos.
<Path> = <path> / <path> [/ ...] # Primero or second ruta debe ser a ruta objeto.
<Path> = <Path>.resolve() # Devuelve absolute ruta con resolved symlinks.
<Path> = Path() # Devuelve relative cwd. También ruta('.').
<Path> = Path.cwd() # Devuelve absolute cwd. También ruta().resolve().
<Path> = Path.home() # Devuelve user's home directorio (absolute).
<Path> = Path(__file__).resolve() # Devuelve script's ruta si cwd wasn't changed.
<Path> = <Path>.parent # Devuelve ruta sin the final component.
<str> = <Path>.name # Devuelve final component como un cadena.
<str> = <Path>.stem # Devuelve final component sin extension.
<str> = <Path>.suffix # Devuelve final component's extension.
<tup.> = <Path>.parts # Devuelve todos los components as cadenas.
<iter> = <Path>.iterdir() # Devuelve directorio contents as ruta objetos.
<iter> = <Path>.glob('<pattern>') # Devuelve rutas coincidentes the wildcard pattern.
<str> = str(<Path>) # Devuelve ruta como un cadena.
<file> = open(<Path>) # También <Path>.read/write_text/bytes().
Comandos del sistema operativo
import os, shutil, subprocess
os.chdir(<path>) # Cambia the actual working directorio.
os.mkdir(<path>, mode=0o777) # Crea una directorio. Permissions are in octal.
os.makedirs(<path>, mode=0o777) # Crea todos los ruta's dirs. También `exist_ok=False`.
shutil.copy(from, to) # Copia the archivo. 'to' can exist or be a dir.
shutil.copy2(from, to) # También copia creation y modification tiempo.
shutil.copytree(from, to) # Copia the directorio. 'to' must not exist.
os.rename(from, to) # Renames/mueve the archivo or directorio.
os.replace(from, to) # Igual, pero overwrites archivo 'to' even on Windows.
shutil.move(from, to) # Rename() that mueve en 'to' si it's a dir.
os.remove(<path>) # Elimina the archivo.
os.rmdir(<path>) # Elimina the vacío directorio.
shutil.rmtree(<path>) # Elimina the directorio.
- Las rutas pueden ser cadenas de texto, objetos rutas o DirEntry.
- Las funciones informan de errores relacionados con el sistema operativo mediante la generación de una excepción OSError o una de sus subclases.
Comandos de shell
<pipe> = os.popen('<command>') # Executes command in sh/cmd. Devuelve its stdout pipe.
<str> = <pipe>.read(size=-1) # Lee 'tamaño' chars or hasta EOF. También readline/s().
<int> = <pipe>.close() # Cierra the pipe. Devuelve None on success (returncode 0).
Envía '1 + 1' a la calculadora básica y captura su resultado:
>>> subprocess.run('bc', input='1 + 1\n', capture_output=True, text=True)
CompletedProcess(args='bc', returncode=0, stdout='2\n', stderr='')
Envía test.in a la calculadora básica que se ejecuta en modo estándar y guarda su resultado en test.out:
>>> from shlex import split
>>> os.popen('echo 1 + 1 > test.in')
>>> subprocess.run(split('bc -s'), stdin=open('test.in'), stdout=open('test.out', 'w'))
CompletedProcess(args=['bc', '-s'], returncode=0)
>>> open('test.out').read()
'2\n'
JSON
Formato de archivo de texto para almacenar colecciones de cadenas de texto y números.
import json
<str> = json.dumps(<object>) # Convierte objeto to JSON cadena.
<object> = json.loads(<str>) # Convierte JSON cadena to objeto.
Read objeto desde JSON archivo
def read_json_file(filename):
with open(filename, encoding='utf-8') as file:
return json.load(file)
Write objeto to JSON archivo
def write_to_json_file(filename, an_object):
with open(filename, 'w', encoding='utf-8') as file:
json.dump(an_object, file, ensure_ascii=False, indent=2)
Pickle
Formato de archivo binario para almacenar objetos Python.
import pickle
<bytes> = pickle.dumps(<object>) # Convierte objeto to bytes objeto.
<object> = pickle.loads(<bytes>) # Convierte bytes objeto to objeto.
Read objeto desde archivo
def read_pickle_file(filename):
with open(filename, 'rb') as file:
return pickle.load(file)
Write objeto to archivo
def write_to_pickle_file(filename, an_object):
with open(filename, 'wb') as file:
pickle.dump(an_object, file)
CSV
CSV (Comma-Separated Values) es un formato de texto utilizado para almacenar e intercambiar datos organizados en filas y columnas. Es habitual encontrarlo en exportaciones de Microsoft Excel, bases de datos, informes y aplicaciones que necesitan intercambiar información.
Aunque su nombre significa valores separados por comas, los archivos CSV también pueden utilizar otros separadores, como el punto y coma (;) o la tabulación (\t).
Python incluye el módulo csv en su biblioteca estándar, por lo que no necesitamos instalar ningún paquete adicional.
import csv
Lectura de archivos CSV
La función csv.reader() permite leer un archivo CSV y recorrer sus filas. Cada fila se devuelve como una lista de cadenas de texto.
<reader> = csv.reader(<file>) # Crea un lector CSV. Admite dialect='excel' y delimiter=','.
<list> = next(<reader>) # Devuelve la siguiente fila como una lista de cadenas.
<list> = list(<reader>) # Devuelve una lista con todas las filas restantes.
Ejemplo práctico
Supongamos que tenemos un archivo llamado usuarios.csv:
nombre,edad,ciudad
Walter,35,Barcelona
Ana,28,Madrid
Carlos,42,Valencia
Podemos leerlo con:
import csv
with open("usuarios.csv", "r", encoding="utf-8", newline="") as archivo:
lector = csv.reader(archivo)
for fila in lector:
print(fila)
Resultado:
['nombre', 'edad', 'ciudad']
['Walter', '35', 'Barcelona']
['Ana', '28', 'Madrid']
['Carlos', '42', 'Valencia']
Observa que todos los valores se devuelven como cadenas, incluso los números. Si necesitamos trabajar con la edad como un entero, tendremos que convertirla:
edad = int(fila[1])
También podemos leer la primera fila por separado:
with open("usuarios.csv", encoding="utf-8", newline="") as archivo:
lector = csv.reader(archivo)
cabecera = next(lector)
datos = list(lector)
print(cabecera)
print(datos)
Detalles que conviene recordar:
- Utiliza
newline=""al abrir archivos CSV para que el módulo gestione correctamente los saltos de línea, incluso dentro de campos entrecomillados. csv.reader()acepta cualquier iterable de cadenas de texto, no únicamente archivos.next(lector)devuelve la siguiente fila. Si no quedan filas, generaStopIteration, salvo que se proporcione un valor por defecto.- Para mostrar los datos como una tabla en la terminal podemos utilizar la biblioteca Tabulate.
- Para trabajar con archivos Excel (
.xlsx,.xlsm) podemos utilizar Pandas junto con un motor de lectura compatible, comoopenpyxl.
Lectura mediante nombres de columnas (DictReader)
Cuando un CSV tiene muchas columnas, acceder a los valores por su posición puede resultar incómodo.
Por ejemplo, fila[2] no nos dice demasiado sobre el contenido que estamos leyendo.
Para estos casos utilizamos csv.DictReader(), que devuelve cada fila como un diccionario y utiliza la primera línea como cabecera.
import csv
with open("usuarios.csv", "r", encoding="utf-8", newline="") as archivo:
lector = csv.DictReader(archivo)
for fila in lector:
print(fila["nombre"], fila["ciudad"])
Resultado:
Walter Barcelona
Ana Madrid
Carlos Valencia
De esta forma, podemos acceder a las columnas por su nombre sin preocuparnos tanto por su posición dentro del archivo.
Escritura de archivos CSV
La función csv.writer() permite escribir información en un archivo CSV.
<writer> = csv.writer(<file>) # Crea un escritor CSV. Admite dialect='excel' y delimiter=','.
<writer>.writerow(<collection>) # Escribe una fila y convierte los valores a texto.
<writer>.writerows(<coll_of_coll>) # Escribe varias filas de una colección.
Ejemplo práctico
Vamos a crear un archivo CSV con información de varios usuarios:
import csv
usuarios = [
["nombre", "edad", "ciudad"],
["Walter", 35, "Barcelona"],
["Ana", 28, "Madrid"],
["Carlos", 42, "Valencia"]
]
with open("usuarios.csv", "w", encoding="utf-8", newline="") as archivo:
escritor = csv.writer(archivo)
escritor.writerows(usuarios)
El resultado será un archivo con el siguiente contenido:
nombre,edad,ciudad
Walter,35,Barcelona
Ana,28,Madrid
Carlos,42,Valencia
Diferencia entre **writerow()** y **writerows()**:
writerow()escribe una única fila.writerows()escribe varias filas de una vez.
Podemos utilizar ambos métodos en un mismo archivo:
with open("usuarios.csv", "w", encoding="utf-8", newline="") as archivo:
escritor = csv.writer(archivo)
escritor.writerow(["nombre", "edad", "ciudad"])
escritor.writerow(["Walter", 35, "Barcelona"])
escritor.writerow(["Ana", 28, "Madrid"])
Modos de apertura:
mode="w": crea el archivo o sobrescribe su contenido si ya existe.mode="a": añade nuevas filas al final sin eliminar los datos existentes.mode="x": crea un archivo nuevo y genera un error si ya existe.
El argumento newline="" también debe utilizarse al escribir, especialmente para evitar líneas en blanco adicionales en determinadas plataformas.
Escritura mediante diccionarios (DictWriter)
Si tenemos los datos almacenados en diccionarios, podemos utilizar csv.DictWriter().
import csv
usuarios = [
{"nombre": "Walter", "edad": 35, "ciudad": "Barcelona"},
{"nombre": "Ana", "edad": 28, "ciudad": "Madrid"}
]
with open("usuarios.csv", "w", encoding="utf-8", newline="") as archivo:
columnas = ["nombre", "edad", "ciudad"]
escritor = csv.DictWriter(archivo, fieldnames=columnas)
escritor.writeheader() # Escribe los nombres de las columnas.
escritor.writerows(usuarios) # Escribe los registros.
Este método resulta especialmente práctico cuando trabajamos con consultas de bases de datos, APIs o información que ya tenemos organizada como diccionarios.
Parámetros de configuración
El módulo csv permite controlar cómo se leen y escriben los archivos.
| Parámetro | Descripción |
|---|---|
dialect |
Define un conjunto de reglas de lectura o escritura. Puede ser un nombre registrado, como 'excel', o un objeto de tipo csv.Dialect. |
delimiter |
Carácter utilizado para separar los campos. Por defecto es ','. |
lineterminator |
Secuencia utilizada para finalizar cada fila al escribir. Por defecto es '\r\n'. |
quotechar |
Carácter utilizado para entrecomillar campos que contienen separadores, comillas o saltos de línea. Por defecto es ". |
escapechar |
Carácter utilizado para escapar caracteres especiales cuando la configuración lo requiere. |
doublequote |
Indica si las comillas que aparecen dentro de un campo se duplican. |
quoting |
Controla cuándo se entrecomillan los campos. |
skipinitialspace |
Si es True, ignora los espacios inmediatamente posteriores al separador. |
strict |
Si es True, genera csv.Error ante determinados errores de formato durante la lectura. |
Valores habituales de **quoting**:
| Constante | Valor | Comportamiento |
|---|---|---|
csv.QUOTE_MINIMAL |
0 | Entrecomilla únicamente los campos que lo necesitan. |
csv.QUOTE_ALL |
1 | Entrecomilla todos los campos. |
csv.QUOTE_NONNUMERIC |
2 | Entrecomilla los valores no numéricos. Al leer, convierte los campos sin comillas a float. |
csv.QUOTE_NONE |
3 | No utiliza comillas para delimitar campos. Puede requerir un carácter de escape. |
Es preferible utilizar las constantes csv.QUOTE_* en lugar de sus valores numéricos, porque el código resulta mucho más fácil de entender.
Dialectos CSV
Un dialecto es un conjunto de parámetros que determina cómo se interpreta o se genera un archivo CSV.
Python incluye varios dialectos predefinidos:
| Propiedad | excel |
excel-tab |
unix |
|---|---|---|---|
delimiter |
',' |
'\t' |
',' |
lineterminator |
'\r\n' |
'\r\n' |
'\n' |
quotechar |
'"' |
'"' |
'"' |
escapechar |
None |
None |
None |
doublequote |
True |
True |
True |
quoting |
0 |
0 |
1 |
skipinitialspace |
False |
False |
False |
Por ejemplo, podemos utilizar el dialecto unix para generar un archivo con saltos de línea \n y todos los campos entrecomillados:
with open("datos.csv", "w", encoding="utf-8", newline="") as archivo:
escritor = csv.writer(archivo, dialect="unix")
escritor.writerow(["nombre", "edad"])
escritor.writerow(["Walter", 35])
Resultado:
"nombre","edad"
"Walter","35"
Consejo práctico: CSV de Excel y separadores
Cuando trabajamos con CSV exportados desde Microsoft Excel, debemos tener en cuenta dos detalles: la codificación de los caracteres y el separador utilizado.
En configuraciones regionales como la española es habitual encontrar archivos CSV separados por punto y coma (;) en lugar de comas.
Por ejemplo:
nombre;edad;ciudad
Walter;35;Barcelona
Ana;28;Madrid
Si intentamos leer este archivo utilizando el separador predeterminado, Python interpretará cada línea como un único campo.
La solución consiste en indicar delimiter=";":
import csv
with open("usuarios.csv", "r", encoding="utf-8", newline="") as archivo:
lector = csv.reader(archivo, delimiter=";")
for fila in lector:
print(fila)
También podemos generar archivos separados por punto y coma:
with open("usuarios.csv", "w", encoding="utf-8", newline="") as archivo:
escritor = csv.writer(archivo, delimiter=";")
escritor.writerow(["nombre", "edad", "ciudad"])
escritor.writerow(["Walter", 35, "Barcelona"])
Consejo práctico: Archivos de Excel y la codificación utf-8-sig
Algunos archivos CSV exportados desde Microsoft Excel incluyen una marca especial al principio llamada BOM (Byte Order Mark).
Esta marca permite identificar la codificación del archivo, pero no forma parte de los datos.
El problema aparece cuando Python lee el archivo utilizando encoding="utf-8". Aunque el contenido se interpreta correctamente, la marca puede quedar incluida en el nombre de la primera columna.
Imaginemos que tenemos este CSV:
nombre,edad,ciudad
Walter,35,Barcelona
Ana,28,Madrid
Si el archivo contiene BOM, al leerlo con csv.DictReader() la primera columna podría llamarse \ufeffnombre en lugar de nombre.
En algunas herramientas, cuando los bytes de esa marca se interpretan con una codificación incorrecta, aparecen los caracteres extraños  al principio del encabezado.
¿Cómo lo solucionamos?
Python dispone de la codificación utf-8-sig, que reconoce y elimina automáticamente el BOM al leer un archivo UTF-8.
import csv
with open("usuarios.csv", "r", encoding="utf-8-sig", newline="") as archivo:
lector = csv.DictReader(archivo)
for fila in lector:
print(fila["nombre"], fila["edad"])
Si además nuestro archivo utiliza punto y coma como separador:
with open("usuarios.csv", "r", encoding="utf-8-sig", newline="") as archivo:
lector = csv.DictReader(archivo, delimiter=";")
for fila in lector:
print(fila["nombre"], fila["ciudad"])
¿Cuándo utilizar cada codificación?
| Codificación | Cuándo utilizarla |
|---|---|
utf-8 |
Archivos UTF-8 sin BOM. |
utf-8-sig |
Archivos UTF-8 que pueden incluir BOM, especialmente exportaciones de Excel. |
cp1252 |
Algunos archivos antiguos de Windows guardados con esta codificación. |
utf-8-sig también permite leer correctamente archivos UTF-8 que no contienen BOM, por lo que es una buena opción cuando desconocemos si el archivo incluye esa marca.
Sin embargo, no convierte automáticamente archivos guardados con otras codificaciones, como Windows-1252.
Mi recomendación: cuando trabajo con CSV procedentes de Excel, suelo utilizar encoding="utf-8-sig" y comprobar si el separador es una coma o un punto y coma. Son dos detalles sencillos que evitan bastantes problemas al importar datos.
Función reutilizable: Leer filas de un CSV
Podemos crear una función que permita leer archivos CSV sin tener que repetir el mismo código.
import csv
def read_csv_file(filename, dialect="excel", **params):
with open(filename, "r", encoding="utf-8-sig", newline="") as archivo:
return list(csv.reader(archivo, dialect=dialect, **params))
Ejemplos de uso:
# CSV separado por comas.
datos = read_csv_file("usuarios.csv")
# CSV separado por punto y coma.
datos = read_csv_file("usuarios.csv", delimiter=";")
# CSV separado por tabulaciones.
datos = read_csv_file("usuarios.csv", dialect="excel-tab")
print(datos)
El argumento **params permite pasar opciones adicionales, como delimiter, quotechar o skipinitialspace, sin modificar la función.
Esta función carga todas las filas en memoria. Para archivos muy grandes, resulta más eficiente procesarlas directamente con un bucle for.
Función reutilizable: Escribir filas en un CSV
También podemos preparar una función para guardar datos en archivos CSV.
import csv
def write_to_csv_file(filename, rows, mode="w", dialect="excel", **params):
with open(filename, mode, encoding="utf-8", newline="") as archivo:
escritor = csv.writer(archivo, dialect=dialect, **params)
escritor.writerows(rows)
Ejemplo de uso:
usuarios = [
["nombre", "edad", "ciudad"],
["Walter", 35, "Barcelona"],
["Ana", 28, "Madrid"]
]
# Guardar utilizando comas.
write_to_csv_file("usuarios.csv", usuarios)
# Guardar utilizando punto y coma.
write_to_csv_file("usuarios_excel.csv", usuarios, delimiter=";")
# Añadir una nueva fila a un archivo existente.
write_to_csv_file(
"usuarios.csv",
[["Carlos", 42, "Valencia"]],
mode="a"
)
Si necesitamos generar un archivo UTF-8 con BOM para una aplicación que lo requiera, podemos cambiar encoding="utf-8" por encoding="utf-8-sig" en la función de escritura. En ese caso conviene generar el archivo desde cero, ya que abrir un archivo existente en modo de anexado con esa codificación puede introducir otra marca BOM.
Resumen de los métodos CSV más utilizados
| Método | Función |
|---|---|
csv.reader() |
Lee filas como listas. |
csv.writer() |
Escribe filas a partir de listas o secuencias. |
csv.DictReader() |
Lee filas como diccionarios utilizando los encabezados. |
csv.DictWriter() |
Escribe filas a partir de diccionarios. |
next() |
Obtiene la siguiente fila del lector. |
writerow() |
Escribe una fila. |
writerows() |
Escribe varias filas. |
writeheader() |
Escribe los encabezados con DictWriter. |
csv.Sniffer() |
Permite intentar detectar el dialecto de un CSV a partir de una muestra. |
Con estas funciones podemos leer, generar y procesar archivos CSV directamente desde Python, sin depender de Excel ni instalar bibliotecas adicionales.
SQLite
Un motor de base de datos sin servidor que almacena cada base de datos en un archivo separado.
import sqlite3
<conn> = sqlite3.connect(<path>) # Abre un archivo existente o crea otro. También admite `:memory:`.
<conn>.close() # Cierra the connection.
Lectura
<cursor> = <conn>.execute('<query>') # Puede lanzar una subclase de `sqlite3.Error`.
<tuple> = <cursor>.fetchone() # Devuelve siguiente fila. También siguiente(<cursor>).
<list> = <cursor>.fetchall() # Devuelve restantes filas. También lista(<cursor>).
Escritura
<conn>.execute('<query>') # Puede lanzar una subclase de `sqlite3.Error`.
<conn>.commit() # Guarda todos los cambia since the último commit.
<conn>.rollback() # Discards todos los cambia since the último commit.
O bien:
with <conn>: # Exits the block con commit() or rollback(),
<conn>.execute('<query>') # depending on whether cualquier excepción occurred.
Marcadores de posición
<conn>.execute('<query>', <list/tuple>) # Sustituye '?'s in query con valores.
<conn>.execute('<query>', <dict/namedtuple>) # Sustituye ':<key>'s con valores.
<conn>.executemany('<query>', <coll_of_above>)# Ejecuta `execute()` varias veces.
- Los valores pasados pueden ser de tipo str, int, float, bytes, None, bool, datetime.fecha o datetime.datetime.
- Los valores booleanos se almacenarán y devolverán como enteros, y las fechas como cadenas con formato ISO.
Ejemplo
En este ejemplo, los valores no se guardan realmente porque se omite 'conn.commit()'.
>>> conn = sqlite3.connect('test.db')
>>> conn.execute('CREATE TABLE person (person_id INTEGER PRIMARY KEY, name, height)')
>>> conn.execute('INSERT INTO person VALUES (NULL, ?, ?)', ('Jean-Luc', 187)).lastrowid
1
>>> conn.execute('SELECT * FROM person').fetchall()
[(1, 'Jean-Luc', 187)]
SQLAlchemy
# $ pip3 install sqlalchemy
from sqlalchemy import create_engine, text
<engine> = create_engine('<url>') # Url: 'dialect://user:password@host/dbname'.
<conn> = <engine>.connect() # Crea una connection. También <conn>.close().
<cursor> = <conn>.execute(text('<query>'), …) # Sustituye ':<key>'s con keyword argumentos.
with <conn>.begin(): ... # Exits the block con commit or rollback.
| Dialecto | pip3 install | import | Dependencias / Proyecto |
|---|---|---|---|
| mysql | mysqlclient |
MySQLdb |
pypi.org/project/mysqlclient |
| postgresql | psycopg2 |
psycopg2 |
pypi.org/project/psycopg2 |
| mssql | pyodbc |
pyodbc |
pypi.org/project/pyodbc |
| oracle | oracledb |
oracledb |
pypi.org/project/oracledb |
Bytes
En Python, un objeto bytes representa una secuencia inmutable de bytes. Cada byte es un valor entero comprendido entre 0 y 255, es decir, 8 bits.
Cuando trabajamos con texto normalmente utilizamos cadenas de tipo str. Sin embargo, cuando necesitamos manipular archivos binarios, paquetes de red, imágenes, protocolos o datos que llegan desde un dispositivo, utilizamos bytes.
Python dispone de dos tipos principales para trabajar con secuencias de bytes:
**bytes**: secuencia inmutable. Una vez creada, no podemos modificar sus elementos.**bytearray**: secuencia mutable. Podemos modificar, añadir o eliminar elementos.
La diferencia es parecida a la que existe entre una tupla y una lista.
texto = "Hola" # Cadena de texto (str).
datos = b"Hola" # Secuencia de bytes (bytes).
buffer = bytearray(b"Hola") # Secuencia de bytes modificable.
Podemos comprobar los tipos:
print(type(texto)) # <class 'str'>
print(type(datos)) # <class 'bytes'>
print(type(buffer)) # <class 'bytearray'>
Operaciones básicas con bytes
<bytes> = b'<str>' # Crea un literal de bytes con caracteres ASCII y escapes.
<int> = <bytes>[index] # Devuelve un entero entre 0 y 255.
<bytes> = <bytes>[<slice>] # Devuelve una secuencia de bytes, incluso si contiene solo uno.
<bytes> = <bytes>.join(<coll_of_bytes>) # Une secuencias utilizando otros bytes como separador.
Ejemplo práctico
datos = b"Python"
print(datos[0]) # 80
print(datos[1]) # 121
print(datos[0:3]) # b'Pyt'
print(len(datos)) # 6
¿Por qué datos[0] devuelve 80 y no la letra P?
Porque al acceder a una posición de un objeto bytes, Python devuelve su valor numérico. En este caso, el carácter P corresponde al número 80 en ASCII.
Si queremos obtener el carácter como texto:
print(chr(datos[0])) # P
Para unir varias secuencias:
palabras = [b"Python", b"Linux", b"Docker"]
resultado = b" - ".join(palabras)
print(resultado)
# b'Python - Linux - Docker'
Codificación: convertir texto a bytes
La codificación es el proceso que transforma caracteres de texto en una secuencia de bytes.
Esto es necesario porque un ordenador almacena y transmite bytes, mientras que las cadenas de Python representan caracteres Unicode.
El método más habitual es encode().
<bytes> = bytes(<coll_of_ints>) # Crea bytes a partir de enteros entre 0 y 255.
<bytes> = bytes(<str>, 'utf-8') # Codifica una cadena usando UTF-8.
<bytes> = <str>.encode('utf-8') # Otra forma de convertir texto a bytes.
<bytes> = bytes.fromhex('<hex>') # Convierte pares hexadecimales en bytes.
<bytes> = <int>.to_bytes(n_bytes, byteorder='big') # Convierte un entero en bytes.
Ejemplo: codificación UTF-8
texto = "España"
datos = texto.encode("utf-8")
print(datos)
# b'Espa\xc3\xb1a'
Aquí vemos algo interesante: la letra ñ ocupa dos bytes en UTF-8 (\xc3\xb1).
Esto nos recuerda que un carácter no siempre equivale a un byte.
texto = "España"
print(len(texto)) # 6 caracteres.
print(len(texto.encode("utf-8"))) # 7 bytes.
Es una diferencia que debemos tener presente al trabajar con protocolos, tamaños de mensajes o límites de almacenamiento.
Crear bytes a partir de enteros
También podemos crear directamente una secuencia indicando los valores de sus bytes.
datos = bytes([65, 66, 67])
print(datos)
# b'ABC'
Los valores deben encontrarse entre 0 y 255.
datos = bytes([255, 128, 0, 10])
print(datos.hex())
# ff80000a
Si intentamos utilizar un número fuera de ese rango, Python genera un ValueError.
Conversión entre hexadecimal y bytes
En redes y sistemas es habitual representar datos binarios mediante números hexadecimales.
Por ejemplo, una dirección MAC o el contenido de un paquete de red suele mostrarse en hexadecimal porque resulta más fácil de leer que una secuencia de bits.
datos = bytes.fromhex("DE AD BE EF")
print(datos)
# b'\xde\xad\xbe\xef'
También podemos realizar la operación contraria:
datos = b"\xde\xad\xbe\xef"
print(datos.hex())
# deadbeef
print(datos.hex(":"))
# de:ad:be:ef
Detalle útil: bytes.fromhex() permite separar los pares hexadecimales mediante espacios en blanco.
bytes.fromhex("DE AD BE EF")
bytes.fromhex("DEADBEEF")
Ambas expresiones generan la misma secuencia.
Decodificación: convertir bytes a texto
La decodificación realiza el proceso contrario: transforma una secuencia de bytes en caracteres de texto.
Para ello utilizamos habitualmente decode().
<list> = list(<bytes>) # Devuelve una lista de enteros entre 0 y 255.
<str> = <bytes>.decode('utf-8') # Decodifica los bytes como texto UTF-8.
<str> = str(<bytes>, 'utf-8') # Otra forma de decodificar los bytes.
<str> = <bytes>.hex() # Devuelve su representación hexadecimal.
<int> = int.from_bytes(<bytes>, 'big') # Interpreta los bytes como un entero.
Ejemplo práctico
datos = b"Espa\xc3\xb1a"
texto = datos.decode("utf-8")
print(texto)
# España
Debemos utilizar la misma codificación con la que se generaron los bytes.
Si intentamos decodificar una secuencia utilizando una codificación incompatible, podemos obtener un UnicodeDecodeError.
Por ejemplo:
datos = b"\xff"
texto = datos.decode("utf-8")
# UnicodeDecodeError
Cuando recibimos información de una fuente externa podemos gestionar los errores:
texto = datos.decode("utf-8", errors="replace")
Esto sustituye las secuencias inválidas por el carácter de reemplazo �.
También existe errors="ignore", pero conviene utilizarlo con cuidado, ya que descarta información sin avisarnos.
Bytes y bytearray: ¿qué diferencia hay?
Un objeto bytes no permite modificar sus elementos.
datos = b"Hola"
# Esto genera TypeError:
# datos[0] = 65
Si necesitamos modificar el contenido, podemos utilizar bytearray.
datos = bytearray(b"Hola")
datos[0] = 65
print(datos)
# bytearray(b'Aola')
También podemos añadir nuevos bytes:
datos = bytearray(b"ABC")
datos.append(68)
print(datos)
# bytearray(b'ABCD')
| Característica | bytes |
bytearray |
|---|---|---|
| Mutable | No | Sí |
| Acceso por índice | Sí | Sí |
| Segmentación (slicing) | Sí | Sí |
Añadir elementos con append() |
No | Sí |
| Modificar elementos | No | Sí |
| Uso habitual | Datos binarios que no necesitan modificarse | Búferes y datos binarios modificables |
Convertir enteros en bytes
Python permite representar un entero mediante una secuencia de bytes utilizando to_bytes().
numero = 1024
datos = numero.to_bytes(2, byteorder="big")
print(datos)
# b'\x04\x00'
Y podemos recuperar el entero original:
numero = int.from_bytes(datos, byteorder="big")
print(numero)
# 1024
El argumento byteorder determina cómo se ordenan los bytes:
big: primero el byte más significativo.little: primero el byte menos significativo.
Veremos esta diferencia con más detalle en el apartado struct.
Leer bytes desde un archivo
Para leer archivos binarios debemos utilizar el modo rb (read binary).
def read_bytes(filename):
with open(filename, "rb") as file:
return file.read()
Ejemplo:
datos = read_bytes("imagen.png")
print(type(datos)) # <class 'bytes'>
print(len(datos)) # Tamaño del archivo en bytes.
print(datos[:8]) # Primeros ocho bytes.
Esto permite leer el contenido de cualquier archivo como una secuencia de bytes, independientemente de que sea una imagen, un ejecutable o un documento.
Consejo: si el archivo es muy grande, podemos leerlo por bloques para evitar cargarlo entero en memoria.
with open("archivo.bin", "rb") as file:
while bloque := file.read(4096):
print(f"Leídos {len(bloque)} bytes")
En este ejemplo procesamos bloques de hasta 4096 bytes.
Escribir bytes en un archivo
Para escribir información binaria utilizamos el modo wb (write binary).
def write_bytes(filename, bytes_obj):
with open(filename, "wb") as file:
file.write(bytes_obj)
Ejemplo:
datos = bytes([0xDE, 0xAD, 0xBE, 0xEF])
write_bytes("datos.bin", datos)
Podemos comprobar el contenido leyendo nuevamente el archivo:
resultado = read_bytes("datos.bin")
print(resultado.hex())
# deadbeef
Debemos recordar que el modo wb sobrescribe el archivo si ya existe. Para añadir bytes al final utilizaremos ab.
Resumen de métodos para trabajar con bytes
| Método | Función |
|---|---|
b"texto" |
Crea un literal de bytes ASCII. |
bytes() |
Crea una secuencia inmutable de bytes. |
bytearray() |
Crea una secuencia mutable de bytes. |
encode() |
Convierte texto en bytes. |
decode() |
Convierte bytes en texto. |
bytes.fromhex() |
Convierte hexadecimal en bytes. |
bytes.hex() |
Convierte bytes a una representación hexadecimal. |
int.to_bytes() |
Convierte un entero en bytes. |
int.from_bytes() |
Convierte bytes en un entero. |
join() |
Une varias secuencias de bytes. |
len() |
Obtiene la cantidad de bytes. |
open(..., "rb") |
Abre un archivo para lectura binaria. |
open(..., "wb") |
Abre un archivo para escritura binaria. |
Struct
El módulo struct permite convertir valores de Python, como enteros y números de punto flotante, en secuencias de bytes con un formato binario definido.
También permite realizar la operación inversa: interpretar un bloque de bytes y recuperar los valores originales.
Es especialmente útil cuando trabajamos con protocolos de red, archivos binarios, comunicación con dispositivos, firmware o intercambio de información entre sistemas.
Por ejemplo, imaginemos que recibimos un paquete binario que contiene un identificador de 2 bytes, un contador de 4 bytes y una temperatura de 4 bytes en formato flotante.
Con struct podemos interpretar esos campos sin tener que convertir manualmente cada byte.
import struct
Operaciones básicas
<bytes> = struct.pack('<format>', <el_1>, ...) # Convierte valores de Python en bytes.
<tuple> = struct.unpack('<format>', <bytes>) # Recupera los valores desde una secuencia binaria.
<int> = struct.calcsize('<format>') # Devuelve el tamaño del formato en bytes.
<iter> = struct.iter_unpack('<format>', <bytes>) # Recorre registros binarios como tuplas.
Los tres métodos principales son:
pack(): empaqueta valores en una secuencia de bytes.unpack(): interpreta los bytes y devuelve los valores en una tupla.calcsize(): calcula cuántos bytes necesita una estructura.
Ejemplo básico: empaquetar y desempaquetar
Vamos a convertir tres números enteros en una secuencia de bytes.
from struct import pack, unpack
datos = pack(">hhl", 1, 2, 3)
print(datos)
# b'\x00\x01\x00\x02\x00\x00\x00\x03'
El formato >hhl indica:
>: utilizar el orden big-endian.h: primer entero de 2 bytes.h: segundo entero de 2 bytes.l: tercer entero de 4 bytes.
Por tanto, los datos ocupan 8 bytes.
Podemos recuperar los valores originales:
valores = unpack(">hhl", datos)
print(valores)
# (1, 2, 3)
También podemos consultar el tamaño de la estructura:
import struct
print(struct.calcsize(">hhl"))
# 8
El orden de bytes: Big-endian y Little-endian
Una de las cuestiones más importantes al trabajar con información binaria es comprender el orden de los bytes, conocido como endianness.
Cuando un número ocupa más de un byte, necesitamos decidir cuál se almacena primero.
Representación del número 0x1234: Big-endian y Little-endian
Cuando almacenamos un número que ocupa varios bytes, el orden en el que se guardan puede variar según el sistema o el protocolo.
Por ejemplo, el número hexadecimal 0x1234 ocupa 2 bytes: 12 y 34.
Big-endian (>): almacena primero el byte más significativo.
Posición: Byte 0 Byte 1
┌─────────┬─────────┐
Valor: │ 12 │ 34 │
└─────────┴─────────┘
Little-endian (<): almacena primero el byte menos significativo.
Posición: Byte 0 Byte 1
┌─────────┬─────────┐
Valor: │ 34 │ 12 │
└─────────┴─────────┘
En ambos casos el número es el mismo (0x1234). Lo único que cambia es el orden en el que se almacenan sus bytes.
En los protocolos TCP/IP se utiliza habitualmente Big-endian, también conocido como Network Byte Order. En Python podemos indicarlo mediante el prefijo ! del módulo struct.
Podemos comprobarlo directamente con Python:
import struct
numero = 0x1234
big_endian = struct.pack(">H", numero)
little_endian = struct.pack("<H", numero)
print(big_endian.hex()) # 1234
print(little_endian.hex()) # 3412
Ambas representaciones contienen el mismo valor numérico, pero almacenan los bytes en diferente orden.
En redes IP, el orden de bytes utilizado habitualmente para los campos numéricos de los protocolos es big-endian, conocido como network byte order.
Prefijos de formato
El primer carácter de la cadena de formato determina el orden de los bytes, el tamaño de los tipos y las reglas de alineación.
| Prefijo | Orden de bytes | Tamaño de tipos | Alineación |
|---|---|---|---|
@ |
Nativo del sistema | Nativo | Nativa |
= |
Nativo del sistema | Estándar | Sin relleno automático |
< |
Little-endian | Estándar | Sin relleno automático |
> |
Big-endian | Estándar | Sin relleno automático |
! |
Big-endian (orden de red) | Estándar | Sin relleno automático |
Si no especificamos ningún prefijo, Python utiliza @ por defecto.
Esto significa que el tamaño y la alineación pueden depender de la plataforma.
Mi recomendación: cuando trabajamos con archivos binarios que deben poder leerse en distintos equipos o con protocolos de red, conviene definir siempre el prefijo explícitamente. Normalmente utilizaremos <, > o !.
Tipos de datos enteros
Cada carácter de formato identifica el tipo de dato que vamos a empaquetar.
| Formato | Tipo equivalente en C | Bytes estándar | Rango |
|---|---|---|---|
b |
signed char | 1 | -128 a 127 |
B |
unsigned char | 1 | 0 a 255 |
h |
signed short | 2 | -32.768 a 32.767 |
H |
unsigned short | 2 | 0 a 65.535 |
i |
signed int | 4 | -2.147.483.648 a 2.147.483.647 |
I |
unsigned int | 4 | 0 a 4.294.967.295 |
l |
signed long | 4 | -2.147.483.648 a 2.147.483.647 |
L |
unsigned long | 4 | 0 a 4.294.967.295 |
q |
signed long long | 8 | -2⁶³ a 2⁶³ − 1 |
Q |
unsigned long long | 8 | 0 a 2⁶⁴ − 1 |
Los tamaños indicados corresponden a los formatos estándar, es decir, cuando utilizamos =, <, > o !.
Si utilizamos el prefijo @, el tamaño de algunos tipos puede variar según la plataforma.
Una regla sencilla para recordar: las letras minúsculas representan enteros con signo y sus equivalentes mayúsculas, enteros sin signo.
Tipos de punto flotante
También podemos almacenar números decimales mediante formatos de punto flotante.
| Formato | Tipo | Bytes |
|---|---|---|
e |
Float IEEE 754 de 16 bits | 2 |
f |
Float IEEE 754 de 32 bits | 4 |
d |
Double IEEE 754 de 64 bits | 8 |
Ejemplo:
import struct
temperatura = 23.5
datos = struct.pack(">f", temperatura)
print(datos.hex())
# 41bc0000
resultado = struct.unpack(">f", datos)
print(resultado)
# (23.5,)
En este caso estamos almacenando un número de punto flotante de 32 bits, equivalente a 4 bytes.
Hay que tener presente que los valores de punto flotante pueden presentar pequeñas diferencias de precisión debido a su representación binaria.
Caracteres y secuencias de bytes
Además de números, struct permite trabajar con caracteres y campos binarios.
| Formato | Significado |
|---|---|
c |
Un objeto bytes de longitud 1. |
Ns |
Una secuencia de N bytes. |
Np |
Cadena de bytes con prefijo de longitud, almacenada en N bytes. |
x |
Un byte de relleno. |
? |
Valor booleano. |
El número que aparece delante del formato indica el tamaño o la cantidad de elementos.
Ejemplo: campo de texto de 8 bytes
import struct
datos = struct.pack("8s", b"Python")
print(datos)
# b'Python\x00\x00'
Como el texto ocupa 6 bytes y hemos reservado 8, Python añade dos bytes nulos (\x00).
Podemos recuperar el contenido:
texto = struct.unpack("8s", datos)[0]
print(texto.rstrip(b"\x00").decode("utf-8"))
# Python
El formato s trabaja con bytes, no directamente con cadenas Unicode. Por eso, antes de empaquetar texto debemos codificarlo.
Ejemplo práctico: interpretar una cabecera de red
Vamos a imaginar un protocolo sencillo que utiliza una cabecera de 8 bytes con los siguientes campos:
| Campo | Tamaño | Formato | Descripción |
|---|---|---|---|
| Versión | 1 byte | B |
Versión del protocolo. |
| Tipo | 1 byte | B |
Tipo de mensaje. |
| Identificador | 2 bytes | H |
Identificador del paquete. |
| Longitud | 4 bytes | I |
Longitud del mensaje. |
La estructura utilizará el formato !BBHI.
El símbolo ! indica que los números se almacenan en orden de red (big-endian).
import struct
version = 1
tipo = 2
identificador = 100
longitud = 1500
paquete = struct.pack(
"!BBHI",
version,
tipo,
identificador,
longitud
)
print(paquete.hex())
# 01020064000005dc
print(len(paquete))
# 8
Ahora imaginemos que recibimos estos 8 bytes desde un dispositivo o una conexión de red.
Podemos recuperar cada campo utilizando unpack():
version, tipo, identificador, longitud = struct.unpack(
"!BBHI",
paquete
)
print("Versión:", version)
print("Tipo:", tipo)
print("Identificador:", identificador)
print("Longitud:", longitud)
Resultado:
Versión: 1
Tipo: 2
Identificador: 100
Longitud: 1500
Este ejemplo utiliza un protocolo ficticio, pero el procedimiento es el mismo que emplearíamos para interpretar campos de una cabecera binaria real, respetando los tamaños y formatos definidos por el protocolo.
Interpretar múltiples registros con iter_unpack()
Cuando tenemos varios registros almacenados consecutivamente en un mismo bloque de bytes, podemos utilizar struct.iter_unpack().
En lugar de recuperar un único conjunto de valores, obtenemos un iterador que devuelve una tupla por cada registro.
import struct
datos = (
struct.pack(">HH", 1, 100) +
struct.pack(">HH", 2, 200) +
struct.pack(">HH", 3, 300)
)
for identificador, valor in struct.iter_unpack(">HH", datos):
print(identificador, valor)
Resultado:
1 100
2 200
3 300
Cada registro ocupa 4 bytes: dos para el identificador y dos para el valor.
Para utilizar iter_unpack(), el tamaño total del bloque debe ser múltiplo del tamaño de cada registro.
Leer estructuras binarias desde un archivo
Podemos combinar struct con la lectura de archivos binarios que vimos anteriormente.
import struct
FORMATO = ">Hf"
def escribir_registro(filename, identificador, temperatura):
datos = struct.pack(FORMATO, identificador, temperatura)
with open(filename, "wb") as archivo:
archivo.write(datos)
def leer_registro(filename):
with open(filename, "rb") as archivo:
datos = archivo.read()
if len(datos) != struct.calcsize(FORMATO):
raise ValueError("El archivo no contiene un registro válido")
return struct.unpack(FORMATO, datos)
Ejemplo:
escribir_registro("sensor.bin", 15, 24.5)
identificador, temperatura = leer_registro("sensor.bin")
print(identificador) # 15
print(temperatura) # 24.5
Este procedimiento puede resultar útil para guardar mediciones de sensores, intercambiar datos con microcontroladores o interpretar información procedente de dispositivos.
Errores habituales con struct
1. Utilizar un formato que no coincide con los datos
struct.unpack(">I", b"\x01\x02")
El formato I necesita 4 bytes y estamos proporcionando solamente 2. Python generará struct.error.
2. Utilizar el orden de bytes incorrecto
datos = b"\x12\x34"
print(struct.unpack(">H", datos)[0]) # 4660
print(struct.unpack("<H", datos)[0]) # 13330
Los bytes son exactamente los mismos, pero el resultado cambia porque se interpretan con un orden distinto.
3. Intentar almacenar un número fuera del rango permitido
struct.pack("B", 300)
El formato B admite valores de 0 a 255, por lo que esta operación genera un error.
Resumen de las funciones de struct
| Función | Descripción |
|---|---|
struct.pack() |
Convierte valores de Python en una secuencia de bytes. |
struct.unpack() |
Convierte bytes en una tupla de valores. |
struct.calcsize() |
Calcula el tamaño de una estructura binaria. |
struct.iter_unpack() |
Recorre múltiples registros de un bloque binario. |
struct.pack_into() |
Escribe una estructura en un búfer modificable. |
struct.unpack_from() |
Extrae una estructura desde una posición determinada de un búfer. |
struct.Struct() |
Crea un objeto con un formato reutilizable. |
Con bytes podemos representar y manipular información binaria. Con struct damos un paso más: definimos cómo se distribuyen esos bytes y cómo interpretarlos para recuperar sus valores.
Es una herramienta especialmente práctica en networking, sistemas y programación de dispositivos, donde muchas veces recibimos información que no está organizada como texto, sino como campos binarios con tamaños y posiciones definidos.
Arrays
Lista que solo puede contener números de un tipo predefinido. Los tipos disponibles y sus tamaños mínimos en bytes se enumeran arriba. El tamaño de los tipos y el orden de los bytes siempre los determina el sistema; sin embargo, los bytes de cada elemento se pueden intercambiar con el método byteswap().
from array import array
<array> = array('<typecode>', <coll_of_nums>) # Array desde colección de números.
<array> = array('<typecode>', <bytes>) # Array desde bytes objeto.
<array> = array('<typecode>', <array>) # Trata array como un secuencia de números.
<array>.fromfile(<file>, n_items) # Añade elementos desde the binary archivo.
<bytes> = bytes(<array>) # Devuelve una copia de la memoria del array.
<file>.write(<array>) # Escribe array al binary archivo.
Vistas de memoria
Un objeto de secuencia que apunta a la memoria de otro objeto similar a bytes. Cada elemento puede hacer referencia a uno o varios bytes consecutivos, según el formato. El orden y el número de elementos se pueden modificar mediante el segmentado.
<mview> = memoryview(<bytes/bytearray/array>) # Immutable si bytes, else mutable.
<real> = <mview>[index] # Devuelve un entero o un número de coma flotante.
<mview> = <mview>[<slice>] # Devuelve mview con rearranged elementos.
<mview> = <mview>.cast('<typecode>') # Solo works between b/B/c y other tipos.
<mview>.release() # Releases memory buffer del base objeto.
<bytes> = bytes(<mview>) # Devuelve un nuevo objeto `bytes`.
<bytes> = <bytes>.join(<coll_of_mviews>) # Une mviews using bytes como un separator.
<array> = array('<typecode>', <mview>) # Trata mview como un secuencia de números.
<file>.write(<mview>) # Escribe mview al binary archivo.
<list> = list(<mview>) # Devuelve una lista de enteros o números de coma flotante.
<str> = str(<mview>, 'utf-8') # Trata mview como un bytes objeto.
<str> = <mview>.hex() # Devuelve hex pairs. Acepta `sep=<str>`.
Colas de doble extremo
Una lista segura para subprocesos con adiciones y eliminaciones eficientes desde ambos lados. Se pronuncia "deck".
from collections import deque
<deque> = deque(<collection>) # Usa `maxlen=<int>` to conjunto tamaño limit.
<deque>.appendleft(<el>) # Opposite elemento is dropped si full.
<deque>.extendleft(<collection>) # proporcionados colección obtiene reversed.
<deque>.rotate(n=1) # Último elemento becomes primero.
<el> = <deque>.popleft() # Lanza IndexError si deque is vacío.
#Hilos
El intérprete de CPython solo puede ejecutar un único hilo a la vez. El uso de múltiples hilos no resultará en una ejecución más rápida, a menos que al menos uno de los hilos contenga una operación de entrada/salida.
from threading import Thread, Lock, RLock, Semaphore, Event, Barrier
from concurrent.futures import ThreadPoolExecutor, as_completed
Hilo
<Thread> = Thread(target=<function>)# Usa `args=<collection>` to conjunto the argumentos.
<Thread>.start() # Inicia the thread. También <Thread>.is_alive().
<Thread>.join() # Espera para el thread to finish.
- Utilice
kwargs=<dict>para pasar argumentos con nombre a la función. - Utilice
daemon=True, de lo contrario el programa no podrá finalizar mientras el hilo esté activo.
Bloqueos
<lock> = Lock/RLock() # RLock can solo be released by acquirer.
<lock>.acquire() # Espera para el lock to be available.
<lock>.release() # Makes the lock available again.
O bien:
with <lock>: # Enters the block by calling acquire() y
... # exits it con release(), even si hay error.
Semáforos, eventos y barreras
<Semaphore> = Semaphore(value=1) # Lock that puede ser acquired by 'valor' threads.
<Event> = Event() # método wait() blocks hasta conjunto() is called.
<Barrier> = Barrier(n_times) # Wait() blocks hasta it's called n_times.
Colas
<Queue> = queue.Queue(maxsize=0) # A thread-safe primero-in-primero-out queue.
<Queue>.put(<el>) # Blocks hasta queue detiene being full.
<Queue>.put_nowait(<el>) # Lanza queue.Full excepción si full.
<el> = <Queue>.get() # Blocks hasta queue detiene being vacío.
<el> = <Queue>.get_nowait() # Lanza queue.vacío excepción si vacío.
Grupo de hilos
<Exec> = ThreadPoolExecutor(max_workers=None) # O bien: `with ThreadPoolExecutor() as <name>: ...`
<iter> = <Exec>.map(<func>, <args_1>, ...) # Multithreaded y non-lazy map(). Mantiene orden.
<Futr> = <Exec>.submit(<func>, <arg_1>, ...) # Crea una thread y devuelve its Future obj.
<Exec>.shutdown() # Blocks hasta todos los threads finish executing.
<bool> = <Future>.done() # Comprueba si the thread has finished executing.
<obj> = <Future>.result(timeout=None) # Espera for thread to finish y devuelve resultado.
<bool> = <Future>.cancel() # Cancels or devuelve False si running/finished.
<iter> = as_completed(<coll_of_Futures>) # siguiente() espera for siguiente completed Future.
- Las funciones
map()yas_completed()también aceptantimeout. Esto provoca unfutures.TimeoutErrorcuando se llama anext()o cuando el hilo se bloquea.map()cuenta el tiempo desde la llamada original yas_completed()desde la primera llamada anext().as_completed()falla si se llama anext()demasiado tarde, incluso si el hilo finalizó a tiempo. - Las excepciones que ocurren dentro de los hilos se generan cuando se llama a
next()en el iterador demapo cuando se llama aresult()en unFuture. Su métodoexception()devuelve una excepción oNone. ProcessPoolExecutorproporciona paralelismo real, pero: todo lo que se envía a/desde los trabajadores debe ser[pickable](https://gto76.github.io/python-cheatsheet/#pickle), las colas deben enviarse usando los parámetrosinitargseinitializerdel ejecutor, y todos los ejecutores solo deben ser accesibles medianteif __name__ == "__main__": ....
Operadores
Módulo de funciones que proporcionan la funcionalidad de los operadores. Las funciones están ordenadas por precedencia de operador, comenzando por la de menor vinculación.
import operator as op
<bool> = op.not_(<obj>) # or, y, not (or/y ausentes)
<bool> = op.eq/ne/lt/ge/is_/is_not/contains(<obj>, <obj>) # ==, !=, <, >=, is, is not, in
<obj> = op.or_/xor/and_(<int/set>, <int/set>) # |, ^, &
<int> = op.lshift/rshift(<int>, <int>) # <<, >>
<obj> = op.add/sub/mul/truediv/floordiv/mod(<obj>, <obj>) # +, -, *, /, //, %
<num> = op.neg/invert(<num>) # -, ~
<num> = op.pow(<num>, <num>) #
<func> = op.itemgetter/attrgetter/methodcaller(<obj> [, ...]) # [índice/clave], .name, .name([…])
elementwise_sum = map(op.add, list_a, list_b)
sorted_by_second = sorted(<coll.>, key=op.itemgetter(1))
sorted_by_both = sorted(<coll.>, key=op.itemgetter(1, 0))
product_of_elems = functools.reduce(op.mul, <collection>)
first_element = op.methodcaller('pop', 0)(<list>)
- La mayoría de los operadores llaman al método especial del objeto que lleva su nombre (el segundo objeto se pasa como argumento), mientras que los operadores lógicos llaman a su propio código que utiliza
bool(). - Las comparaciones se pueden encadenar:
x < y < zes lo mismo que(x < y) y (y < z).
Sentencia match
Ejecuta el primer bloque con el patrón coincidente. Añadido en Python 3.10.
match <object/expression>:
case <pattern> [if <condition>]:
<code>
...
Patrones
<value_pattern> = 1/'abc'/True/None/math.pi # Coincide con the literal or a dotted name.
<class_pattern> = <type>() # Coincide con cualquier objeto de that tipo.
<wildcard_patt> = _ # Coincide con cualquier objeto.
<capture_patt> = <name> # Coincide con cualquier objeto y vincula it to name.
<or_pattern> = <pattern> | <pattern> [| ...] # Coincide con cualquier del patterns.
<as_pattern> = <pattern> as <name> # Vincula the match al name.
<sequence_patt> = [<pattern>, ...] # Coincide con secuencia con coincidentes elementos.
<mapping_patt> = {<value_pattern>: <pattern>, ...} # Coincide con diccionario con coincidentes elementos.
<class_pattern> = <type>(<attr_name>=<patt>, ...) # Coincide con objeto con coincidentes attributes.
- Los patrones de secuencia también se pueden escribir como tuplas.
- Utilice
*<nombre>y<nombre>en patrones de secuencia/mapeo para vincular los elementos restantes. - Los patrones de secuencia deben coincidir con todos los elementos, mientras que los patrones de mapeo no.
- Los patrones se pueden encerrar entre corchetes para anular la precedencia (
|>as>,). - Los tipos integrados permiten un único patrón posicional que se compara con todo el objeto.
- Todos los nombres vinculados en el caso de coincidencia, así como las variables inicializadas en su bloque, son visibles después de la instrucción de coincidencia.
Ejemplo
>>> from pathlib import Path
>>> match Path('/home/gto/python-cheatsheet/README.md'):
... case Path(
... parts=['/', 'home', user, *_],
... stem=stem,
... suffix=('.md' | '.txt') as suffix
... ) if stem.lower() == 'readme':
... print(f'{stem}{suffix} is a readme file that belongs to user {user}.')
'README.md is a readme file that belongs to user gto.'
Registro de eventos
import logging
logging.basicConfig(filename=<path>, level='DEBUG') # Configures the root logger (see Setup).
logging.debug/info/warning/error/critical(<str>) # Logs al root logger.
<Logger> = logging.getLogger(__name__) # Logger named después de the module.
<Logger>.<level>(<str>) # Logs al logger.
<Logger>.exception(<str>) # error() that añade caught excepción.
Configuración
logging.basicConfig(
filename=None, # Logs to console (stderr) de forma predeterminada.
format='%(levelname)s:%(name)s:%(message)s', # Add '%(asctime)s' for local datetime.
level=logging.WARNING, # Descarta messages con lower priority.
handlers=[logging.StreamHandler(sys.stderr)] # Utiliza FileHandler si filename is conjunto.
)
<Formatter> = logging.Formatter('<format>') # Crea una Formatter.
<Handler> = logging.FileHandler(<path>, mode='a') # Crea una Handler. También `encoding=None`.
<Handler>.setFormatter(<Formatter>) # Añade Formatter al Handler.
<Handler>.setLevel(<int/str>) # Procesa todos los messages de forma predeterminada.
<Logger>.addHandler(<Handler>) # Añade Handler al Logger.
<Logger>.setLevel(<int/str>) # What is sent to its/ancestors' handlers.
<Logger>.propagate = <bool> # Cuts off ancestors' handlers si False.
- El registrador padre se puede especificar nombrando al registrador hijo como
<padre>.<nombre>. - Si el registrador no tiene un nivel definido, lo hereda del primer ancestro que sí lo tenga.
- El formateador también acepta: ruta de acceso, nombre de archivo, nombre de función, número de línea, hilo y proceso.
- Un
handlers.RotatingFileHandlercrea y elimina archivos de registro según los argumentosmaxBytesybackupCount.
Crea un registrador que escribe todos los mensajes en un archivo y los envía al controlador raíz que imprime advertencias o mensajes de mayor nivel:
>>> logger = logging.getLogger('my_module')
>>> handler = logging.FileHandler('test.log', encoding='utf-8')
>>> handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s:%(name)s:%(message)s'))
>>> logger.addHandler(handler)
>>> logger.setLevel('DEBUG')
>>> logging.basicConfig()
>>> logging.root.handlers[0].setLevel('WARNING')
>>> logger.critical('Running out of disk space.')
CRITICAL:my_module:Running out of disk space.
>>> print(open('test.log').read())
2023-02-07 23:21:01,430 CRITICAL:my_module:Running out of disk space.
#Introspección
<list> = dir() # Names de local variables, funciones, clases, etc.
<dict> = vars() # Dict de local variables, etc. También locals().
<dict> = globals() # Dict de global vars, etc. (incl. '__builtins__').
<list> = dir(<object>) # Names de objeto's attributes (including métodos).
<dict> = vars(<object>) # Dict de writable attributes. También <obj>.__dict__.
<bool> = hasattr(<object>, '<attr_name>') # Comprueba si getattr() lanza an AttributeError.
value = getattr(<object>, '<attr_name>') # Valor predeterminado valor puede ser proporcionados as the third argumento.
setattr(<object>, '<attr_name>', value) # Solo works on objetos con __dict__ attribute.
delattr(<object>, '<attr_name>') # mismo. También `del <object>.<attr_name>`.
<Sig> = inspect.signature(<function>) # Devuelve función's Signature objeto.
<dict> = <Sig>.parameters # Dict de Parameter objetos. También <Sig>.return_type.
<memb> = <Param>.kind # Member de ParameterKind enum (KEYWORD_ONLY, ...).
<obj> = <Param>.default # Devuelve param's valor predeterminado valor or Parameter.vacío.
<type> = <Param>.annotation # Devuelve param's tipo hint or Parameter.vacío.
#Corrutinas
- Las corrutinas tienen mucho en común con los hilos, pero a diferencia de estos, solo ceden el control cuando llaman a otra corrutina y consumen menos memoria.
- La definición de una corrutina comienza con
asyncy su llamada conawait. asyncio.run(<coroutine>)es el punto de entrada principal para los programas asíncronos.
import asyncio as aio
<coro> = <async_function>(<args>) # Crea una corrutina al llamar a una función definida con async def.
<obj> = await <coroutine> # Ejecuta la corrutina y devuelve su resultado.
<task> = aio.create_task(<coroutine>) # Programa la corrutina para su ejecución concurrente.
<obj> = await <task> # Espera a que termine la tarea y devuelve su resultado. También admite cancel().
<coro> = aio.gather(<coro/task>, ...) # Ejecuta varias corrutinas concurrentemente y reúne sus resultados.
<coro> = aio.wait(<tasks>, ...) # Espera a las tareas según la condición indicada y devuelve (done, pending).
<iter> = aio.as_completed(<coros/tasks>) # Permite obtener los resultados según van finalizando las tareas.
Aclaraciones:
async def: define una función asíncrona.await: suspende la ejecución de la corrutina actual hasta que finaliza la operación esperada.create_task(): programa una corrutina como tarea dentro del bucle de eventos.gather(): permite ejecutar varias operaciones concurrentemente y obtener sus resultados en el orden en que se pasaron.wait(): devuelve dos conjuntos de tareas: las finalizadas (done) y las pendientes (pending). Admitereturn_when=asyncio.ALL_COMPLETED,FIRST_COMPLETEDoFIRST_EXCEPTION.as_completed(): permite procesar los resultados a medida que las operaciones terminan, sin esperar a que finalicen todas.
Nota: asyncio.wait() recibe tareas u objetos Future, no corrutinas directamente, desde Python 3.11. Además, aio.wait() devuelve una corrutina que debe esperarse con await, mientras que aio.gather() devuelve un objeto awaitable.
Ejecuta un juego de terminal donde controlas un asterisco que debe evitar los números:
import asyncio, collections, curses, curses.textpad, enum, random
P = collections.namedtuple('P', 'x y') # posición
D = enum.Enum('D', 'n e s w') # Direction
W, H = 15, 7 # Width, Height
def main(screen):
curses.curs_set(0) # Makes cursor invisible.
screen.nodelay(True) # Makes getch() non-blocking.
asyncio.run(main_coroutine(screen)) # Inicia running asyncio code.
async def main_coroutine(screen):
moves = asyncio.Queue()
state = {'*': P(0, 0), {id_: P(W//2, H//2) for id_ in range(10)}}
ai = [random_controller(id_, moves) for id_ in range(10)]
mvc = [human_controller(screen, moves), model(moves, state), view(state, screen)]
tasks = [asyncio.create_task(cor) for cor in ai + mvc]
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
async def random_controller(id_, moves):
while True:
d = random.choice(list(D))
moves.put_nowait((id_, d))
await asyncio.sleep(random.triangular(0.01, 0.65))
async def human_controller(screen, moves):
while True:
key_mappings = {258: D.s, 259: D.n, 260: D.w, 261: D.e}
if d := key_mappings.get(screen.getch()):
moves.put_nowait(('*', d))
await asyncio.sleep(0.005)
async def model(moves, state):
while state['*'] not in (state[id_] for id_ in range(10)):
id_, d = await moves.get()
deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)}
state[id_] = P((state[id_].x + deltas[d].x) % W, (state[id_].y + deltas[d].y) % H)
async def view(state, screen):
offset = P(curses.COLS//2 - W//2, curses.LINES//2 - H//2)
while True:
screen.erase()
curses.textpad.rectangle(screen, offset.y-1, offset.x-1, offset.y+H, offset.x+W)
for id_, p in state.items():
screen.addstr(offset.y + (p.y - state['*'].y + H//2) % H,
offset.x + (p.x - state['*'].x + W//2) % W, str(id_))
screen.refresh()
await asyncio.sleep(0.005)
if __name__ == '__main__':
curses.wrapper(main)
Bibliotecas
Barra de progreso
# $ pip3 install tqdm
>>> import tqdm, time
>>> for el in tqdm.tqdm([1, 2, 3], desc='Processing'):
... time.sleep(1)
Processing: 100%|████████████████████| 3/3 [00:03<00:00, 1.00s/it]
Gráficos
# $ pip3 install matplotlib
import matplotlib.pyplot as plt
plt.plot/bar/scatter(x_data, y_data [, label=<str>]) # O bien: plt.plot(y_data)
plt.legend() # Añade a legend.
plt.savefig(<path>) # Guarda the figure.
plt.show() # Muestra the figure.
plt.clf() # Clears the figure.
Tablas
Imprime a CSV spreadsheet al console:
# $ pip3 install tabulate
import csv, tabulate
with open('test.csv', encoding='utf-8', newline='') as file:
rows = list(csv.reader(file))
print(tabulate.tabulate(rows, headers='firstrow'))
Curses
Ejecuta un explorador de archivos básico en la consola:
# $ pip3 install windows-curses
import curses, os
from curses import A_REVERSE, KEY_DOWN, KEY_UP, KEY_LEFT, KEY_RIGHT, KEY_ENTER
def main(screen):
ch, first, selected, paths = 0, 0, 0, os.listdir()
while ch != ord('q'):
height, width = screen.getmaxyx()
screen.erase()
for y, filename in enumerate(paths[first : first+height]):
color = A_REVERSE if filename == paths[selected] else 0
screen.addnstr(y, 0, filename, width-1, color)
ch = screen.getch()
selected += (ch == KEY_DOWN) - (ch == KEY_UP)
selected = max(0, min(len(paths)-1, selected))
first += (selected >= first + height) - (selected < first)
if ch in [KEY_LEFT, KEY_RIGHT, KEY_ENTER, ord('\n'), ord('\r')]:
new_dir = '..' if ch == KEY_LEFT else paths[selected]
if os.path.isdir(new_dir):
os.chdir(new_dir)
first, selected, paths = 0, 0, os.listdir()
if __name__ == '__main__':
curses.wrapper(main)
PySimpleGUI
Una aplicación con interfaz gráfica de usuario para convertir pesos:
# $ pip3 install PySimpleGUI
import PySimpleGUI as sg
text_box = sg.Input(default_text='100', enable_events=True, key='-VALUE-')
dropdown = sg.InputCombo(['g', 'kg', 't'], 'kg', readonly=True, enable_events=True, k='-UNIT-')
label = sg.Text('100 kg is 220.462 lbs.', key='-OUTPUT-')
button = sg.Button('Close')
window = sg.Window('Weight Converter', [[text_box, dropdown], [label], [button]])
while True:
event, values = window.read()
if event in [sg.WIN_CLOSED, 'Close']:
break
try:
value = float(values['-VALUE-'])
except ValueError:
continue
unit = values['-UNIT-']
factors = {'g': 0.001, 'kg': 1, 't': 1000}
lbs = value * factors[unit] / 0.45359237
window['-OUTPUT-'].update(value=f'{value} {unit} is {lbs:g} lbs.')
window.close()
Extracción de datos web
Extrae la URL y el logotipo de Python de su página de Wikipedia:
# $ pip3 install requests beautifulsoup4
import requests, bs4, os
response = requests.get('https://en.wikipedia.org/wiki/Python_(programming_language)')
document = bs4.BeautifulSoup(response.text, 'html.parser')
table = document.find('table', class_='infobox vevent')
python_url = table.find('th', text='Website').next_sibling.a['href']
logo_url = table.find('img')['src']
logo = requests.get(f'https:{logo_url}').content
filename = os.path.basename(logo_url)
with open(filename, 'wb') as file:
file.write(logo)
print(f'{python_url}, file://{os.path.abspath(filename)}')
Selenium
Biblioteca para extraer datos de sitios web con contenido dinámico.
# $ pip3 install selenium
from selenium import webdriver
<Drv> = webdriver.Chrome/Firefox/Safari/Edge() # Abre the browser. También <Drv>.quit().
<Drv>.get('<url>') # También <Drv>.implicitly_wait(segundos).
<El> = <Drv/El>.find_element('css selector', '<css>') # '<tag>#<id>.<class>[<attr>="<val>"]'.
<list> = <Drv/El>.find_elements('xpath', '<xpath>') # '//<tag>[@<attr>="<val>"]'.
<str> = <El>.get_attribute/get_property(<str>) # También <El>.text/tag_name.
<El>.click/clear() # También <El>.send_keys(<str>).
XPath — también disponible en la consola del navegador a través de $x(<xpath>) y mediante la biblioteca lxml:
<xpath> = //<element>[/ or // <element>] # Child: /, Descendant: //, Parent: /..
<xpath> = //<element>/following::<element> # siguiente sibling. También preceding/parent/…
<element> = <tag><conditions><index> # `<tag> = */a/…`, `<index> = [1/2/…]`.
<condition> = [<sub_cond> [and/or <sub_cond>]] # For negation usa `not(<sub_cond>)`.
<sub_cond> = @<attr>="<val>" # `.="<val>"` coincide con complete text.
<sub_cond> = contains(@<attr>, "<val>") # Is <val> a substring de attr's valor?
<sub_cond> = [//]<element> # Has coincidentes child? Descendant si //.
Desarrollo web
Flask es un microframework/servidor web. Si solo quieres abrir un archivo HTML en un navegador web, usa 'webbrowser.open(<ruta>)' en su lugar.
# $ pip3 install flask
import flask
app = flask.Flask(__name__) # Devuelve app objeto. Put at the top.
app.run(host=None, port=None, debug=None) # O bien: `$ flask --app <module> run`
- La aplicación se inicia en
http://localhost:5000. Para ejecutarla externamente, utilicehost="0.0.0.0". - Para mayor seguridad, instale un servidor WSGI como Waitress y un servidor HTTP como Nginx.
- El modo de depuración reinicia la aplicación cada vez que se modifica el script y muestra los errores en el navegador.
Petición estática
@app.route('/img/<path:filename>')
def serve_file(filename):
return flask.send_from_directory('dirname/', filename)
Petición dinámica
@app.route('/<sport>')
def serve_html(sport):
return flask.render_template_string('<h1>{{title}}</h1>', title=sport)
- Utilice
render_template(filename, <kwargs>)para renderizar el archivo ubicado en el directorio de plantillas. - Para devolver un código de error, usa
abort(<int>)y para redirigir, usaredirect(<url>). request.args[<str>]devuelve el parámetro de la cadena de consulta (la parte de la URL después del signo de interrogación).session[<str>] = <obj>almacena los datos de la sesión. Requiereapp.secret_key = <str>.
Petición REST
@app.post('/<sport>/odds')
def serve_json(sport):
team = flask.request.form['team']
return {'team': team, 'odds': [2.09, 3.74, 3.68]}
Inicia la aplicación en su propio hilo y consulta su API REST:
# $ pip3 install requests
>>> import threading, requests
>>> threading.Thread(target=app.run, daemon=True).start()
>>> url = 'http://localhost:5000/football/odds'
>>> request_data = {'team': 'arsenal f.c.'}
>>> response = requests.post(url, data=request_data)
>>> response.json()
{'team': 'arsenal f.c.', 'odds': [2.09, 3.74, 3.68]}
Análisis de rendimiento
from time import perf_counter
start_time = perf_counter()
...
duration_in_seconds = perf_counter() - start_time
Medir el tiempo de un fragmento
>>> from timeit import timeit
>>> timeit('list(range(10000))', number=1000, globals=globals(), setup='pass')
0.19373
Análisis por línea
$ pip3 install line_profiler
$ echo '@profile
def main():
a = list(range(10000))
b = set(range(10000))
main()' > test.py
$ kernprof -lv test.py
Line # Hits tiempo Per Hit % tiempo línea Contents
==============================================================
1 @profile
2 def main():
3 1 253.4 253.4 32.2 a = list(range(10000))
4 1 534.1 534.1 67.8 b = set(range(10000))
Gráficos de llamadas y flame graphs
$ apt/brew install graphviz && pip3 install gprof2dot snakeviz # Or download installer.
$ tail --lines=+2 test.py > test.py # Elimina primero línea.
$ python3 -m cProfile -o test.prof test.py # Ejecuta built-in profiler.
$ gprof2dot --format=pstats test.prof | dot -T png -o test.png # Genera call graph.
$ xdg-open/open test.png # Muestra call graph.
$ snakeviz test.prof # Muestra flame graph.
Perfiladores de muestreo y memoria
| pip3 install | Target (Objetivo) | How to run (Ejecución) | Lines | Live |
|---|---|---|---|---|
| pyinstrument | CPU | pyinstrument test.py |
× | × |
| py-spy | CPU | py-spy top -- python3 test.py |
× | ✓ |
| scalene | CPU + Memory | scalene test.py |
✓ | × |
| memray | Memory | memray run --live test.py |
✓ | ✓ |
NumPy
Minilenguaje para la manipulación de arrays. Puede ejecutarse hasta cien veces más rápido que el código Python equivalente. Una alternativa aún más rápida que se ejecuta en una GPU se llama CuPy.
# $ pip3 install numpy
import numpy as np
<array> = np.array(<list/list_of_lists/…>) # Devuelve un array de NumPy de una o varias dimensiones.
<array> = np.zeros/ones/empty(<shape>) # También np.full(<shape>, <el>).
<array> = np.arange(from_inc, to_exc, ±step) # También np.linspace(inicio, stop, len).
# También np.random.random(<shape>).
<array> = np.random.randint(from_inc, to_exc, <shape>)
<view> = <array>.reshape(<shape>) # También `<array>.shape = <shape>`.
<array> = <array>.flatten() # También `<view> = <array>.ravel()`.
<view> = <array>.transpose() # O bien: <array>.T
<array> = np.copy/abs/sqrt/log/int64(<array>) # Devuelve nuevo array del mismo shape.
<array> = <array>.sum/max/mean/argmax/all(axis) # Aggregates indicados dimension.
<array> = np.apply_along_axis(<func>, axis, <array>)# Func can return a scalar or array.
<array> = np.concatenate(<list_of_arrays>, axis=0) # Links arrays along primero axis (filas).
<array> = np.row_stack/column_stack(<list_of_arrays>) # Trata 1d arrays as filas or columnas.
<array> = np.tile/repeat(<array>, <int/list> [, axis]) # Tiles array or repite its elementos.
- La forma es una tupla de tamaños de dimensión. Una imagen RGB de 100x50 tiene la forma (50, 100, 3).
- El eje es el índice de una dimensión. La dimensión más a la izquierda tiene el índice 0. Al sumar la imagen RGB a lo largo del eje 2, se obtiene una imagen en escala de grises con la forma (50, 100).
Indexación
<el> = <2d>[row_index, col_index] # O bien: <3d>[<int>, <int>, <int>]
<1d_view> = <2d>[row_index] # O bien: <3d>[<int>, <int>, <slice>]
<1d_view> = <2d>[:, col_index] # O bien: <3d>[<int>, <slice>, <int>]
<2d_view> = <2d>[from:to_row_i, from:to_col_i] # O bien: <3d>[<int>, <slice>, <slice>]
<1d_array> = <2d>[row_indices, col_indices] # O bien: <3d>[<int/1d>, <1d>, <1d>]
<2d_array> = <2d>[row_indices] # O bien: <3d>[<int/1d>, <1d>, <slice>]
<2d_array> = <2d>[:, col_indices] # O bien: <3d>[<int/1d>, <slice>, <1d>]
<2d_array> = <2d>[np.ix_(row_indices, col_indices)] # O bien: <3d>[<int/1d/2d>, <2d>, <2d>]
<2d_bools> = <2d> > <el/1d/2d> # 1d objeto must have tamaño de a fila.
<1/2d_arr> = <2d>[<2d/1d_bools>] # 1d_bools must have tamaño de a columna.
':'devuelve una porción de los índices de todas las dimensiones. Las dimensiones omitidas se convierten por defecto a':'.- Los índices no deben ser tuplas, ya que Python convierte
'obj[i, j]'en'obj[(i, j)]'. 'ix_()'devuelve dos matrices 2D. Los índices de diferentes formas se unifican mediante difusión.- Cualquier valor que se pueda difundir a la forma indexada se puede asignar a la selección.
Difusión (broadcasting)
conjunto de rules by which NumPy funciones operate on arrays de different sizes y/or dimensions.
left = [[0.1], [0.6], [0.8]] # Shape: (3, 1)
right = [ 0.1 , 0.6 , 0.8 ] # Shape: (3,)
1. Si array shapes differ in longitud, left-pad the shorter shape con ones:
left = [[0.1], [0.6], [0.8]] # Shape: (3, 1)
right = [[0.1 , 0.6 , 0.8]] # Shape: (1, 3) <- !
2. Si cualquier dimensions differ in tamaño, expand the ones that have tamaño 1 by duplicating their elementos:
left = [[0.1, 0.1, 0.1], # Shape: (3, 3) <- !
[0.6, 0.6, 0.6],
[0.8, 0.8, 0.8]]
right = [[0.1, 0.6, 0.8], # Shape: (3, 3) <- !
[0.1, 0.6, 0.8],
[0.1, 0.6, 0.8]]
Ejemplo
For cada point devuelve índice de its nearest point ([0.1, 0.6, 0.8] => [1, 2, 1]):
>>> points = np.array([0.1, 0.6, 0.8])
[ 0.1, 0.6, 0.8]
>>> wrapped_points = points.reshape(3, 1)
[[ 0.1],
[ 0.6],
[ 0.8]]
>>> distances = wrapped_points - points
[[ 0. , -0.5, -0.7],
[ 0.5, 0. , -0.2],
[ 0.7, 0.2, 0. ]]
>>> distances = np.abs(distances)
[[ 0. , 0.5, 0.7],
[ 0.5, 0. , 0.2],
[ 0.7, 0.2, 0. ]]
>>> distances[range(3), range(3)] = np.inf
[[ inf, 0.5, 0.7],
[ 0.5, inf, 0.2],
[ 0.7, 0.2, inf]]
>>> distances.argmin(1)
[1, 2, 1]
Imágenes
# $ pip3 install pillow
from PIL import Image
<Image> = Image.new('<mode>', (width, height)) # Crea nuevo image. También `color=<int/tuple>`.
<Image> = Image.open(<path>) # Identifica format based on archivo's contents.
<Image> = <Image>.convert('<mode>') # Convierte image al nuevo mode.
<Image>.save(<path>) # Selecciona format based on extension (png/jpg…).
<Image>.show() # Abre image in the valor predeterminado preview app.
<int/tuple> = <Image>.getpixel((x, y)) # Devuelve pixel's valor (its color).
<Image>.putpixel((x, y), <int/tuple>) # Actualiza pixel's valor.
<ImagingCore> = <Image>.getdata() # Devuelve una vista plana de los valores de los píxeles.
<Image>.putdata(<list/ImagingCore>) # Actualiza pixels con a copy del secuencia.
<Image>.paste(<Image>, (x, y)) # Draws proporcionados image at the indicados location.
<Image> = <Image>.filter(<Filter>) # `<Filter> = ImageFilter.<name>(<args>)`
<Image> = <Enhance>.enhance(<float>) # `<Enhance> = ImageEnhance.<name>(<Image>)`
<array> = np.array(<Image>) # Crea una 2d/3d NumPy array desde the image.
<Image> = Image.fromarray(np.uint8(<array>)) # Usa `<array>.clip(0, 255)` to clip valores.
Modos
'L'- Luminosidad (es decir, escala de grises). Cada píxel es un entero entre 0 y 255.'RGB'- Rojo, verde, azul (es decir, color verdadero). Cada píxel es una tupla de tres enteros.'RGBA'- RGB con canal alfa. Un valor alfa bajo (el cuarto entero) indica mayor transparencia.'HSV'- Espacio de color de tono, saturación y valor.
Ejemplos
Crea una imagen PNG de un degradado de arcoíris:
WIDTH, HEIGHT = 100, 100
n_pixels = WIDTH * HEIGHT
hues = (255 * i/n_pixels for i in range(n_pixels))
img = Image.new('HSV', (WIDTH, HEIGHT))
img.putdata([(int(h), 255, 255) for h in hues])
img.convert('RGB').save('test.png')
Agrega ruido a la imagen PNG y la muestra:
from random import randint
add_noise = lambda value: max(0, min(255, value + randint(-20, 20)))
img = Image.open('test.png').convert('HSV')
img.putdata([(add_noise(h), s, v) for h, s, v in img.getdata()])
img.show()
Dibujo en imágenes
from PIL import ImageDraw
<ImageDraw> = ImageDraw.Draw(<Image>) # objeto for adding 2D graphics al image.
<ImageDraw>.point((x, y)) # Draws a point. Trunca floats en ints.
<ImageDraw>.line((x1, y1, x2, y2 [, ...])) # To get anti-aliasing usa Image's resize().
<ImageDraw>.arc((x1, y1, x2, y2), deg1, deg2) # Always draws in clockwise direction.
<ImageDraw>.rectangle((x1, y1, x2, y2)) # To rotate usa Image's rotate() y paste().
<ImageDraw>.polygon((x1, y1, x2, y2, ...)) # Último point obtiene connected al primero.
<ImageDraw>.ellipse((x1, y1, x2, y2)) # To rotate usa Image's rotate() y paste().
<ImageDraw>.text((x, y), <str>, font=<Font>) # `<Font> = ImageFont.truetype(<path>, size)`
- Utilice
fill=<color>para establecer el color principal. - Utilice
width=<int>para establecer el grosor de las líneas o contornos. - Utilice
outline=<color>para establecer el color de los contornos. - El color puede ser un entero, una tupla, una cadena
#rrggbb[aa]o el nombre de un color.
Animación
Crea un GIF de una pelota rebotando:
# $ pip3 install imageio
from PIL import Image, ImageDraw
import imageio
WIDTH, HEIGHT, R = 126, 126, 10
frames = []
for velocity in range(1, 16):
y = sum(range(velocity))
frame = Image.new('L', (WIDTH, HEIGHT))
draw = ImageDraw.Draw(frame)
draw.ellipse((WIDTH/2-R, y, WIDTH/2+R, y+R*2), fill='white')
frames.append(frame)
frames += reversed(frames[1:-1])
imageio.mimsave('test.gif', frames, duration=0.03)
Audio
import wave
<Wave> = wave.open('<path>', 'rb') # Abre the WAV archivo.
<int> = <Wave>.getframerate() # Devuelve número de frames per second.
<int> = <Wave>.getnchannels() # Devuelve número de samples per frame.
<int> = <Wave>.getsampwidth() # Devuelve número de bytes per sample.
<tuple> = <Wave>.getparams() # Devuelve namedtuple de todos los parameters.
<bytes> = <Wave>.readframes(nframes) # Devuelve siguiente n frames. Todos los si -1.
<Wave> = wave.open('<path>', 'wb') # Crea/trunca a archivo for writing.
<Wave>.setframerate(<int>) # Pasa 44100 for CD, 48000 for video.
<Wave>.setnchannels(<int>) # Pasa 1 for mono, 2 for stereo.
<Wave>.setsampwidth(<int>) # Pasa 2 for CD, 3 for hi-res sound.
<Wave>.setparams(<tuple>) # Establece todos los parameters.
<Wave>.writeframes(<bytes>) # Añade frames al archivo.
- El objeto Bytes contiene una secuencia de tramas, cada una compuesta por una o más muestras.
- En una señal estéreo, la primera muestra de una trama pertenece al canal izquierdo.
- Cada muestra consta de uno o más bytes que, al convertirse a un entero, indican el desplazamiento de la membrana del altavoz en un momento dado.
- Si el ancho de la muestra es de un byte, el entero debe codificarse sin signo.
- Para todos los demás tamaños, el entero debe codificarse con signo y en orden de bytes little-endian.
Valores de muestra
| sampwidth (bytes) | Bits | min | zero (silencio) | max |
|---|---|---|---|---|
| 1 | 8-bit | 0 | 128 | 255 |
| 2 | 16-bit | -32,768 | 0 | 32,767 |
| 3 | 24-bit | -8,388,608 | 0 | 8,388,607 |
Leer muestras de punto flotante desde un archivo WAV
def read_wav_file(filename):
def get_int(bytes_obj):
an_int = int.from_bytes(bytes_obj, 'little', signed=(sampwidth != 1))
return an_int - 128 * (sampwidth == 1)
with wave.open(filename, 'rb') as file:
sampwidth = file.getsampwidth()
frames = file.readframes(-1)
bytes_samples = (frames[i : i+sampwidth] for i in range(0, len(frames), sampwidth))
return [get_int(b) / pow(2, sampwidth * 8 - 1) for b in bytes_samples]
Escribir muestras de punto flotante en un archivo WAV
def write_to_wav_file(filename, float_samples, nchannels=1, sampwidth=2, framerate=44100):
def get_bytes(a_float):
a_float = max(-1, min(1 - 2e-16, a_float))
a_float += sampwidth == 1
a_float *= pow(2, sampwidth * 8 - 1)
return int(a_float).to_bytes(sampwidth, 'little', signed=(sampwidth != 1))
with wave.open(filename, 'wb') as file:
file.setnchannels(nchannels)
file.setsampwidth(sampwidth)
file.setframerate(framerate)
file.writeframes(b''.join(get_bytes(f) for f in float_samples))
Ejemplos
Guarda una onda sinusoidal de 440 Hz en un archivo WAV mono:
from math import pi, sin
samples_f = (sin(i * 2 * pi * 440 / 44100) for i in range(100_000))
write_to_wav_file('test.wav', samples_f)
Agrega ruido al archivo WAV mono:
from random import random
add_noise = lambda value: value + (random() - 0.5) * 0.03
samples_f = (add_noise(f) for f in read_wav_file('test.wav'))
write_to_wav_file('test.wav', samples_f)
Reproduce el archivo WAV:
# $ pip3 install simpleaudio
from simpleaudio import play_buffer
with wave.open('test.wav', 'rb') as file:
p = file.getparams()
frames = file.readframes(-1)
play_buffer(frames, p.nchannels, p.sampwidth, p.framerate).wait_done()
Texto a voz
# $ pip3 install pyttsx3
import pyttsx3
engine = pyttsx3.init()
engine.say('Sally sells seashells by the seashore.')
engine.runAndWait()
Sintetizador
Reproduce la canción "Popcorn" de Gershon Kingsley:
# $ pip3 install simpleaudio
import array, itertools as it, math, simpleaudio
F = 44100
P1 = '71♩,69♪,,71♩,66♪,,62♩,66♪,,59♩,,,71♩,69♪,,71♩,66♪,,62♩,66♪,,59♩,,,'
P2 = '71♩,73♪,,74♩,73♪,,74♪,,71♪,,73♩,71♪,,73♪,,69♪,,71♩,69♪,,71♪,,67♪,,71♩,,,'
get_pause = lambda seconds: it.repeat(0, int(seconds * F))
sin_f = lambda i, hz: math.sin(i * 2 * math.pi * hz / F)
get_wave = lambda hz, seconds: (sin_f(i, hz) for i in range(int(seconds * F)))
get_hz = lambda note: 8.176 * 2 (int(note[:2]) / 12)
get_sec = lambda note: 1/4 if '♩' in note else 1/8
get_samples = lambda note: get_wave(get_hz(note), get_sec(note)) if note else get_pause(1/8)
samples_f = it.chain.from_iterable(get_samples(n) for n in (P1+P2).split(','))
samples_i = array.array('h', (int(f * 30000) for f in samples_f))
simpleaudio.play_buffer(samples_i, 1, 2, F).wait_done()
Pygame
# $ pip3 install pygame
import pygame as pg
pg.init()
screen = pg.display.set_mode((500, 500))
rect = pg.Rect(240, 240, 20, 20)
while not pg.event.get(pg.QUIT):
deltas = {pg.K_UP: (0, -20), pg.K_RIGHT: (20, 0), pg.K_DOWN: (0, 20), pg.K_LEFT: (-20, 0)}
for event in pg.event.get(pg.KEYDOWN):
dx, dy = deltas.get(event.key, (0, 0))
rect = rect.move((dx, dy))
screen.fill(pg.Color('black'))
pg.draw.rect(screen, pg.Color('white'), rect)
pg.display.flip()
Rectángulos
Objeto para almacenar coordenadas rectangulares.
<Rect> = pg.Rect(x, y, width, height) # Floats get truncated en ints.
<int> = <Rect>.x/y/centerx/centery/# Top, right, bottom, left. Permite assignments.
<tup.> = <Rect>.topleft/center/… # Topright, bottomright, bottomleft. mismo.
<Rect> = <Rect>.move((delta_x, delta_y)) # Usa move_ip() to move en el mismo objeto.
<bool> = <Rect>.collidepoint((x, y)) # Comprueba si rectangle contains the point.
<bool> = <Rect>.colliderect(<Rect>) # Comprueba si the two rectangles overlap.
<int> = <Rect>.collidelist(<list_of_Rect>) # Devuelve índice de primero colliding Rect or -1.
<list> = <Rect>.collidelistall(<list_of_Rect>) # Devuelve indexes de todos los colliding rectangles.
Superficies
Objeto para representar imágenes.
<Surf> = pg.display.set_mode((width, height)) # Abre nuevo window y devuelve its surface.
<Surf> = pg.Surface((width, height)) # nuevo RGB surface. RGBA si `flags=pg.SRCALPHA`.
<Surf> = pg.image.load(<path/file>) # Carga the image. Format depends on source.
<Surf> = pg.surfarray.make_surface(<np_array>) # También `<np_arr> = surfarray.pixels3d(<Surf>)`.
<Surf> = <Surf>.subsurface(<Rect>) # Crea un nuevo surface desde the cutout.
<Surf>.fill(color) # tupla, Color('#rrggbb[aa]') or Color(<name>).
<Surf>.set_at((x, y), color) # Actualiza pixel. También <Surf>.get_at((x, y)).
<Surf>.blit(<Surf>, (x, y)) # Draws proporcionados surface at indicados location.
from pygame.transform import scale, ...
<Surf> = scale(<Surf>, (width, height)) # Devuelve scaled surface.
<Surf> = rotate(<Surf>, anticlock_degrees) # Devuelve rotated y scaled surface.
<Surf> = flip(<Surf>, x_bool, y_bool) # Devuelve flipped surface.
from pygame.draw import line, ...
line(<Surf>, color, (x1, y1), (x2, y2), width) # Draws a línea al surface.
arc(<Surf>, color, <Rect>, from_rad, to_rad) # También ellipse(<Surf>, color, <Rect>, width=0).
rect(<Surf>, color, <Rect>, width=0) # También polygon(<Surf>, color, points, width=0).
Fuentes
<Font> = pg.font.Font(<path/file>, size) # Carga TTF archivo. Pasa None for valor predeterminado font.
<Surf> = <Font>.render(text, antialias, color) # Background color puede ser indicados al final.
Sonido
<Sound> = pg.mixer.Sound(<path/file/bytes>) # WAV archivo or bytes/array de signed shorts.
<Sound>.play/stop() # También set_volume(<float>), fadeout(msec).
Ejemplo básico de Mario Bros.
import collections, dataclasses, enum, io, itertools as it, pygame as pg, urllib.request
from random import randint
P = collections.namedtuple('P', 'x y') # posición
D = enum.Enum('D', 'n e s w') # Direction
W, H, MAX_S = 50, 50, P(5, 10) # Width, Height, Max speed
def main():
def get_screen():
pg.init()
return pg.display.set_mode((W*16, H*16))
def get_images():
url = 'https://gto76.github.io/python-cheatsheet/web/mario_bros.png'
img = pg.image.load(io.BytesIO(urllib.request.urlopen(url).read()))
return [img.subsurface(get_rect(x, 0)) for x in range(img.get_width() // 16)]
def get_mario():
Mario = dataclasses.make_dataclass('Mario', 'rect spd facing_left frame_cycle'.split())
return Mario(get_rect(1, 1), P(0, 0), False, it.cycle(range(3)))
def get_tiles():
border = [(x, y) for x in range(W) for y in range(H) if x in [0, W-1] or y in [0, H-1]]
platforms = [(randint(1, W-2), randint(2, H-2)) for _ in range(W*H // 10)]
return [get_rect(x, y) for x, y in border + platforms]
def get_rect(x, y):
return pg.Rect(x*16, y*16, 16, 16)
run(get_screen(), get_images(), get_mario(), get_tiles())
def run(screen, images, mario, tiles):
clock = pg.time.Clock()
pressed = set()
while not pg.event.get(pg.QUIT) and clock.tick(28):
keys = {pg.K_UP: D.n, pg.K_RIGHT: D.e, pg.K_DOWN: D.s, pg.K_LEFT: D.w}
pressed |= {keys.get(e.key) for e in pg.event.get(pg.KEYDOWN)}
pressed -= {keys.get(e.key) for e in pg.event.get(pg.KEYUP)}
update_speed(mario, tiles, pressed)
update_position(mario, tiles)
draw(screen, images, mario, tiles, pressed)
def update_speed(mario, tiles, pressed):
x, y = mario.spd
x += 2 * ((D.e in pressed) - (D.w in pressed))
x += (x < 0) - (x > 0)
y += 1 if D.s not in get_boundaries(mario.rect, tiles) else (D.n in pressed) * -10
mario.spd = P(x=max(-MAX_S.x, min(MAX_S.x, x)), y=max(-MAX_S.y, min(MAX_S.y, y)))
def update_position(mario, tiles):
x, y = mario.rect.topleft
n_steps = max(abs(s) for s in mario.spd)
for _ in range(n_steps):
mario.spd = stop_on_collision(mario.spd, get_boundaries(mario.rect, tiles))
mario.rect.topleft = x, y = x + (mario.spd.x / n_steps), y + (mario.spd.y / n_steps)
def get_boundaries(rect, tiles):
deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)}
return {d for d, delta in deltas.items() if rect.move(delta).collidelist(tiles) != -1}
def stop_on_collision(spd, bounds):
return P(x=0 if (D.w in bounds and spd.x < 0) or (D.e in bounds and spd.x > 0) else spd.x,
y=0 if (D.n in bounds and spd.y < 0) or (D.s in bounds and spd.y > 0) else spd.y)
def draw(screen, images, mario, tiles, pressed):
def get_marios_image_index():
if D.s not in get_boundaries(mario.rect, tiles):
return 4
return next(mario.frame_cycle) if {D.w, D.e} & pressed else 6
screen.fill((85, 168, 255))
mario.facing_left = (D.w in pressed) if {D.w, D.e} & pressed else mario.facing_left
screen.blit(images[get_marios_image_index() + mario.facing_left * 9], mario.rect)
for t in tiles:
screen.blit(images[18 if t.x in [0, (W-1)*16] or t.y in [0, (H-1)*16] else 19], t)
pg.display.flip()
if __name__ == '__main__':
main()
Pandas
# $ pip3 install pandas matplotlib
import pandas as pd, matplotlib.pyplot as plt
Series
Diccionario ordenado con un nombre.
>>> pd.Series([1, 2], index=['x', 'y'], name='a')
x 1
y 2
Name: a, dtype: int64
<Sr> = pd.Series(<list>) # Assigns RangeIndex starting at 0.
<Sr> = pd.Series(<dict>) # Takes diccionario's claves for índice.
<Sr> = pd.Series(<dict/Series>, index=<list>) # Solo mantiene elementos con claves indicados in índice.
<el> = <Sr>.loc[key] # O bien: <Sr>.iloc[i]
<Sr> = <Sr>.loc[coll_of_keys] # O bien: <Sr>.iloc[coll_of_i]
<Sr> = <Sr>.loc[from_key : to_key_inc] # O bien: <Sr>.iloc[from_i : to_i_exc]
<el> = <Sr>[key/i] # O bien: <Sr>.<key>
<Sr> = <Sr>[coll_of_keys/coll_of_i] # O bien: <Sr>[clave/i : clave/i]
<Sr> = <Sr>[bools] # O bien: <Sr>.loc/iloc[bools]
<Sr> = <Sr> > <el/Sr> # Devuelve una serie de valores booleanos.
<Sr> = <Sr> + <el/Sr> # elementos con non-coincidentes claves get valor NaN.
<Sr> = pd.concat(<coll_of_Sr>) # Concats varios series en one long Series.
<Sr> = <Sr>.combine_first(<Sr>) # Añade elementos that are not yet present.
<Sr>.update(<Sr>) # Actualiza elementos that are already present.
<Sr>.plot.line/area/bar/pie/hist() # Genera a Matplotlib plot.
plt.show() # Muestra the plot. También plt.savefig(<path>).
Series — Aggregate, Transform, Map:
<el> = <Sr>.sum/max/mean/idxmax/all() # O bien: <Sr>.agg(lambda <Sr>: <el>)
<Sr> = <Sr>.rank/diff/cumsum/ffill/interpo…() # O bien: <Sr>.agg/transform(lambda <Sr>: <Sr>)
<Sr> = <Sr>.fillna(<el>) # O bien: <Sr>.agg/transform/map(lambda <el>: <el>)
>>> sr = pd.Series([2, 3], index=['x', 'y'])
x 2
y 3
| Método | 'sum' | ['sum'] | {'s': 'sum'} |
|---|---|---|---|
sr.apply(…) |
5 | sum: 5 | s: 5 |
sr.agg(…) |
5 | sum: 5 | s: 5 |
| Método | 'rank' | ['rank'] | {'r': 'rank'} |
|---|---|---|---|
sr.apply(…) |
x: 1, y: 2 | rank: x: 1, y: 2 | r: x: 1, y: 2 |
sr.agg(…) |
x: 1, y: 2 | rank: x: 1, y: 2 | r: x: 1, y: 2 |
- Los objetos indexados no pueden ser tuplas porque
obj[x, y]se convierte enobj[(x, y)]. - Los métodos
ffill(),interpolate(),fillna()ydropna()aceptaninplace=True. - El último resultado tiene un índice jerárquico. Usa
<Sr>[key_1, key_2]para obtener sus valores.
DataFrame
Tabla con filas y columnas etiquetadas.
>>> pd.DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['x', 'y'])
x y
a 1 2
b 3 4
<DF> = pd.DataFrame(<list_of_rows>) # filas puede ser either listas, dicts or series.
<DF> = pd.DataFrame(<dict_of_columns>) # columnas puede ser either listas, dicts or series.
<el> = <DF>.loc[row_key, col_key] # O bien: <DF>.iloc[row_i, col_i]
<Sr/DF> = <DF>.loc[row_key/s] # O bien: <DF>.iloc[row_i/s]
<Sr/DF> = <DF>.loc[:, col_key/s] # O bien: <DF>.iloc[:, col_i/s]
<DF> = <DF>.loc[row_bools, col_bools] # O bien: <DF>.iloc[row_bools, col_bools]
<Sr/DF> = <DF>[col_key/s] # O bien: <DF>.<col_key>
<DF> = <DF>[row_bools] # Mantiene filas as indicados by bools.
<DF> = <DF>[<DF_of_bools>] # Assigns NaN to elementos that are False in bools.
<DF> = <DF> > <el/Sr/DF> # Devuelve DF de bools. Sr is treated como un fila.
<DF> = <DF> + <el/Sr/DF> # elementos con non-coincidentes claves get valor NaN.
<DF> = <DF>.set_index(col_key) # Sustituye fila claves con columna's valores.
<DF> = <DF>.reset_index(drop=False) # Descarta or mueve fila claves to columna named índice.
<DF> = <DF>.sort_index(ascending=True) # Ordena filas by fila claves. Usa `axis=1` for cols.
<DF> = <DF>.sort_values(col_key/s) # Ordena filas by proporcionados columna/s. También `axis=1`.
DataFrame — Merge, Join, Concat:
>>> l = pd.DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['x', 'y'])
x y
a 1 2
b 3 4
>>> r = pd.DataFrame([[4, 5], [6, 7]], index=['b', 'c'], columns=['y', 'z'])
y z
b 4 5
c 6 7
| Operación / Método | 'outer' | 'inner' | 'left' | Description |
|---|---|---|---|---|
| l.merge(r, on='y', how=...) | x y z <br>0 1 2 . <br>1 3 4 5 <br>2 . 6 7 | x y z <br>3 4 5 | x y z <br>1 2 . <br>3 4 5 | Une por columnas si se define on o left/right_on, si no, por columnas comunes. Usa 'inner' por defecto. |
| l.join(r, lsuffix='l', rsuffix='r', how=...) | x yl yr z <br>a 1 2 . . <br>b 3 4 4 5 <br>c . . 6 7 | x yl yr z <br>3 4 4 5 | x yl yr z <br>1 2 . . <br>3 4 4 5 | Une por las etiquetas de las filas (índices). Usa 'left' por defecto. Si r es una Serie, se trata como columna. |
| pd.concat([l, r], axis=0, join=...) | x y z <br>a 1 2 . <br>b 3 4 . <br>b . 4 5 <br>c . 6 7 | y <br>2 <br>4 <br>4 <br>6 | Añade filas al final. Usa 'outer' por defecto. Una Serie se trata como columna. Para añadir una fila usa pd.concat([l, DF([sr])]). |
|
| pd.concat([l, r], axis=1, join=...) | x y y z <br>a 1 2 . . <br>b 3 4 4 5 <br>c . . 6 7 | x y y z <br>3 4 4 5 | Añade columnas al extremo derecho. Usa 'outer' por defecto. Una Serie se trata como una columna. | |
| l.combine_first(r) | x y z <br>a 1 2 . <br>b 3 4 5 <br>c . 6 7 | Añade filas y columnas faltantes. También actualiza elementos que contienen NaN. El argumento r debe ser un DataFrame. |
||
| Operación / Método | outer |
inner |
left |
Descripción |
l.merge(r, on='y', how=...) |
x y z<br>0 1 2 .<br>1 3 4 5<br>2 . 6 7 |
x y z<br>3 4 5 |
x y z<br>1 2 .<br>3 4 5 |
Une por columnas si se especifica on o left_on/right_on; de lo contrario, por columnas comunes. Utiliza inner por defecto. |
l.join(r, lsuffix='l', rsuffix='r', how=...) |
x yl yr z<br>a 1 2 . .<br>b 3 4 4 5<br>c . . 6 7 |
x yl yr z<br>b 3 4 4 5 |
x yl yr z<br>a 1 2 . .<br>b 3 4 4 5 |
Une por los índices de las filas. Utiliza left por defecto. Si r es una serie, se trata como una columna. |
pd.concat([l, r], axis=0, join=...) |
x y z<br>a 1 2 .<br>b 3 4 .<br>b . 4 5<br>c . 6 7 |
y<br>a 2<br>b 4<br>b 4<br>c 6 |
— | Añade filas al final. Utiliza outer por defecto. Una serie se trata como una columna. Para añadir una fila se puede utilizar pd.concat([l, pd.DataFrame([sr])]). |
pd.concat([l, r], axis=1, join=...) |
x y y z<br>a 1 2 . .<br>b 3 4 4 5<br>c . . 6 7 |
x y y z<br>b 3 4 4 5 |
— | Añade columnas a la derecha. Utiliza outer por defecto. Una serie se trata como una columna. |
l.combine_first(r) |
x y z<br>a 1 2 .<br>b 3 4 5<br>c . 6 7 |
— | — | Añade las filas y columnas que faltan y completa los valores NaN con los valores de r. El argumento r debe ser un DataFrame. |
DataFrame — Aggregate, Transform, Map:
<Sr> = <DF>.sum/max/mean/idxmax/all() # O bien: <DF>.apply/agg(lambda <Sr>: <el>)
<DF> = <DF>.rank/diff/cumsum/ffill/interpo…() # O bien: <DF>.apply/agg/transfo…(lambda <Sr>: <Sr>)
<DF> = <DF>.fillna(<el>) # O bien: <DF>.applymap(lambda <el>: <el>)
- Todos los operations operate on columnas de forma predeterminada. Pasa
'axis=1'to process the filas instead.
>>> df = pd.DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['x', 'y'])
x y
a 1 2
b 3 4
| Método | 'sum' |
['sum'] |
{'x': 'sum'} |
|---|---|---|---|
df.apply(…) |
x: 4, y: 6 | x: 4, y: 6 | x: 4 |
df.agg(…) |
x: 4, y: 6 | sum: (x: 4, y: 6) | x: 4 |
| Método | 'rank' |
['rank'] |
{'x': 'rank'} |
|---|---|---|---|
df.apply(…) |
x: a=1, b=2; y: a=1, b=2 | x: a=1, b=2; y: a=1, b=2 | x: a=1, b=2 |
df.agg(…) |
x: a=1, b=2; y: a=1, b=2 | x: a=1, b=2; y: a=1, b=2 | x: a=1, b=2 |
df.transform(…) |
x: a=1, b=2; y: a=1, b=2 | x: a=1, b=2; y: a=1, b=2 | x: a=1, b=2 |
- Usa
'<DF>[col_key_1, col_key_2][row_key]'to get the fifth resultado's valores.
DataFrame — Plot, Encode, Decode:
<DF>.plot.line/area/bar/scatter(x=col_key, …) # `y=col_key/s`. También hist/box(by=col_key).
plt.show() # Muestra the plot. También plt.savefig(<path>).
<DF> = pd.read_json/html('<str/path/url>') # Run `$ pip3 install beautifulsoup4 lxml`.
<DF> = pd.read_csv('<path/url>') # `header/index_col/dtype/parse_dates=<obj>`.
<DF> = pd.read_pickle/excel('<path/url>') # Usa `sheet_name=None` to get todos los Excel sheets.
<DF> = pd.read_sql('<table/query>', <conn.>) # SQLite3/SQLAlchemy connection (see #SQLite).
<dict> = <DF>.to_dict('d/l/s/…') # Devuelve columnas as dicts, listas or series.
<str> = <DF>.to_json/html/csv/latex() # Guarda output to archivo si ruta is proporcionados.
<DF>.to_pickle/excel(<path>) # Run `$ pip3 install "pandas[excel]" odfpy`.
<DF>.to_sql('<table_name>', <connection>) # También `if_exists='fail/replace/append'`.
GroupBy
objeto that groups together filas de a dataframe based en el valor del proporcionados columna.
>>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 6]], list('abc'), list('xyz'))
>>> df.groupby('z').get_group(6)
x y z
b 4 5 6
c 7 8 6
<GB> = <DF>.groupby(column_key/s) # Divide DF en groups based on proporcionados columna.
<DF> = <GB>.apply(<func>) # Maps cada group. Func can return DF, Sr or el.
<GB> = <GB>[column_key] # único columna GB. Todos los operations return a Sr.
<Sr> = <GB>.size() # A Sr de group sizes. mismo claves as get_group().
GroupBy — Aggregate, Transform, Map:
<DF> = <GB>.sum/max/mean/idxmax/all() # O bien: <GB>.agg(lambda <Sr>: <el>)
<DF> = <GB>.rank/diff/cumsum/ffill() # O bien: <GB>.transform(lambda <Sr>: <Sr>)
<DF> = <GB>.fillna(<el>) # O bien: <GB>.transform(lambda <Sr>: <Sr>)
>>> gb = df.groupby('z'); gb.apply(print)
x y z
a 1 2 3
x y z
b 4 5 6
c 7 8 6
┏━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━┓
┃ │ 'sum' │ 'rank' │ ['rank'] │ {'x': 'rank'} ┃
┠─────────────────┼─────────────┼─────────────┼─────────────┼───────────────┨
┃ gb.agg(…) │ x y │ │ x y │ ┃
┃ │ z │ x y │ rank rank │ x ┃
┃ │ 3 1 2 │ a 1 1 │ a 1 1 │ a 1 ┃
┃ │ 6 11 13 │ b 1 1 │ b 1 1 │ b 1 ┃
┃ │ │ c 2 2 │ c 2 2 │ c 2 ┃
┠─────────────────┼─────────────┼─────────────┼─────────────┼───────────────┨
┃ gb.transform(…) │ x y │ x y │ │ ┃
┃ │ a 1 2 │ a 1 1 │ │ ┃
┃ │ b 11 13 │ b 1 1 │ │ ┃
┃ │ c 11 13 │ c 2 2 │ │ ┃
┗━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━┛
Rolling
objeto for rolling window calculations.
<RSr/RDF/RGB> = <Sr/DF/GB>.rolling(win_size) # También: `min_periods=None, center=False`.
<RSr/RDF/RGB> = <RDF/RGB>[column_key/s] # O bien: <RDF/RGB>.column_key
<Sr/DF> = <R>.mean/sum/max() # O bien: <R>.apply/agg(<agg_func/str>)
#Plotly
# $ pip3 install pandas plotly kaleido
import pandas as pd, plotly.express as ex
<Figure> = ex.line(<DF>, x=<col_name>, y=<col_name>) # O bien: ex.línea(x=<list>, y=<list>)
<Figure>.update_layout(margin=dict(t=0, r=0, b=0, l=0), …) # `paper_bgcolor='rgb(0, 0, 0)'`.
<Figure>.write_html/json/image('<path>') # También <Figure>.show().
Muestra un gráfico de líneas con el total de fallecimientos por coronavirus por millón de habitantes, agrupados por continente.
covid = pd.read_csv('https://raw.githubusercontent.com/owid/covid-19-data/8dde8ca49b'
'6e648c17dd420b2726ca0779402651/public/data/owid-covid-data.csv',
usecols=['iso_code', 'date', 'total_deaths', 'population'])
continents = pd.read_csv('https://gto76.github.io/python-cheatsheet/web/continents.csv',
usecols=['Three_Letter_Country_Code', 'Continent_Name'])
df = pd.merge(covid, continents, left_on='iso_code', right_on='Three_Letter_Country_Code')
df = df.groupby(['Continent_Name', 'date']).sum().reset_index()
df['Total Deaths per Million'] = df.total_deaths * 1e6 / df.population
df = df[df.date > '2020-03-14']
df = df.rename({'date': 'Date', 'Continent_Name': 'Continent'}, axis='columns')
ex.line(df, x='Date', y='Total Deaths per Million', color='Continent').show()
Muestra un gráfico de líneas con varios ejes que representa el total de casos de coronavirus y las variaciones en los precios de Bitcoin, el índice Dow Jones y el oro.:
import pandas as pd, plotly.graph_objects as go
def main():
covid, bitcoin, gold, dow = scrape_data()
display_data(wrangle_data(covid, bitcoin, gold, dow))
def scrape_data():
def get_covid_cases():
url = 'https://covid.ourworldindata.org/data/owid-covid-data.csv'
df = pd.read_csv(url, usecols=['location', 'date', 'total_cases'])
return df[df.location == 'World'].set_index('date').total_cases
def get_ticker(symbol):
url = (f'https://query1.finance.yahoo.com/v7/finance/download/{symbol}?'
'period1=1579651200&period2=9999999999&interval=1d&events=history')
df = pd.read_csv(url, usecols=['Date', 'Close'])
return df.set_index('Date').Close
out = get_covid_cases(), get_ticker('BTC-USD'), get_ticker('GC=F'), get_ticker('^DJI')
return map(pd.Series.rename, out, ['Total Cases', 'Bitcoin', 'Gold', 'Dow Jones'])
def wrangle_data(covid, bitcoin, gold, dow):
df = pd.concat([bitcoin, gold, dow], axis=1) # Crea tabla by joining columnas on dates.
df = df.sort_index().interpolate() # Ordena tabla by fecha y interpolates NaN-s.
df = df.loc['2020-02-23':] # Discards filas antes de '2020-02-23'.
df = (df / df.iloc[0]) * 100 # Calcula percentages relative to day 1.
df = df.join(covid) # Añade columna con covid cases.
return df.sort_values(df.index[-1], axis=1) # Ordena columnas by último day's valor.
def display_data(df):
figure = go.Figure()
for col_name in reversed(df.columns):
yaxis = 'y1' if col_name == 'Total Cases' else 'y2'
trace = go.Scatter(x=df.index, y=df[col_name], name=col_name, yaxis=yaxis)
figure.add_trace(trace)
figure.update_layout(
yaxis1=dict(title='Total Cases', rangemode='tozero'),
yaxis2=dict(title='%', rangemode='tozero', overlaying='y', side='right'),
legend=dict(x=1.08),
width=944,
height=423
)
figure.show()
if __name__ == '__main__':
main()
Appendix
Cython
Biblioteca que compila código Python a C.
# $ pip3 install cython
import pyximport; pyximport.install()
import <cython_script>
<cython_script>.main()
Definitions:
- Todas las definiciones
'cdef'son opcionales, pero contribuyen a la aceleración. - El script debe guardarse con la extensión
'pyx'.
cdef <ctype> <var_name> = <el>
cdef <ctype>[n_elements] <var_name> = [<el>, <el>, ...]
cdef <ctype/void> <func_name>(<ctype> <arg_name>): ...
cdef class <class_name>:
cdef public <ctype> <attr_name>
def __init__(self, <ctype> <arg_name>):
self.<attr_name> = <arg_name>
cdef enum <enum_name>: <member_name>, <member_name>, ...
Entornos virtuales
Sistema para instalar bibliotecas directamente en el directorio del proyecto.
$ python3 -m venv <name> # Crea virtual environment in actual directorio.
$ source <name>/bin/activate # Activates venv. On Windows run `<name>\Scripts\activate`.
$ pip3 install <library> # Installs the library en active environment.
$ python3 <path> # Ejecuta the script in active environment. También `./<path>`.
$ deactivate # Deactivates the active virtual environment.
Plantilla básica de script
#!/usr/bin/env python3
#
# Usage: .py
#
from sys import argv, exit
from collections import defaultdict, namedtuple
from dataclasses import make_dataclass
from enum import Enum
import functools as ft, itertools as it, operator as op, re
def main():
pass
###
## UTIL
#
def read_file(filename):
with open(filename, encoding='utf-8') as file:
return file.readlines()
if __name__ == '__main__':
main()
#Índice
- Solo available in the PDF.
- Ctrl+F / ⌘F is usually sufficient.
- Searching
'#<title>'will limit the search al titles.