El Tutorial de Python
El Tutorial de Python
v=zlOaObf5yR0
El tutorial de Python
Python es un lenguaje de programación potente y fácil de aprender. Tiene
estructuras de datos de alto nivel eficientes y un simple pero efectivo sistema de
programación orientado a objetos. La elegante sintaxis de Python y su tipado
dinámico, junto a su naturaleza interpretada lo convierten en un lenguaje ideal
para scripting y desarrollo rápido de aplicaciones en muchas áreas, para la
mayoría de plataformas.
Para una descripción de los objetos estándar y de los módulos, ver La Biblioteca
Estándar de Python. Referencia del Lenguaje Python dónde se ofrece una
definición más formal del lenguaje. Para escribir extensiones en C o C++,
leer Ampliación e incrustación del intérprete de Python y Manual de referencia de
la API en C de Python. Existen diversos libros que cubren Python en detalle.
Este tutorial no pretende ser exhaustivo ni cubrir cada una de las características
del lenguaje, ni siquiera las más utilizadas. En vez de eso, pretende introducir
muchas de las funcionalidades más notables y brindar una idea clara acerca del
estilo y el tipo de lenguaje que es Python. Después de leerlo podrás leer y escribir
módulos y programas en Python, y estarás listo para aprender más acerca de las
diversas librerías y módulos descritos en La Biblioteca Estándar de Python.
Puede escribir un script de shell de Unix o archivos por lotes de Windows para
algunas de estas tareas, pero los scripts de shell son mejores para mover archivos
y cambiar datos de texto, no son adecuados para juegos o aplicaciones GUI.
Podría escribir un programa C / C ++ / Java, pero puede llevar mucho tiempo de
desarrollo obtener incluso un programa de primer borrador. Python es más fácil de
usar, está disponible en los sistemas operativos Windows, macOS y Unix, y lo
ayudará a hacer el trabajo más rápidamente.
Ahora que estás emocionado con Python, querrás verlo en más detalle. Como la
mejor forma de aprender un lenguaje es usarlo, el tutorial te invita a que juegues
con el intérprete de Python a medida que vas leyendo.
Se puede salir del intérprete con estado de salida cero ingresando el carácter de
fin de archivo (Control-D en Unix, Control-Z en Windows). Si eso no funciona,
puedes salir del intérprete escribiendo el comando: quit().
Algunos módulos de Python también son útiles como scripts. Estos pueden
invocarse utilizando python -m module [arg] ..., que ejecuta el archivo
fuente para module como si se hubiera escrito el nombre completo en la línea de
comandos.
Cuando se usa un script, a veces es útil poder ejecutar el script y luego ingresar al
modo interactivo. Esto se puede hacer pasando la -i antes del nombre del script.
Todas las opciones de la línea de comandos se describen en Línea de comandos
y entorno.
2.1.1. Paso de argumentos
Cuando son conocidos por el intérprete, el nombre del script y los argumentos
adicionales se convierten a una lista de cadenas de texto asignada a la
variable argv del módulo sys. Puedes acceder a esta lista haciendo import sys.
La longitud de la lista es al menos uno; cuando no se utiliza ningún script o
argumento, sys.argv[0] es una cadena vacía. Cuando se pasa el nombre del
script con '-' (lo que significa la entrada estándar), sys.argv[0] vale '-'.
Cuando se usa -c comando, sys.argv[0] vale '-c'. Cuando se usa -
m módulo, sys.argv[0] contiene el valor del nombre completo del módulo. Las
opciones encontradas después de -c comand o -m módulo no son consumidas
por el procesador de opciones de Python pero de todas formas se almacenan
en sys.argv para ser manejadas por el comando o módulo.
2.1.2. Modo interactivo
Cuando se leen los comandos desde un terminal, se dice que el intérprete está
en modo interactivo. En este modo, espera el siguiente comando con el prompt
primario, generalmente tres signos de mayor que (>>>); para las líneas de
continuación, aparece el prompt secundario, por defecto tres puntos (...). El
intérprete imprime un mensaje de bienvenida que indica su número de versión y
un aviso de copyright antes de imprimir el primer prompt primario:
$ python3.10
Python 3.10 (default, June 4 2019, 09:25:04)
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more
information.
>>>
>>>
Para declarar una codificación que no sea la predeterminada, se debe agregar una
línea de comentario especial como la primera línea del archivo. La sintaxis es la
siguiente:
#!/usr/bin/env python3
# -*- coding: cp1252 -*-
Notas al pie
You can toggle the display of prompts and output by clicking on >>> in the upper-
right corner of an example box. If you hide the prompts and output for an example,
then you can easily copy and paste the input lines into your interpreter.
Algunos ejemplos:
3.1.1. Números
El intérprete puede utilizarse como una simple calculadora; puedes introducir una
expresión y este escribirá los valores. La sintaxis es sencilla: los
operadores +, -, * y /``funcionan como en la mayoría de los lenguaje
s (por ejemplo, Pascal o C); los paréntesis (``()) pueden ser
usados para agrupar. Por ejemplo:
>>>
>>> 2 + 2
4
>>> 50 - 5*6
20
>>> (50 - 5*6) / 4
5.0
>>> 8 / 5 # division always returns a floating point number
1.6
Los números enteros (ej. 2, 4, 20) tienen tipo int, los que tienen una parte
fraccionaria (por ejemplo 5.0, 1.6) tiene el tipo float. Vamos a ver más acerca
de los tipos numéricos más adelante en el tutorial.
>>>
>>>
>>> 5 ** 2 # 5 squared
25
>>> 2 ** 7 # 2 to the power of 7
128
El signo igual (=) se usa para asignar un valor a una variable. Ningún resultado se
mostrará antes del siguiente prompt interactivo:
>>>
>>> width = 20
>>> height = 5 * 9
>>> width * height
900
>>>
>>>
>>> 4 * 3.75 - 1
14.0
>>>
Esta variable debe ser tratada como de sólo lectura por el usuario. No le asignes
explícitamente un valor; crearás una variable local independiente con el mismo
nombre enmascarando la variable con el comportamiento mágico.
Además de int y float, Python admite otros tipos de números,
como Decimal y Fraction. Python también tiene soporte incorporado
para complex numbers, y usa el sufijo j o J para indicar la parte imaginaria (por
ejemplo, 3+5j).
3.1.2. Cadenas de caracteres
Además de números, Python puede manipular cadenas de texto, las cuales
pueden ser expresadas de distintas formas. Pueden estar encerradas en comillas
simples ('...') o dobles ("...") con el mismo resultado 2. \ puede ser usado
para escapar comillas:
>>>
>>>
>>>
Las cadenas de texto literales pueden contener múltiples líneas. Una forma es
usar triples comillas: """...""" o '''...'''. Los fin de línea son incluidos
automáticamente, pero es posible prevenir esto agregando una \ al final de la
línea. Por ejemplo:
print("""\
Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to
""")
produce la siguiente salida (tened en cuenta que la línea inicial no está incluida):
>>>
>>>
>>>
>>>
>>>
>>>
Los índices quizás sean números negativos, para empezar a contar desde la
derecha:
>>>
Nota que -0 es lo mismo que 0, los índice negativos comienzan desde -1.
>>>
Los índices de las rebanadas tienen valores por defecto útiles; el valor por defecto
para el primer índice es cero, el valor por defecto para el segundo índice es la
longitud de la cadena a rebanar.
>>>
>>> word[:2] # character from the beginning to position 2
(excluded)
'Py'
>>> word[4:] # characters from position 4 (included) to the
end
'on'
>>> word[-2:] # characters from the second-last (included) to
the end
'on'
Nota cómo el inicio siempre se incluye y el final siempre se excluye. Esto asegura
que s[:i] + s[i:] siempre sea igual a s:
>>>
Una forma de recordar cómo funcionan las rebanadas es pensar que los índices
apuntan entre caracteres, con el borde izquierdo del primer carácter numerado 0.
Luego, el punto derecho del último carácter de una cadena de n caracteres tiene
un índice n, por ejemplo
+---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
0 1 2 3 4 5 6
-6 -5 -4 -3 -2 -1
>>>
>>> word[42] # the word only has 6 characters
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
>>>
>>> word[4:42]
'on'
>>> word[42:]
''
>>>
>>>
>>>
>>> s = 'supercalifragilisticexpialidocious'
>>> len(s)
34
Ver también
Cadenas de caracteres — str
Aquí se describen con más detalle las antiguas operaciones para formateo
utilizadas cuando una cadena de texto está a la izquierda del operador %.
3.1.3. Listas
Python tiene varios tipos de datos compuestos, utilizados
para agrupar otros valores. El más versátil es la lista, la cual
puede ser escrita como una lista de valores separados por
coma (ítems) entre corchetes. Las listas pueden contener
ítems de diferentes tipos, pero usualmente los ítems son del
mismo tipo.
>>>
>>>
>>> squares[0] # indexing returns the item
1
>>> squares[-1]
25
>>> squares[-3:] # slicing returns a new list
[9, 16, 25]
>>>
>>> squares[:]
[1, 4, 9, 16, 25]
>>>
>>>
>>>
>>> cubes.append(216) # add the cube of 6
>>> cubes.append(7 ** 3) # and the cube of 7
>>> cubes
[1, 8, 27, 64, 125, 216, 343]
>>>
>>>
>>>
>>>
>>>
>>> i = 256*256
>>> print('The value of i is', i)
The value of i is 65536
>>>
>>> a, b = 0, 1
>>> while a < 1000:
... print(a, end=',')
... a, b = b, a+b
...
0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,
Notas al pie
1
4.1. La sentencia if
Tal vez el tipo más conocido de sentencia sea el if. Por ejemplo:
>>>
4.2. La sentencia for
La sentencia for en Python difiere un poco de lo que uno puede estar
acostumbrado en lenguajes como C o Pascal. En lugar de siempre iterar sobre
una progresión aritmética de números (como en Pascal) o darle al usuario la
posibilidad de definir tanto el paso de la iteración como la condición de fin (como
en C), la sentencia for de Python itera sobre los ítems de cualquier secuencia
(una lista o una cadena de texto), en el orden que aparecen en la secuencia. Por
ejemplo:
>>>
Código que modifica una colección mientras se itera sobre la misma colección
puede ser complejo de hacer bien. Sin embargo, suele ser más directo iterar sobre
una copia de la colección o crear una nueva colección:
>>>
>>>
>>>
>>>
>>> range(10)
range(0, 10)
Decimos que tal objeto es iterable; esto es, que se puede usar en funciones y
construcciones que esperan algo de lo cual obtener ítems sucesivos hasta que se
termine. Hemos visto que la declaración for es una de esas construcciones,
mientras que un ejemplo de función que toma un iterable es la función sum():
>>>
>>> sum(range(4)) # 0 + 1 + 2 + 3
6
Más adelante veremos otras funciones que aceptan iterables cómo argumentos o
retornan iterables. En el capítulo Estructuras de datos, discutiremos en más detalle
sobre la list().
>>>
>>>
4.5. La sentencia pass
La sentencia pass no hace nada. Se puede usar cuando una sentencia es
requerida por la sintaxis pero el programa no requiere ninguna acción. Por
ejemplo:
>>>
>>>
Otro lugar donde se puede usar pass es como una marca de lugar para una
función o un cuerpo condicional cuando estás trabajando en código nuevo, lo cual
te permite pensar a un nivel de abstracción mayor. El pass se ignora
silenciosamente:
>>>
4.6. La sentencia match
Una sentencia match recibe una expresión y compara su valor a patrones
sucesivos que aparecen en uno o más bloques case. Esto es similar a grandes
rasgos con una sentencia switch en C, Java o JavaScript (y muchos otros
lenguajes), pero también es capaz de extraer componentes (elementos de una
secuencia o atributos de un objeto) de un valor y ponerlos en variables.
La forma más simple compara un valor, el «sujeto», con uno o más literales:
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
Si estás usando clases para estructurar tus datos, puedes usar el nombre de la
clase seguida de una lista de argumentos similar a la de un constructor, pero con
la capacidad de capturar atributos en variables:
class Point:
x: int
y: int
def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
Una recomendación para leer patrones es verlos como una forma extendida de lo
que pondrías en el lado izquierdo de una asignación, para así entender cuales
variables tomarían que valores. Sólo los nombres que aparecen por si solos
(cómo var arriba) son asignados por una sentencia match. Nunca se asigna a los
nombres con puntos (como foo.bar), nombres de atributos (los x= e y= arriba) o
nombres de clases (reconocidos por los «(…)» junto a ellos, como Point arriba).
match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")
Mapping patterns: {"bandwidth": b, "latency": l} captures
the "bandwidth" and "latency" values from a dictionary. Unlike
sequence patterns, extra keys are ignored. An unpacking like **rest is
also supported. (But **_ would be redundant, so it is not allowed.)
Para una explicación más detallada y más ejemplos, puede leerse PEP 636 que
está escrita en un formato de tutorial.
4.7. Definiendo funciones
Podemos crear una función que escriba la serie de Fibonacci hasta un límite
determinado:
>>>
>>> def fib(n): # write Fibonacci series up to n
... """Print a Fibonacci series up to n."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Now call the function we just defined:
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
La ejecución de una función introduce una nueva tabla de símbolos usada para las
variables locales de la función. Más precisamente, todas las asignaciones de
variables en la función almacenan el valor en la tabla de símbolos local; así mismo
la referencia a variables primero mira la tabla de símbolos local, luego en la tabla
de símbolos local de las funciones externas, luego la tabla de símbolos global, y
finalmente la tabla de nombres predefinidos. Así, a variables globales y a variables
de funciones que engloban a una función no se les puede asignar directamente un
valor dentro de una función (a menos que se las nombre en la sentencia global,
o mediante la sentencia nonlocal para variables de funciones que engloban la
función local), aunque si pueden ser referenciadas.
>>>
>>> fib
<function fib at 10042ed0>
>>> f = fib
>>> f(100)
0 1 1 2 3 5 8 13 21 34 55 89
>>>
>>> fib(0)
>>> print(fib(0))
None
Es simple escribir una función que retorne una lista con los números de la serie de
Fibonacci en lugar de imprimirlos:
>>>
i = 5
def f(arg=i):
print(arg)
i = 6
f()
…imprimirá `5.
Advertencia importante: El valor por omisión es evaluado solo una vez. Existe
una diferencia cuando el valor por omisión es un objeto mutable como una lista,
diccionario, o instancia de la mayoría de las clases. Por ejemplo, la siguiente
función acumula los argumentos que se le pasan en subsiguientes llamadas:
print(f(1))
print(f(2))
print(f(3))
Imprimirá
[1]
[1, 2]
[1, 2, 3]
Si no se quiere que el valor por omisión sea compartido entre subsiguientes
llamadas, se pueden escribir la función así:
parrot(1000) # 1
positional argument
parrot(voltage=1000) # 1
keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2
keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2
keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3
positional arguments
parrot('a thousand', state='pushing up the daisies') # 1
positional, 1 keyword
En una llamada a una función, los argumentos nombrados deben seguir a los
argumentos posicionales. Cada uno de los argumentos nombrados pasados
deben coincidir con un argumento aceptado por la función (por ejemplo, actor no
es un argumento válido para la función parrot), y el orden de los mismos no es
importante. Esto también se aplica a los argumentos obligatorios (por
ejemplo, parrot(voltage=1000) también es válido). Ningún argumento puede
recibir más de un valor al mismo tiempo. Aquí hay un ejemplo que falla debido a
esta restricción:
>>>
Se debe notar que el orden en el cual los argumentos nombrados son impresos
está garantizado para coincidir con el orden en el cual fueron provistos en la
llamada a la función.
4.8.3. Parámetros especiales
Por defecto, los argumentos pueden enviarse a una función Python o bien por
posición o explícitamente por clave. Para legibilidad y rendimiento tiene sentido
restringir como se pueden enviar los argumentos, así un desarrollador necesitará
mirar solamente la definición de la función para determinar si los argumentos se
deben enviar por posición, por posición o clave, o por clave.
4.8.3.4. Ejemplos de Funciones
Considere el siguiente ejemplo de definiciones de funciones prestando especial
atención a los marcadores / y *:
>>>
>>>
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
>>>
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got some positional-only arguments
passed as keyword arguments: 'arg'
>>>
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was
given
>>> kwd_only_arg(arg=3)
3
>>>
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3
were given
>>>
4.8.3.5. Resumen
El caso de uso determinará qué parámetros utilizar en una definición de función:
A modo de guía:
>>>
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
>>>
Del mismo modo, los diccionarios pueden entregar argumentos nombrados con el
operador **:
>>>
>>>
El ejemplo anterior muestra el uso de una expresión lambda para retornar una
función. Otro uso es para pasar pequeñas funciones como argumentos
>>>
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
La primera línea debe ser siempre un resumen corto y conciso del propósito del
objeto. Para ser breve, no se debe mencionar explícitamente el nombre o tipo del
objeto, ya que estos están disponibles de otros modos (excepto si el nombre es un
verbo que describe el funcionamiento de la función). Esta línea debe empezar con
una letra mayúscula y terminar con un punto.
Si hay más líneas en la cadena de texto de documentación, la segunda línea debe
estar en blanco, separando visualmente el resumen del resto de la descripción.
Las líneas siguientes deben ser uno o más párrafos describiendo las
convenciones para llamar al objeto, efectos secundarios, etc.
>>>
4.8.8. Anotación de funciones
Las anotaciones de funciones son información completamente opcional sobre los
tipos usadas en funciones definidas por el usuario (ver PEP 484 para más
información).
>>>
Para Python, PEP 8 se erigió como la guía de estilo a la que más proyectos
adhirieron; promueve un estilo de codificación fácil de leer y visualmente
agradable. Todos los desarrolladores Python deben leerlo en algún momento; aquí
están extraídos los puntos más importantes:
Esto ayuda a los usuarios con pantallas pequeñas y hace posible tener
varios archivos de código abiertos, uno al lado del otro, en pantallas
grandes.
Usar docstrings.
Notas al pie
Para Python, PEP 8 se erigió como la guía de estilo a la que más proyectos
adhirieron; promueve un estilo de codificación fácil de leer y visualmente
agradable. Todos los desarrolladores Python deben leerlo en algún momento; aquí
están extraídos los puntos más importantes:
Esto ayuda a los usuarios con pantallas pequeñas y hace posible tener
varios archivos de código abiertos, uno al lado del otro, en pantallas
grandes.
Usar docstrings.
Notas al pie
5. Estructuras de datos
Este capítulo describe algunas cosas que ya has aprendido en más detalle y
agrega algunas cosas nuevas también.
list.append(x)
Retorna el índice basado en cero del primer elemento cuyo valor sea igual
a x. Lanza una excepción ValueError si no existe tal elemento.
Ordena los elementos de la lista in situ (los argumentos pueden ser usados
para personalizar el orden de la lista, ver sorted() para su explicación).
list.reverse()
>>>
Otra cosa que puedes observar es que no todos los datos se pueden ordenar o
comparar. Por ejemplo, [None, 'hello', 10] no se puede ordenar ya que los
enteros no se pueden comparar con strings y None no se puede comparar con los
otros tipos. También hay algunos tipos que no tienen una relación de orden
definida. Por ejemplo, 3+4j < 5+7j no es una comparación válida.
>>>
>>>
5.1.3. Comprensión de listas
Las comprensiones de listas ofrecen una manera concisa de crear listas. Sus usos
comunes son para hacer nuevas listas donde cada elemento es el resultado de
algunas operaciones aplicadas a cada miembro de otra secuencia o iterable, o
para crear un segmento de la secuencia de esos elementos para satisfacer una
condición determinada.
Por ejemplo, asumamos que queremos crear una lista de cuadrados, como:
>>>
>>> squares = []
>>> for x in range(10):
... squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Nota que esto crea (o sobreescribe) una variable llamada x que sigue existiendo
luego de que el bucle haya terminado. Podemos calcular la lista de cuadrados sin
ningún efecto secundario haciendo:
>>>
y es equivalente a:
>>>
>>> combs = []
>>> for x in [1,2,3]:
... for y in [3,1,4]:
... if x != y:
... combs.append((x, y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
>>>
>>>
Considerá el siguiente ejemplo de una matriz de 3x4 implementada como una lista
de tres listas de largo 4:
>>>
>>> matrix = [
... [1, 2, 3, 4],
... [5, 6, 7, 8],
... [9, 10, 11, 12],
... ]
>>>
>>>
>>> transposed = []
>>> for i in range(4):
... transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
>>>
>>> transposed = []
>>> for i in range(4):
... # the following 3 lines implement the nested listcomp
... transposed_row = []
... for row in matrix:
... transposed_row.append(row[i])
... transposed.append(transposed_row)
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
>>>
>>> list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
5.2. La instrucción del
Hay una manera de quitar un ítem de una lista dado su índice en lugar de su valor:
la instrucción del. Esta es diferente del método pop(), el cual retorna un valor. La
instrucción del también puede usarse para quitar secciones de una lista o vaciar
la lista completa (lo que hacíamos antes asignando una lista vacía a la sección).
Por ejemplo:
>>>
>>>
>>> del a
Hacer referencia al nombre a de aquí en más es un error (al menos hasta que se
le asigne otro valor). Veremos otros usos para del más adelante.
5.3. Tuplas y secuencias
Vimos que las listas y cadenas tienen propiedades en común, como el indizado y
las operaciones de seccionado. Estas son dos ejemplos de datos de
tipo secuencia (ver Tipos secuencia — list, tuple, range). Como Python es un
lenguaje en evolución, otros datos de tipo secuencia pueden agregarse. Existe
otro dato de tipo secuencia estándar: la tupla.
Una tupla consiste de un número de valores separados por comas, por ejemplo:
>>>
Como puedes ver, en la salida las tuplas siempre se encierran entre paréntesis,
para que las tuplas anidadas puedan interpretarse correctamente; pueden
ingresarse con o sin paréntesis, aunque a menudo los paréntesis son necesarios
de todas formas (si la tupla es parte de una expresión más grande). No es posible
asignar a los ítems individuales de una tupla, pero sin embargo sí se puede crear
tuplas que contengan objetos mutables, como las listas.
A pesar de que las tuplas puedan parecerse a las listas, frecuentemente se utilizan
en distintas situaciones y para distintos propósitos. Las tuplas son immutable y
normalmente contienen una secuencia heterogénea de elementos que son
accedidos al desempaquetar (ver más adelante en esta sección) o indizar (o
incluso acceder por atributo en el caso de las namedtuples). Las listas
son mutable, y sus elementos son normalmente homogéneos y se acceden
iterando a la lista.
>>>
>>> empty = ()
>>> singleton = 'hello', # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)
>>>
>>> x, y, z = t
5.4. Conjuntos
Python también incluye un tipo de dato para conjuntos. Un conjunto es una
colección no ordenada y sin elementos repetidos. Los usos básicos de éstos
incluyen verificación de pertenencia y eliminación de entradas duplicadas. Los
conjuntos también soportan operaciones matemáticas como la unión, intersección,
diferencia, y diferencia simétrica.
Las llaves o la función set() pueden usarse para crear conjuntos. Notá que para
crear un conjunto vacío tenés que usar set(), no {}; esto último crea un
diccionario vacío, una estructura de datos que discutiremos en la sección
siguiente.
>>>
>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange',
'banana'}
>>> print(basket) # show that duplicates
have been removed
{'orange', 'banana', 'pear', 'apple'}
>>> 'orange' in basket # fast membership testing
True
>>> 'crabgrass' in basket
False
>>>
5.5. Diccionarios
Otro tipo de dato útil incluido en Python es el diccionario (ver Tipos Mapa — dict).
Los diccionarios se encuentran a veces en otros lenguajes como «memorias
asociativas» o «arreglos asociativos». A diferencia de las secuencias, que se
indexan mediante un rango numérico, los diccionarios se indexan con claves, que
pueden ser cualquier tipo inmutable; las cadenas y números siempre pueden ser
claves. Las tuplas pueden usarse como claves si solamente contienen cadenas,
números o tuplas; si una tupla contiene cualquier objeto mutable directa o
indirectamente, no puede usarse como clave. No podés usar listas como claves,
ya que las listas pueden modificarse usando asignación por índice, asignación por
sección, o métodos como append() y extend().
Las operaciones principales sobre un diccionario son guardar un valor con una
clave y extraer ese valor dada la clave. También es posible borrar un par
clave:valor con del. Si usás una clave que ya está en uso para guardar un valor,
el valor que estaba asociado con esa clave se pierde. Es un error extraer un valor
usando una clave no existente.
>>>
>>>
>>>
Cuando las claves son cadenas simples, a veces resulta más fácil especificar los
pares usando argumentos por palabra clave:
>>>
5.6. Técnicas de iteración
Cuando iteramos sobre diccionarios, se pueden obtener al mismo tiempo la clave
y su valor correspondiente usando el método items().
>>>
Cuando se itera sobre una secuencia, se puede obtener el índice de posición junto
a su valor correspondiente usando la función enumerate().
>>>
Para iterar sobre dos o más secuencias al mismo tiempo, los valores pueden
emparejarse con la función zip().
>>>
>>>
>>>
>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange',
'banana']
>>> for i in sorted(basket):
... print(i)
...
apple
apple
banana
orange
orange
pear
>>>
A veces uno intenta cambiar una lista mientras la está iterando; sin embargo, a
menudo es más simple y seguro crear una nueva lista:
>>>
>>>
Notas al pie
6. Módulos
Si sales del intérprete de Python y vuelves a entrar, las definiciones que habías
hecho (funciones y variables) se pierden. Por lo tanto, si quieres escribir un
programa más o menos largo, es mejor que utilices un editor de texto para
preparar la entrada para el intérprete y ejecutarlo con ese archivo como entrada.
Esto se conoce como crear un script. A medida que tu programa crezca, quizás
quieras separarlo en varios archivos para que el mantenimiento sea más sencillo.
Quizás también quieras usar una función útil que has escrito en distintos
programas sin copiar su definición en cada programa.
Para soportar esto, Python tiene una manera de poner definiciones en un archivo y
usarlos en un script o en una instancia del intérprete. Este tipo de ficheros se
llama módulo; las definiciones de un módulo pueden ser importadas a otros
módulos o al módulo principal (la colección de variables a las que tienes acceso
en un script ejecutado en el nivel superior y en el modo calculadora).
>>>
>>> fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'
>>>
Cada módulo tiene su propio espacio de nombres, el cual es usado como espacio
de nombres global para todas las funciones definidas en el módulo. Por lo tanto, el
autor de un módulo puede usar variables globales en el módulo sin preocuparse
acerca de conflictos con una variable global del usuario. Por otro lado, si sabes lo
que estás haciendo puedes acceder a las variables globales de un módulo con la
misma notación usada para referirte a sus
funciones, nombremodulo.nombreitem.
>>>
>>> from fibo import fib, fib2
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
Hay incluso una variante para importar todos los nombres que un módulo define:
>>>
Esto importa todos los nombres excepto los que inician con un guión bajo ( _). La
mayoría de las veces los programadores de Python no usan esto ya que introduce
en el intérprete un conjunto de nombres desconocido, posiblemente escondiendo
algunas de las definiciones previas.
>>>
>>>
Por razones de eficiencia, cada módulo es importado solo una vez por sesión del
intérprete. Por lo tanto, si cambias tus módulos, debes reiniciar el interprete – ó, si
es un solo módulo que quieres probar de forma interactiva,
usa importlib.reload(), por
ejemplo: import importlib; importlib.reload(modulename).
6.1.1. Ejecutando módulos como scripts
Cuando ejecutes un módulo de Python con
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
puedes hacer que el archivo sea utilizable tanto como script, como módulo
importable, porque el código que analiza la línea de órdenes sólo se ejecuta si el
módulo es ejecutado como archivo principal:
$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34
>>>
>>>
>>>
6.3. La función dir()
La función integrada dir() se usa para encontrar qué nombres define un módulo.
Retorna una lista ordenada de cadenas:
>>>
>>>
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
Note que lista todos los tipos de nombres: variables, módulos, funciones, etc.
dir() no lista los nombres de las funciones y variables integradas. Si quieres una
lista de esos, están definidos en el módulo estándar builtins:
>>>
6.4. Paquetes
Los Paquetes son una forma de estructurar el espacio de nombres de módulos de
Python usando «nombres de módulo con puntos». Por ejemplo, el nombre del
módulo A.B designa un submódulo B en un paquete llamado A. Así como el uso
de módulos salva a los autores de diferentes módulos de tener que preocuparse
por los nombres de las variables globales de cada uno, el uso de nombres de
módulo con puntos salva a los autores de paquetes con múltiples módulos, como
NumPy o Pillow de preocupaciones por los nombres de los módulos de cada uno.
Supongamos que quieres designar una colección de módulos (un «paquete») para
el manejo uniforme de archivos y datos de sonidos. Hay diferentes formatos de
archivos de sonido (normalmente reconocidos por su extensión, por
ejemplo: .wav, .aiff, .au), por lo que tienes que crear y mantener una colección
siempre creciente de módulos para la conversión entre los distintos formatos de
archivos. Hay muchas operaciones diferentes que quizás quieras ejecutar en los
datos de sonido (como mezclarlos, añadir eco, aplicar una función ecualizadora,
crear un efecto estéreo artificial), por lo que además estarás escribiendo una lista
sin fin de módulos para realizar estas operaciones. Aquí hay una posible
estructura para tu paquete (expresados en términos de un sistema jerárquico de
archivos):
Los usuarios del paquete pueden importar módulos individuales del mismo, por
ejemplo:
import sound.effects.echo
La única solución es que el autor del paquete provea un índice explícito del
paquete. La declaración import usa la siguiente convención: si el código
del __init__.py de un paquete define una lista llamada __all__, se toma como
la lista de los nombres de módulos que deberían ser importados cuando se
hace from package import *. Es tarea del autor del paquete mantener
actualizada esta lista cuando se libera una nueva versión del paquete. Los autores
de paquetes podrían decidir no soportarlo, si no ven un uso para importar * en sus
paquetes. Por ejemplo, el archivo sound/effects/__init__.py podría contener
el siguiente código:
Si no se define __all__, la
declaración from sound.effects import * no importa todos los submódulos
del paquete sound.effects al espacio de nombres actual; sólo se asegura que
se haya importado el paquete sound.effects (posiblemente ejecutando algún
código de inicialización que haya en __init__.py) y luego importa aquellos
nombres que estén definidos en el paquete. Esto incluye cualquier nombre
definido (y submódulos explícitamente cargados) por __init__.py. También
incluye cualquier submódulo del paquete que pudiera haber sido explícitamente
cargado por declaraciones import previas. Considere este código:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
A pesar de que ciertos módulos están diseñados para exportar solo nombres que
siguen ciertos patrones cuando uses import *, también se considera una mala
práctica en código de producción.
Note que los imports relativos se basan en el nombre del módulo actual. Ya que el
nombre del módulo principal es siempre "__main__", los módulos pensados para
usarse como módulo principal de una aplicación Python siempre deberían
usar import absolutos.
Notas al pie
7. Entrada y salida
Hay diferentes métodos de presentar la salida de un programa; los datos pueden
ser impresos de una forma legible por humanos, o escritos a un archivo para uso
futuro. Este capítulo discutirá algunas de las posibilidades.
7.1. Formateo elegante de la salida
Hasta ahora encontramos dos maneras de escribir valores: declaraciones de
expresión y la función print(). (Una tercera manera es usando el
método write() de los objetos tipo archivo; el archivo de salida estándar puede
referenciarse como sys.stdout. Mirá la Referencia de la Biblioteca para más
información sobre esto).
>>>
>>>
Por último, puede realizar todo el control de cadenas usted mismo mediante
operaciones de concatenación y segmentación de cadenas para crear
cualquier diseño que se pueda imaginar. El tipo de cadena tiene algunos
métodos que realizan operaciones útiles para rellenar cadenas a un ancho
de columna determinado.
Cuando no necesita una salida elegante, pero solo desea una visualización rápida
de algunas variables con fines de depuración, puede convertir cualquier valor en
una cadena con las funciones repr() o str().
Algunos ejemplos:
>>>
>>>
Pasar un entero después de ':' hará que ese campo sea un número mínimo de
caracteres de ancho. Esto es útil para hacer que las columnas se alineen.
>>>
>>>
>>>
Las llaves y caracteres dentro de las mismas (llamados campos de formato) son
reemplazadas con los objetos pasados en el método str.format(). Un número
en las llaves se refiere a la posición del objeto pasado en el
método str.format().
>>>
>>>
>>>
other='Georg'))
The story of Bill, Manfred, and Georg.
>>>
Esto se podría hacer, también, pasando la tabla como argumentos nombrados con
la notación “**”.
>>>
>>>
>>>
(Resaltar que el espacio existente entre cada columna es añadido debido a como
funciona print(): siempre añade espacios entre sus argumentos.)
>>>
>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'
>>>
>>>
Normalmente, los ficheros se abren en modo texto, significa que lees y escribes
caracteres desde y hacia el fichero, el cual se codifica con una codificación
específica. Si no se especifica la codificación el valor por defecto depende de la
plataforma (ver open()). 'b' agregado al modo abre el fichero en modo binario: y
los datos se leerán y escribirán en forma de objetos de bytes. Este modo debería
usarse en todos los ficheros que no contienen texto.
Cuando se lee en modo texto, por defecto se convierten los fines de lineas que
son específicos a las plataformas (\n en Unix, \r\n en Windows) a solamente \n.
Cuando se escribe en modo texto, por defecto se convierten los \n a los finales de
linea específicos de la plataforma. Este cambio automático está bien para archivos
de texto, pero corrompería datos binarios como los de archivos JPEG o EXE.
Asegurate de usar modo binario cuando leas y escribas tales archivos.
>>>
>>> # We can check that the file has been automatically closed.
>>> f.closed
True
Advertencia
>>>
>>> f.close()
>>> f.read()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.
>>>
>>> f.read()
'This is the entire file.\n'
>>> f.read()
''
f.readline() lee una sola linea del archivo; el carácter de fin de linea ( \n) se
deja al final de la cadena, y sólo se omite en la última linea del archivo si el mismo
no termina en un fin de linea. Esto hace que el valor de retorno no sea ambiguo;
si f.readline() retorna una cadena vacía, es que se alcanzó el fin del archivo,
mientras que una linea en blanco es representada por '\n', una cadena
conteniendo sólo un único fin de linea.
>>>
>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''
Para leer líneas de un archivo, podés iterar sobre el objeto archivo. Esto es
eficiente en memoria, rápido, y conduce a un código más simple:
>>>
>>> for line in f:
... print(line, end='')
...
This is the first line of the file.
Second line of the file
Si querés leer todas las líneas de un archivo en una lista también podés
usar list(f) o f.readlines().
>>>
Otros tipos de objetos necesitan ser convertidos – tanto a una cadena (en modo
texto) o a un objeto de bytes (en modo binario) – antes de escribirlos:
>>>
>>>
Nota
>>>
json.dump(x, f)
x = json.load(f)
Ver también
pickle - El módulo pickle
8. Errores y excepciones
Hasta ahora los mensajes de error apenas habían sido mencionados, pero si has
probado los ejemplos anteriores probablemente hayas visto algunos. Hay (al
menos) dos tipos diferentes de errores: errores de sintaxis y excepciones.
8.1. Errores de sintaxis
Los errores de sintaxis, también conocidos como errores de interpretación, son
quizás el tipo de queja más común que tenés cuando todavía estás aprendiendo
Python:
>>>
8.2. Excepciones
Incluso si una declaración o expresión es sintácticamente correcta, puede generar
un error cuando se intenta ejecutar. Los errores detectados durante la ejecución
se llaman excepciones, y no son incondicionalmente fatales: pronto aprenderás a
gestionarlos en programas Python. Sin embargo, la mayoría de las excepciones
no son gestionadas por el código, y resultan en mensajes de error como los
mostrados aquí:
>>>
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
La última línea de los mensajes de error indica qué ha sucedido. Hay excepciones
de diferentes tipos, y el tipo se imprime como parte del mensaje: los tipos en el
ejemplo son: ZeroDivisionError, NameError y TypeError. La cadena
mostrada como tipo de la excepción es el nombre de la excepción predefinida que
ha ocurrido. Esto es válido para todas las excepciones predefinidas del intérprete,
pero no tiene por que ser así para excepciones definidas por el usuario (aunque es
una convención útil). Los nombres de las excepciones estándar son identificadores
incorporados al intérprete (no son palabras clave reservadas).
8.3. Gestionando excepciones
Es posible escribir programas que gestionen determinadas excepciones. Véase el
siguiente ejemplo, que le pide al usuario una entrada hasta que ingrese un entero
válido, pero permite al usuario interrumpir el programa (usando Control-C o lo que
soporte el sistema operativo); nótese que una interrupción generada por el usuario
es señalizada generando la excepción KeyboardInterrupt.
>>>
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except BaseException as err:
print(f"Unexpected {err=}, {type(err)=}")
raise
>>>
>>> try:
... raise Exception('spam', 'eggs')
... except Exception as inst:
... print(type(inst)) # the exception instance
... print(inst.args) # arguments stored in .args
... print(inst) # __str__ allows args to be printed
directly,
... # but may be overridden in
exception subclasses
... x, y = inst.args # unpack args
... print('x =', x)
... print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
Si una excepción tiene argumentos, estos se imprimen como en la parte final (el
“detalle”) del mensaje para las excepciones no gestionadas (“Unhandled
exception”).
>>>
8.4. Lanzando excepciones
La declaración raise permite al programador forzar a que ocurra una excepción
específica. Por ejemplo:
>>>
>>>
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: HiThere
8.5. Encadenamiento de excepciones
La declaración raise permite una palabra clave opcional from que habilita el
encadenamiento de excepciones. Por ejemplo:
Esto puede resultar útil cuando está transformando excepciones. Por ejemplo:
>>>
>>>
>>> try:
... open('database.sqlite')
... except OSError:
... raise RuntimeError from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError
Exception classes can be defined which do anything any other class can do, but
are usually kept simple, often only offering a number of attributes that allow
information about the error to be extracted by handlers for the exception.
Muchos módulos estándar definen sus propias excepciones para reportar errores
que pueden ocurrir en funciones propias. Se puede encontrar más información
sobre clases en el capítulo Clases.
>>>
>>> try:
... raise KeyboardInterrupt
... finally:
... print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
Si una cláusula finally está presente, el bloque finally se ejecutará al final
antes de que todo el bloque try se complete. La cláusula finally se ejecuta
independientemente de que la cláusula try produzca o no una excepción. Los
siguientes puntos explican casos más complejos en los que se produce una
excepción:
Por ejemplo:
>>>
>>>
El problema con este código es que deja el archivo abierto por un periodo de
tiempo indeterminado luego de que esta parte termine de ejecutarse. Esto no es
un problema en scripts simples, pero puede ser un problema en aplicaciones más
grandes. La declaración with permite que los objetos como archivos sean usados
de una forma que asegure que siempre se los libera rápido y en forma correcta.:
with open("myfile.txt") as f:
for line in f:
print(line, end="")
(Sin haber una terminología universalmente aceptada sobre clases, haré uso
ocasional de términos de Smalltalk y C++. Usaría términos de Modula-3, ya que su
semántica orientada a objetos es más cercana a Python que C++, pero no espero
que muchos lectores hayan escuchado hablar de él.)
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
scope_test()
print("In global scope:", spam)
class ClassName:
<statement-1>
.
.
.
<statement-N>
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
x = MyClass()
def __init__(self):
self.data = []
>>>
9.3.3. Objetos instancia
Ahora, ¿Qué podemos hacer con los objetos instancia? La única operación que es
entendida por los objetos instancia es la referencia de atributos. Hay dos tipos de
nombres de atributos válidos, atributos de datos y métodos.
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
9.3.4. Objetos método
Generalmente, un método es llamado luego de ser vinculado:
x.f()
xf = x.f
while True:
print(xf())
De hecho, tal vez hayas adivinado la respuesta: lo que tienen de especial los
métodos es que el objeto es pasado como el primer argumento de la función. En
nuestro ejemplo, la llamada x.f() es exactamente equivalente a MiClase.f(x). En
general, llamar a un método con una lista de n argumentos es equivalente a llamar
a la función correspondiente con una lista de argumentos que es creada
insertando el objeto del método antes del primer argumento.
class Dog:
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
Como se vio en Unas palabras sobre nombres y objetos, los datos compartidos
pueden tener efectos inesperados que involucren objetos mutable como ser listas
y diccionarios. Por ejemplo, la lista trucos en el siguiente código no debería ser
usada como variable de clase porque una sola lista sería compartida por todos las
instancias de Perro:
class Dog:
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
class Dog:
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
9.4. Algunas observaciones
Si el mismo nombre de atributo aparece tanto en la instancia como en la clase, la
búsqueda del atributo prioriza la instancia:
>>>
>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east
A los atributos de datos los pueden hacer referencia tanto los métodos como los
usuarios («clientes») ordinarios de un objeto. En otras palabras, las clases no se
usan para implementar tipos de datos abstractos puros. De hecho, en Python no
hay nada que haga cumplir el ocultar datos; todo se basa en convención. (Por otro
lado, la implementación de Python, escrita en C, puede ocultar por completo
detalles de implementación y el control de acceso a un objeto si es necesario; esto
se puede usar en extensiones a Python escritas en C.)
Los clientes deben usar los atributos de datos con cuidado; éstos pueden romper
invariantes que mantienen los métodos si pisan los atributos de datos. Observá
que los clientes pueden añadir sus propios atributos de datos a una instancia sin
afectar la validez de sus métodos, siempre y cuando se eviten conflictos de
nombres; de nuevo, una convención de nombres puede ahorrar un montón de
dolores de cabeza.
No hay un atajo para hacer referencia a atributos de datos (¡u otros métodos!)
desde dentro de un método. A mi parecer, esto en realidad aumenta la legibilidad
de los métodos: no existe posibilidad alguna de confundir variables locales con
variables de instancia cuando repasamos un método.
class C:
f = f1
def g(self):
return 'hello world'
h = g
class Bag:
def __init__(self):
self.data = []
9.5. Herencia
Por supuesto, una característica del lenguaje no sería digna del nombre «clase» si
no soportara herencia. La sintaxis para una definición de clase derivada se ve así:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
class DerivedClassName(modname.BaseClassName):
Las clases derivadas pueden redefinir métodos de su clase base. Como los
métodos no tienen privilegios especiales cuando llaman a otros métodos del
mismo objeto, un método de la clase base que llame a otro método definido en la
misma clase base puede terminar llamando a un método de la clase derivada que
lo haya redefinido. (Para los programadores de C++: en Python todos los métodos
son en efecto virtuales.)
Para la mayoría de los propósitos, en los casos más simples, podés pensar en la
búsqueda de los atributos heredados de clases padres como primero en
profundidad, de izquierda a derecha, sin repetir la misma clase cuando está dos
veces en la jerarquía. Por lo tanto, si un atributo no se encuentra en ClaseDerivada,
se busca en Base1, luego (recursivamente) en las clases base de Base1, y sólo si
no se encuentra allí se lo busca en Base2, y así sucesivamente.
Ya que hay un caso de uso válido para los identificadores privados de clase (a
saber: colisión de nombres con nombres definidos en las subclases), hay un
soporte limitado para este mecanismo. Cualquier identificador con la
forma __spam (al menos dos guiones bajos al principio, como mucho un guión bajo
al final) es textualmente reemplazado por _nombredeclase__spam,
donde nombredeclase es el nombre de clase actual al que se le sacan guiones bajos
del comienzo (si los tuviera). Se modifica el nombre del identificador sin importar
su posición sintáctica, siempre y cuando ocurra dentro de la definición de una
clase.
La modificación de nombres es útil para dejar que las subclases sobreescriban los
métodos sin romper las llamadas a los métodos desde la misma clase. Por
ejemplo:
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
class MappingSubclass(Mapping):
Hay que aclarar que las reglas de modificación de nombres están diseñadas
principalmente para evitar accidentes; es posible acceder o modificar una variable
que es considerada como privada. Esto hasta puede resultar útil en circunstancias
especiales, tales como en el depurador.
9.7. Cambalache
A veces es útil tener un tipo de datos similar al «registro» de Pascal o la
«estructura» de C, que sirva para juntar algunos pocos ítems con nombre. Una
definición de clase vacía funcionará perfecto:
class Employee:
pass
Algún código Python que espera un tipo abstracto de datos en particular puede
frecuentemente recibir en cambio una clase que emula los métodos de aquel tipo
de datos. Por ejemplo, si tenés una función que formatea algunos datos a partir de
un objeto archivo, podés definir una clase con métodos read() y readline() que
obtengan los datos de alguna cadena en memoria intermedia, y pasarlo como
argumento.
>>>
>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x10c90e650>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
next(it)
StopIteration
Habiendo visto la mecánica del protocolo de iteración, es fácil agregar
comportamiento de iterador a tus clases. Definí un método __iter__() que retorne
un objeto con un método __next__(). Si la clase define __next__(), entonces alcanza
con que __iter__() retorne self:
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
>>>
9.9. Generadores
Generators son una herramienta simple y poderosa para crear iteradores. Están
escritas como funciones regulares pero usan la palabra clave yield siempre que
quieran retornar datos. Cada vez que se llama a next(), el generador se reanuda
donde lo dejó (recuerda todos los valores de datos y qué instrucción se ejecutó por
última vez). Un ejemplo muestra que los generadores pueden ser trivialmente
fáciles de crear:
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>>
Todo lo que puede ser hecho con generadores también puede ser hecho con
iteradores basados en clases, como se describe en la sección anterior. Lo que
hace que los generadores sean tan compactos es que los
métodos __iter__() y __next__() son creados automáticamente.
9.10. Expresiones generadoras
Algunos generadores simples pueden ser escritos de manera concisa como
expresiones usando una sintaxis similar a las comprensiones de listas pero con
paréntesis en lugar de corchetes. Estas expresiones están hechas para
situaciones donde el generador es utilizado de inmediato por la función que lo
encierra. Las expresiones generadoras son más compactas pero menos versátiles
que las definiciones completas de generadores y tienden a ser más amigables con
la memoria que sus comprensiones de listas equivalentes.
Ejemplos:
>>>
Notas al pie
Excepto por una cosa. Los objetos módulo tienen un atributo de sólo lectura
secreto llamado __dict__ que retorna el diccionario usado para implementar
el espacio de nombres del módulo; el nombre __dict__ es un atributo pero
no un nombre global. Obviamente, usar esto viola la abstracción de la
implementación del espacio de nombres, y debería ser restringido a cosas
como depuradores post-mortem.
>>>
>>> import os
>>> os.getcwd() # Return the current working directory
'C:\\Python310'
>>> os.chdir('/server/accesslogs') # Change current working
directory
>>> os.system('mkdir today') # Run the command mkdir in the
system shell
0
>>>
>>> import os
>>> dir(os)
<returns a list of all module functions>
>>> help(os)
<returns an extensive manual page created from the module's
docstrings>
>>>
10.2. Comodines de archivos
El módulo glob provee una función para hacer listas de archivos a partir de
búsquedas con comodines en directorios:
>>>
>>>
import argparse
>>>
>>> sys.stderr.write('Warning, log file not found starting a new
one\n')
Warning, log file not found starting a new one
>>>
>>> import re
>>> re.findall(r'\bf[a-z]*', 'which foot or hand fell fastest')
['foot', 'fell', 'fastest']
>>> re.sub(r'(\b[a-z]+) \1', r'\1', 'cat in the the hat')
'cat in the hat'
Cuando se necesita algo más sencillo solamente, se prefieren los métodos de las
cadenas porque son más fáciles de leer y depurar:
>>>
10.6. Matemática
El módulo math permite el acceso a las funciones de la biblioteca C subyacente
para la matemática de punto flotante:
>>>
>>>
10.7. Acceso a Internet
Hay varios módulos para acceder a Internet y procesar sus protocolos. Dos de los
más simples son urllib.request para traer data de URLs y smtplib para
mandar correos:
>>>
10.8. Fechas y tiempos
El módulo datetime ofrece clases para gestionar fechas y tiempos tanto de
manera simple como compleja. Aunque soporta aritmética sobre fechas y tiempos,
el foco de la implementación es en la extracción eficiente de partes para
gestionarlas o formatear la salida. El módulo también soporta objetos que son
conscientes de la zona horaria.
>>>
10.9. Compresión de datos
Los formatos para archivar y comprimir datos se soportan directamente con los
módulos: zlib, gzip, bz2, lzma, zipfile y tarfile.
>>>
10.10. Medición de rendimiento
Algunos usuarios de Python desarrollan un profundo interés en saber el
rendimiento relativo de las diferentes soluciones al mismo problema. Python
provee una herramienta de medición que responde esas preguntas
inmediatamente.
>>>
10.11. Control de calidad
Una forma para desarrollar software de alta calidad es escribir pruebas para cada
función mientras se la desarrolla, y correr esas pruebas frecuentemente durante el
proceso de desarrollo.
def average(values):
"""Computes the arithmetic mean of a list of numbers.
import doctest
doctest.testmod() # automatically validate the embedded tests
import unittest
class TestStatisticalFunctions(unittest.TestCase):
def test_average(self):
self.assertEqual(average([20, 30, 70]), 40.0)
self.assertEqual(round(average([1, 5, 7]), 1), 4.3)
with self.assertRaises(ZeroDivisionError):
average([])
with self.assertRaises(TypeError):
average(20, 30, 70)
unittest.main() # Calling from the command line invokes all
tests
>>>
>>>
>>>
>>>
11.2. Plantillas
El módulo string incluye una clase versátil Template (plantilla) con una sintaxis
simplificada apta para ser editada por usuarios finales. Esto permite que los
usuarios personalicen sus aplicaciones sin necesidad de modificar la aplicación en
sí.
>>>
>>>
>>>
>>> t = BatchRename(fmt)
>>> date = time.strftime('%d%b%y')
>>> for i, filename in enumerate(photofiles):
... base, ext = os.path.splitext(filename)
... newname = t.substitute(d=date, n=i, f=ext)
... print('{0} --> {1}'.format(filename, newname))
import struct
start = 0
for i in range(3): # show the first 3 file
headers
start += 14
fields = struct.unpack('<IIIHH', data[start:start+16])
crc32, comp_size, uncomp_size, filenamesize, extra_size =
fields
start += 16
filename = data[start:start+filenamesize]
start += filenamesize
extra = data[start:start+extra_size]
print(filename, hex(crc32), comp_size, uncomp_size)
11.4. Multi-hilos
La técnica de multi-hilos (o multi-threading) permite desacoplar tareas que no
tienen dependencia secuencial. Los hilos se pueden usar para mejorar el grado de
reacción de las aplicaciones que aceptan entradas del usuario mientras otras
tareas se ejecutan en segundo plano. Un caso de uso relacionado es ejecutar E/S
en paralelo con cálculos en otro hilo.
El código siguiente muestra cómo el módulo de alto nivel threading puede
ejecutar tareas en segundo plano mientras el programa principal continúa su
ejecución:
class AsyncZip(threading.Thread):
def __init__(self, infile, outfile):
threading.Thread.__init__(self)
self.infile = infile
self.outfile = outfile
def run(self):
f = zipfile.ZipFile(self.outfile, 'w',
zipfile.ZIP_DEFLATED)
f.write(self.infile)
f.close()
print('Finished background zip of:', self.infile)
11.5. Registrando
El módulo logging ofrece un sistema de registros (logs) completo y flexible. En su
forma más simple, los mensajes de registro se envían a un archivo o
a sys.stderr:
import logging
logging.debug('Debugging information')
logging.info('Informational message')
logging.warning('Warning:config file %s not found',
'server.conf')
logging.error('Error occurred')
logging.critical('Critical error -- shutting down')
11.6. Referencias débiles
Python realiza administración de memoria automática (cuenta de referencias para
la mayoría de los objetos, y garbage collection para eliminar ciclos). La memoria
se libera poco después de que la última referencia a la misma haya sido
eliminada.
Este enfoque funciona bien para la mayoría de las aplicaciones pero de vez en
cuando existe la necesidad de controlar objetos sólo mientras estén siendo
utilizados por otra cosa. Desafortunadamente, el sólo hecho de controlarlos crea
una referencia que los convierte en permanentes. El módulo weakref provee
herramientas para controlar objetos sin crear una referencia. Cuando el objeto no
se necesita mas, es removido automáticamente de una tabla de referencias
débiles y se dispara una callback para objetos weakref. Comúnmente las
aplicaciones incluyen cacheo de objetos que son costosos de crear:
>>>
>>> import weakref, gc
>>> class A:
... def __init__(self, value):
... self.value = value
... def __repr__(self):
... return str(self.value)
...
>>> a = A(10) # create a reference
>>> d = weakref.WeakValueDictionary()
>>> d['primary'] = a # does not create a reference
>>> d['primary'] # fetch the object if it is
still alive
10
>>> del a # remove the one reference
>>> gc.collect() # run garbage collection right
away
0
>>> d['primary'] # entry was automatically
removed
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
d['primary'] # entry was automatically
removed
File "C:/python310/lib/weakref.py", line 46, in __getitem__
o = self.data[key]()
KeyError: 'primary'
>>>
>>>
>>>
>>>
>>>
>>>
>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857')
Esto significa que tal vez no sea posible para una instalación de Python cumplir los
requerimientos de todas las aplicaciones. Si la aplicación A necesita la versión 1.0
de un módulo particular y la aplicación B necesita la versión 2.0, entonces los
requerimientos entran en conflicto e instalar la versión 1.0 o 2.0 dejará una de las
aplicaciones sin funcionar.
Para crear un entorno virtual, decide en que carpeta quieres crearlo y ejecuta el
módulo venv como script con la ruta a la carpeta:
Una ruta común para el directorio de un entorno virtual es .venv. Ese nombre
mantiene el directorio típicamente escondido en la consola y fuera de vista
mientras le da un nombre que explica cuál es el motivo de su existencia. También
permite que no colisione con los ficheros de definición de variables de
entorno .env que algunas herramientas soportan.
En Windows, ejecuta:
tutorial-env\Scripts\activate.bat
source tutorial-env/bin/activate
(Este script está escrito para la consola bash. Si usas las consolas csh or fish,
hay scripts alternativos activate.csh y activate.fish que deberá usar en su
lugar.)
Activar el entorno virtual cambiará el prompt de tu consola para mostrar que
entorno virtual está usando, y modificará el entorno para que al
ejecutar python sea con esa versión e instalación en particular. Por ejemplo:
$ source ~/envs/tutorial-env/bin/activate
(tutorial-env) $ python
Python 3.5.1 (default, May 6 2016, 10:59:36)
...
>>> import sys
>>> sys.path
['', '/usr/local/lib/python35.zip', ...,
'~/envs/tutorial-env/lib/python3.5/site-packages']
>>>
Este tutorial forma parte del conjunto de documentación de Python. Algunos otros
documentos que encontrarás en este conjunto son:
Notas al pie
1
La tienda de queso, Cheese Shop, es un chiste de Monty Python: un cliente
entra a una tienda de queso pero para cualquier queso que pide, el
vendedor le dice que no lo tienen.
0.125
0.001
…tiene el valor 0/2 + 0/4 + 1/8. Estas dos fracciones tienen valores idénticos, la
única diferencia real es que la primera está escrita en notación fraccional en base
10 y la segunda en base 2.
0.3
…o, mejor,
0.33
…o, mejor,
0.333
…y así. No importa cuantos dígitos desees escribir, el resultado nunca será
exactamente 1/3, pero será una aproximación cada vez mejor de 1/3.
0.0001100110011001100110011001100110011001100110011...
>>>
>>> 0.1
0.1000000000000000055511151231257827021181583404541015625
Esos son más dígitos que lo que la mayoría de la gente encuentra útil, por lo que
Python mantiene manejable la cantidad de dígitos al mostrar en su lugar un valor
redondeado
>>>
>>> 1 / 10
0.1
Sólo recordá que, a pesar de que el valor mostrado resulta ser exactamente 1/10,
el valor almacenado realmente es la fracción binaria más cercana posible.
Notá que esta es la verdadera naturaleza del punto flotante binario: no es un error
de Python, y tampoco es un error en tu código. Verás lo mismo en todos los
lenguajes que soportan la aritmética de punto flotante de tu hardware (a pesar de
que en algunos lenguajes por omisión no muestren la diferencia, o no lo hagan en
todos los modos de salida).
Para una salida más elegante, quizás quieras usar el formateo de cadenas de
texto para generar un número limitado de dígitos significativos:
>>>
>>> repr(math.pi)
'3.141592653589793'
Es importante darse cuenta que esto es, realmente, una ilusión: estás
simplemente redondeando al mostrar el valor verdadero de la máquina.
Una ilusión puede generar otra. Por ejemplo, ya que 0.1 no es exactamente 1/10,
sumar tres veces 0.1 podría también no generar exactamente 0.3:
>>>
>>> .1 + .1 + .1 == .3
False
También, ya que 0.1 no puede acercarse más al valor exacto de 1/10 y 0.3 no
puede acercarse más al valor exacto de 3/10, redondear primero con la
función round() no puede ayudar:
>>>
>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)
False
A pesar que los números no pueden acercarse a los valores exactos que
pretendemos, la función round() puede ser útil para redondear a posteriori, para
que los resultados con valores inexactos se puedan comparar entre sí:
>>>
Binary floating-point arithmetic holds many surprises like this. The problem with
«0.1» is explained in precise detail below, in the «Representation Error» section.
See The Perils of Floating Point for a more complete account of other common
surprises.
Como dice cerca del final, «no hay respuestas fáciles». A pesar de eso, ¡no le
tengas mucho miedo al punto flotante! Los errores en las operaciones flotantes de
Python se heredan del hardware de punto flotante, y en la mayoría de las
máquinas están en el orden de no más de una 1 parte en 2**53 por operación. Eso
es más que adecuado para la mayoría de las tareas, pero necesitás tener en
cuenta que no es aritmética decimal, y que cada operación de punto flotante sufre
un nuevo error de redondeo.
Para los casos de uso que necesitan una representación decimal exacta, probá el
módulo decimal, que implementa aritmética decimal útil para aplicaciones de
contabilidad y de alta precisión.
>>>
>>> x = 3.14159
>>> x.as_integer_ratio()
(3537115888337719, 1125899906842624)
Ya que la fracción es exacta, se puede usar para recrear sin pérdidas el valor
original:
>>>
>>>
>>> x.hex()
'0x1.921f9f01b866ep+1'
>>>
>>> x == float.fromhex('0x1.921f9f01b866ep+1')
True
>>>
15.1. Error de Representación
Esta sección explica el ejemplo «0.1» en detalle, y muestra como en la mayoría de
los casos vos mismo podés realizar un análisis exacto como este. Se asume un
conocimiento básico de la representación de punto flotante binario.
1 / 10 ~= J / (2**N)
…como
J ~= 2**N / 10
>>>
>>>
>>>
>>> q+1
7205759403792794
7205759403792794 / 2 ** 56
3602879701896397 / 2 ** 55
Notá que como lo redondeamos, esto es un poquito más grande que 1/10; si no lo
hubiéramos redondeado, el cociente hubiese sido un poquito menor que 1/10.
¡Pero no hay caso en que sea exactamente 1/10!
>>>
>>> 0.1 * 2 ** 55
3602879701896397.0
Si multiplicamos esa fracción por 10**55, podemos ver el valor hasta los 55 dígitos
decimales:
>>>
>>> 3602879701896397 * 10 ** 55 // 2 ** 55
1000000000000000055511151231257827021181583404541015625
>>>
>>>
>>> Fraction.from_float(0.1)
Fraction(3602879701896397, 36028797018963968)
>>> (0.1).as_integer_ratio()
(3602879701896397, 36028797018963968)
>>> Decimal.from_float(0.1)
Decimal('0.10000000000000000555111512312578270211815834045410156
25')
16. Apéndice
16.1. Modo interactivo
16.1.1. Manejo de errores
Cuando ocurre un error, el intérprete imprime un mensaje de error y la traza del
error. En el modo interactivo, luego retorna al prompt primario; cuando la entrada
viene de un archivo, el programa termina con código de salida distinto a cero luego
de imprimir la traza del error. (Las excepciones manejadas por una
clausula except en una sentencia try no son errores en este contexto). Algunos
errores son incondicionalmente fatales y causan una terminación con código de
salida distinto de cero; esto se debe a inconsistencias internas o a que el
intérprete se queda sin memoria. Todos los mensajes de error se escriben en el
flujo de errores estándar; las salidas normales de comandos ejecutados se
escriben en la salida estándar.
#!/usr/bin/env python3.5
$ chmod +x myscript.py
Este archivo es solo leído en las sesiones interactivas del intérprete, no cuando
Python lee comandos de un script ni cuando /dev/tty se explicita como una
fuente de comandos (que de otro modo se comporta como una sesión interactiva).
Se ejecuta en el mismo espacio de nombres en el que los comandos interactivos
se ejecutan, entonces los objetos que define o importa pueden ser usados sin
cualificaciones en la sesión interactiva. En este archivo también puedes cambiar
los prompts sys.ps1 y sys.ps2.
import os
filename = os.environ.get('PYTHONSTARTUP')
if filename and os.path.isfile(filename):
with open(filename) as fobj:
startup_file = fobj.read()
exec(startup_file)
>>>