0% encontró este documento útil (0 votos)
1K vistas240 páginas

Python Cisco Parte2

El documento explica qué son los módulos en Python. Los módulos dividen el código en partes separadas para facilitar el mantenimiento y la colaboración entre desarrolladores. Para usar un módulo, se debe importar y luego acceder a sus funciones y variables usando el nombre del módulo y un punto antes del nombre de la entidad. Esto permite que los nombres dentro del módulo no entren en conflicto con los nombres locales.

Cargado por

Eedwin Montañez
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
1K vistas240 páginas

Python Cisco Parte2

El documento explica qué son los módulos en Python. Los módulos dividen el código en partes separadas para facilitar el mantenimiento y la colaboración entre desarrolladores. Para usar un módulo, se debe importar y luego acceder a sus funciones y variables usando el nombre del módulo y un punto antes del nombre de la entidad. Esto permite que los nombres dentro del módulo no entren en conflicto con los nombres locales.

Cargado por

Eedwin Montañez
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 240

¿Qué es un módulo?

El código de computadora tiene una tendencia a crecer. Podemos decir que el


código que no crece probablemente sea completamente inutilizable o este
abandonado. Un código real, deseado y ampliamente utilizado se desarrolla
continuamente, ya que tanto las demandas de los usuarios como las
expectativas de los usuarios se desarrollan a su propio ritmo.

Un código que no puede responder a las necesidades de los usuarios se olvidará


rápidamente y se reemplazará instantáneamente con un código nuevo, mejor y
más flexible. Se debe estar preparado para esto, y nunca pienses que tus
programas están terminados por completo. La finalización es un estado de
transición y generalmente pasa rápidamente, después del primer informe de
error. Python en sí es un buen ejemplo de cómo actúa esta regla.

El código creciente es, de hecho, un problema creciente. Un código más grande


siempre significa un mantenimiento más difícil. La búsqueda de errores siempre
es más fácil cuando el código es más pequeño (al igual que encontrar una
rotura mecánica es más simple cuando la maquinaria es más simple y más
pequeña).

Además, cuando se espera que el código que se está creando sea realmente
grande (puedes usar el número total de líneas de código como una medida útil,
pero no muy precisa, del tamaño del código) entonces, se deseará, o más bien,
habrá la necesidad de dividirlo en muchas partes, implementado en paralelo
por unos cuantos, una docena, varias docenas o incluso varios cientos de
desarrolladores.

Por supuesto, esto no se puede hacer usando un archivo fuente grande, el cual
esta siendo editado por todos los programadores al mismo tiempo. Esto
seguramente conducirá a un desastre.

Si se desea que dicho proyecto de software se complete con éxito, se deben


tener los medios que le permitan:

 Se deben dividir todas las tareas entre los desarrolladores.


 Despues, unir todas las partes creadas en un todo funcional.

Por ejemplo, un determinado proyecto se puede dividir en dos partes


principales:

 La interfaz de usuario (la parte que se comunica con el usuario mediante


widgets y una pantalla gráfica).
 La lógica (la parte que procesa los datos y produce resultados).

Cada una de estas partes se puede (muy probablemente) dividir en otras más
pequeñas, y así sucesivamente. Tal proceso a menudo se
denomina descomposición.
Por ejemplo, si te pidieran organizar una boda, no harías todo tu mismo:
encontrarías una serie de profesionales y dividirías la tarea entre todos.

¿Cómo se divide una pieza de software en partes separadas pero cooperantes?


Esta es la pregunta. Módulos son la respuesta.

¿Cómo hacer uso de un módulo?


El manejo de los módulos consta de dos cuestiones diferentes:

 El primero (probablemente el más común) ocurre cuando se desea


utilizar un módulo ya existente, escrito por otra persona o creado por el
programador mismo en algún proyecto complejo: en este caso, se
considera al programador como el usuario del módulo.
 El segundo ocurre cuando se desea crear un nuevo módulo, ya sea para
uso propio o para facilitar la vida de otros programadores: aquí eres
el proveedor del módulo.

Discutamos ambas por separado.

En primer lugar, un módulo se identifica por su nombre. Si se desea utilizar


cualquier módulo, se necesita saber su nombre. Se entrega una cantidad
(bastante grande) de módulos junto con Python. Se puede pensar en ellos como
una especie de "equipo extra de Python".

Todos estos módulos, junto con las funciones integradas, forman la Biblioteca
estándar de Python - un tipo especial de biblioteca donde los módulos
desempeñan el papel de libros (incluso podemos decir que las carpetas
desempeñan el papel de estanterías). Si deseas ver la lista completa de todos
los "volúmenes" recopilados en esa biblioteca, se puede encontrar aquí:
https://docs.python.org/3/library/index.html.

Cada módulo consta de entidades (como un libro consta de capítulos). Estas


entidades pueden ser funciones, variables, constantes, clases y objetos. Si se
sabe cómo acceder a un módulo en particular, se puede utilizar cualquiera de
las entidades que almacena.

Comencemos la discusión con uno


de los módulos más utilizados, el
que lleva por nombre  math . Su
nombre habla por sí mismo: el
módulo contiene una rica colección
de entidades (no solo funciones)
que permiten a un programador
implementar efectivamente
cálculos que exigen el uso de
funciones matemáticas,
como sen() o log().

Importando un módulo
Para que un módulo sea utilizable, hay que importarlo (piensa en ello como sacar un
libro del estante). La importación de un módulo se realiza mediante una instrucción
llamada  import . Nota:  import  es también una palabra reservada (con todas sus
implicaciones).

Supongamos que deseas utilizar dos entidades proporcionadas por el módulo  math :

 Un símbolo (constante) que representa un valor preciso (tan preciso como sea
posible usando aritmética de punto flotante doble) de π (aunque usar una letra
griega para nombrar una variable es totalmente posible en Python, el símbolo
se llama pi: es una solución más conveniente, especialmente para esa parte del
mundo que ni tiene ni va a usar un teclado griego).
 Una función llamada  sin()  (el equivalente informático de la función
matemática sine).

Ambas entidades están disponibles a través del módulo  math , pero la forma en que se
pueden usar depende en gran medida de cómo se haya realizado la importación.
Importando un módulo: continuación
Para continuar, debes familiarizarte con un término importante: namespace.

No te preocupes, no entraremos en detalles: esta explicación será lo más breve


posible.

Un namespace es un espacio (entendido en un contexto no físico) en el que


existen algunos nombres y los nombres no entran en conflicto entre sí (es decir,
no hay dos objetos diferentes con el mismo nombre). Podemos decir que cada
grupo social es un namespace - el grupo tiende a nombrar a cada uno de sus
miembros de una manera única (por ejemplo, los padres no darán a sus hijos
los mismos nombres).

Esta singularidad se puede lograr de muchas maneras, por ejemplo, mediante


el

uso de apodos junto con los nombres (funcionará dentro de un grupo pequeño
como una clase en una escuela) o asignando identificadores especiales a todos
los miembros del grupo (el Seguro Social de EE. UU. El número es un buen
ejemplo de tal práctica).

Dentro de un determinado namespace, cada nombre debe permanecer


único. Esto puede significar que algunos nombres pueden desaparecer cuando
cualquier otra entidad de un nombre ya conocido ingresa al namespace.
Mostraremos cómo funciona y cómo controlarlo, pero primero, volvamos a las
importaciones.

Si el módulo de un nombre especificado existe y es accesible (un módulo es


de hecho un archivo fuente de Python), Python importa su contenido, se
hacen conocidos todos los nombres definidos en el módulo, pero no
ingresan al namespace del código.

Esto significa que puede tener sus propias entidades llamadas  sin  o  pi  y no
serán afectadas en alguna manera por el import.

En este punto, es posible que te


estes preguntando cómo
acceder al  pi  el cual viene del
módulo  math .

Para hacer esto, se debe de


mandar llamar el  pi  con el su
nombre en el módulo original.

Importando un módulo: continuación


Observa el fragmento a continuación, esta es la forma en que se habilitan los nombres
de  pi  y  sin  con el nombre de su módulo de origen:

math.pi
math.sin

Es sencillo, se pone:

 El nombre del módulo ( math ).


 Un punto.
 Y el nombre de la entidad ( pi ).

Tal forma indica claramente el namespace en el que existe el nombre.

Nota: el uso de esto es obligatorio si un módulo ha sido importado con la


instrucción  import . No importa si alguno de los nombres del código y del namespace
del módulo están en conflicto o no.

Este primer ejemplo no será muy avanzado: solo se desea imprimir el valor
de sin(1/2π).

Observa el código en el editor. Así es como se prueba.

El código genera el valor esperado:  1.0 .


Nota: el eliminar cualquiera de las dos indicaciones hará que el código sea erróneo. No
hay otra forma de entrar al namespace de  math  si se hizo lo siguiente:

import math

Importando un módulo: continuación


Ahora te mostraremos como pueden dos namespaces (el tuyo y el del módulo)
coexistir.

Echa un vistazo al ejemplo en la ventana del editor.

Se ha definido una variable y función propia para  pi  y  sin  respectivamente, y se


emplean junto con los de la librería math.

Ejecuta el programa. El código debe producir la siguiente salida:

0.99999999

1.0

Como puedes ver, las entidades no se afectan entre sí.


Importando un módulo: continuación
En el segundo método, la sintaxis del  import  señala con precisión qué entidad (o
entidades) del módulo son aceptables en el código:

from math import pi

La instrucción consta de los siguientes elementos:

 La palabra reservada  from .


 El nombre del módulo a ser (selectivamente) importado.
 La palabra reservada  import .
 El nombre o lista de nombres de la entidad o entidades las cuales estan
siendo importadas al namespace.

La instrucción tiene este efecto:

 Las entidades listadas son las unicas que son importadas del módulo
indicado.
 Los nombres de las entidades importadas pueden ser accedidas dentro del
programa.

Nota: no se importan otras entidades, únicamente las especificadas. Además, no se


pueden importar entidades adicionales utilizando una línea como esta:

print(math.e)

Esto ocasionará un error, ( e  es la constante de Euler: 2.71828...).

Reescribamos el código anterior para incorporar esta nueva técnica.

Aquí esta:
from math import sin, pi

print(sin(pi/2))

El resultado debe de ser el mismo que el anterior, se han empleado las mismas
entidades:  1.0 . Copia y pega el código en el editor, y ejecuta el programa.

¿El código parece más simple? Quizás, pero el aspecto no es el único efecto de este
tipo de importación. Veamos mas a detalle esto.

Importando un módulo: continuación


Observa el código en el editor. Analízalo cuidadosamente:

 La línea 01: lleva a cabo la importación selectiva.


 La línea 03: hace uso de las entidades importadas y obtiene el resultado
esperado ( 1.0 ).
 La línea 05 a la 11: redefine el significado de  pi  y  sin  - en efecto, reemplazan
las definiciones originales (importadas) dentro del namespace del código.
 La línea 13: retorna  0.99999999 , lo cual confirma nuestras conclusiones.

Hagamos otra prueba. Observa el código a continuación:

pi = 3.14 # linea 01
def sin(x):
if 2 * x == pi:
return 0.99999999
else:
return None # linea 07

print(sin(pi/2)) # linea 09

from math import sin, pi # linea 12


print(sin(pi/2)) # linea 14

Aquí, se ha invertido la secuencia de las operaciones del código:

 Las líneas del 01 al 07: definen nuestro propio  pi  y  sin .


 La línea 09: hace uso de ellas ( 0.99999999 aparece en pantalla).
 La línea 12: lleva a cabo la importación - los símbolos importados reemplazan
sus definiciones anteriores dentro del namespace.
 La línea 14: retorna  1.0  como resultado.

Importando un Módulo: *
En el tercer método, la sintaxis del  import  es una forma más agresiva que la
presentada anteriormente:

from module import *

Como puedes ver, el nombre de una entidad (o la lista de nombres de entidades) se


reemplaza con un solo asterisco ( * ).

Tal instrucción importa todas las entidades del módulo indicado.

¿Es conveniente? Sí, lo es, ya que libera del deber de enumerar todos los nombres que
se necesiten.

¿Es inseguro? Sí, a menos que conozca todos los nombres proporcionados por el
módulo, es posible que no puedas evitar conflictos de nombres. Trata esto como
una solución temporal e intenta no usarlo en un código regular.

Importando un módulo: la palabra reservada as


Si se importa un módulo y no se esta conforme con el nombre del módulo en
particular (por ejemplo, sí es el mismo que el de una de sus entidades ya definidas)
puede darsele otro nombre: esto se llama aliasing o renombrado.

Aliasing (renombrado) hace que el módulo se identifique con un nombre diferente al


original.
La creación de un alias se realiza junto con la importación del módulo, y exige la
siguiente forma de la instrucción import:

import module as alias

el "module" identifica el nombre del módulo original mientras que el "alias" es el


nombre que se desea usar en lugar del original.

Nota:  as  es una palabra reservada.

Importando un Módulo: continuación


Si necesitas cambiar la palabra  math , puedes introducir tu propio nombre, como en el
ejemplo:

import math as m

print(m.sin(m.pi/2))

Nota: después de la ejecución exitosa de una importación con alias, el nombre


original del módulo se vuelve inaccesible y no debe ser utilizado.

A su vez, cuando usa la variante  from module import name  y se necesita cambiar el
nombre de la entidad, se crea un alias para la entidad. Esto hará que el nombre sea
reemplazado por el alias que se elija.

Así es como se puede hacer:

from module import nombre as alias

Como anteriormente, el nombre original (sin alias) se vuelve inaccesible.

La frase  nombre as alias  puede repetirse: emplea comas para separar las frases,
como a continuación:

from module import n as a, m as b, o as c


El ejemplo puede parecer un poco extraño, pero funciona:

from math import pi as PI, sin as sine

print(sine(PI/2))

Ahora estás familiarizado con los conceptos básicos del uso de módulos. Permítenos
mostrarte algunos módulos y algunas de sus entidades útiles.

Trabajando con módulos estándar


Antes de comenzar a revisar algunos módulos estándar de Python, veamos la
función  dir() . No tiene nada que ver con el comando  dir  de las terminales de
Windows o Unix. El comando  dir()  no muestra el contenido de un directorio o
carpeta de disco, pero no se puede negar que hace algo similar: puede revelar
todos los nombres proporcionados a través de un módulo en particular.

Hay una condición: el módulo debe haberse importado previamente como un


todo (es decir, utilizar la instrucción  import module  -  from module  no es
suficiente).

La función devuelve una lista ordenada alfabéticamente la cual contiene


todos los nombres de las entidades disponibles en el módulo:

dir(module)

Nota: Si el nombre del módulo tiene un alias, debe usar el alias, no el nombre
original.

Usar la función dentro de un script normal no tiene mucho sentido, pero aún
así, es posible.

Por ejemplo, se puede ejecutar el siguiente código para imprimir los nombres
de todas las entidades dentro del módulo  math :

import math

for name in dir(math):

print(name, end="\t")
El código de ejemplo debería producir el siguiente resultado:

__doc__ __loader__ __name__ __package__ __spec__ acos acosh


asin asinh atan atan2 atanh ceil copysign cos cosh
degrees e erf erfc exp expm1 fabs factorial floor
fmod frexp fsum gamma hypot isfinite isinf isnan ldexp
lgamma log log10 log1p log2 modf pi pow radians
sin sinh sqrt tan tanh trunc

¿Has notado los nombres extraños que comienzan con  __  al inicio de la lista? Se
hablará más sobre ellos cuando hablemos sobre los problemas relacionados con
la escritura de módulos propios.

Algunos de los nombres pueden traer recuerdos de las lecciones de


matemáticas, y probablemente no tendrás ningún problema en adivinar su
significado.

El emplear la función  dir()  dentro de un código puede no parecer muy útil; por
lo general, se desea conocer el contenido de un módulo en particular antes de
escribir y ejecutar el código.

Afortunadamente, se puede ejecutar la función directamente en la consola


de Python (IDLE), sin necesidad de escribir y ejecutar un script por separado.

Así es como se puede hacer:

import math

dir(math)

Deberías de ver algo similar a esto:


Funciones seleccionadas del módulo math
Comencemos con una vista previa de algunas de las funciones proporcionadas por el
módulo  math . Se han elegido algunas arbitrariamente, pero esto no significa que las
funciones no mencionadas aquí sean menos significativas. Tomate el tiempo para
revisar las demás por ti mismo: no tenemos el espacio ni el tiempo para hablar de
todas a detalle. El primer grupo de funciones de módulo  math  están relacionadas
con trigonometría:

 sin(x)  → el seno de x.
 cos(x)  → el coseno de x.
 tan(x)  → la tangente de x.
Todas estas funciones toman un argumento (una medida de ángulo expresada en
radianes) y devuelven el resultado apropiado (ten cuidado con  tan()  - no todos los
argumentos son aceptados). También están sus versiones inversas:

 asin(x)  → el arcoseno de x.
 acos(x)  → el arcocoseno de x.
 atan(x)  → el arcotangente de x.

Estas funciones toman un argumento (verifica que sea correcto) y devuelven una
medida de un ángulo en radianes. Para trabajar eficazmente con mediciones de
ángulos, el módulo  math  proporciona las siguientes entidades:

 pi  → una constante con un valor que es una aproximación de π.


 radians(x)  → una función que convierte x de grados a radianes.
 degrees(x)  → actuando en el otro sentido (de radianes a grados).

Ahora observa el código en el editor. El programa de ejemplo no es muy sofisticado,


pero ¿puedes predecir sus resultados? Además de las funciones circulares
(enumeradas anteriormente), el módulo  math  también contiene un conjunto de
sus análogos hiperbólicos:

 sinh(x)  → el seno hiperbólico.


 cosh(x)  → el coseno hiperbólico.
 tanh(x)  → la tangente hiperbólico.
 asinh(x)  → el arcoseno hiperbólico.
 acosh(x)  → el arcocoseno hiperbólico.
 atanh(x)  → el arcotangente hiperbólico.

Funciones
seleccionadas del módulo math: continuación
Existe otro grupo de las funciones  math  relacionadas con la exponenciación:

 e  → una constante con un valor que es una aproximación del número de Euler
(e).
 exp(x)  → encontrar el valor de ex.
 log(x)  → el logaritmo natural de x.
 log(x, b)  → el logaritmo de x con base b.
 log10(x)  → el logaritmo decimal de x (más preciso que  log(x, 10) ).
 log2(x)  → el logaritmo binario de x (más preciso que  log(x, 2) ).
Nota: la función  pow() :

 pow(x, y)  → encontrar el valor de xy (toma en cuenta los dominios).

Esta es una función incorporada y no se tiene que importar.

Observa el código en el editor. ¿Puedes predecir su salida?

Funciones seleccionadas del módulo math:


continuación
El último grupo consta de algunas funciones de propósito general como:

 ceil(x)  → devuelve el entero más pequeño mayor o igual que x.


 floor(x)  → el entero más grande menor o igual que x.
 trunc(x)  → el valor de x truncado a un entero (ten cuidado, no es equivalente
a ceil o floor).
 factorial(x)  → devuelve x! (x tiene que ser un valor entero y no negativo).
 hypot(x, y)  → devuelve la longitud de la hipotenusa de un triángulo
rectángulo con las longitudes de los catetos iguales a x e y (lo mismo
que  sqrt(pow(x, 2) + pow(y, 2))  pero más preciso).

Mira el código en el editor. Analiza el programa cuidadosamente.


Demuestra las diferencias fundamentales entre  ceil() ,  floor()  y  trunc() .

Ejecuta el programa y verifica su salida.

¿Existe aleatoriedad real en las computadoras?


Otro módulo que vale la pena mencionar es el que se llama  random .

Ofrece algunos mecanismos que permiten operar con números


pseudoaleatorios.

Toma en cuenta el prefijo pseudo - los números generados por los módulos


pueden parecer aleatorios en el sentido de que no se pueden predecir, pero no
hay que olvidar que todos se calculan utilizando algoritmos muy refinados.
Los algoritmos no son aleatorios, son deterministas y predecibles. Solo aquellos
procesos físicos que se salgan completamente de nuestro control (como la
intensidad de la radiación cósmica) pueden usarse como fuente de datos
aleatorios reales. Los datos producidos por computadoras deterministas no
pueden ser aleatorios de ninguna manera.

Un generador de números aleatorios toma un valor llamado semilla, lo trata


como un valor de entrada, calcula un número "aleatorio" basado en él (el
método depende de un algoritmo elegido) y produce una nueva semilla.

La duración de un ciclo en el que todos los valores semilla son únicos puede ser
muy largo, pero no es infinito: tarde o temprano los valores iniciales
comenzarán a repetirse y los valores generadores también se repetirán. Esto es
normal. Es una característica, no un error.

El valor de la semilla inicial, establecido durante el inicio del programa,


determina el orden en que aparecerán los valores generados.

El factor aleatorio del proceso puede ser aumentado al establecer la semilla


tomando un número de la hora actual - esto puede garantizar que cada
lanzamiento del programa comience desde un valor semilla diferente (por lo
tanto, usará diferentes números aleatorios).

Afortunadamente, Python realiza dicha inicialización al importar el módulo.

Funciones seleccionadas del módulo random


La función general llamada  random()  (no debe confundirse con el nombre del
módulo) produce un número flotante  x  entre el rango  (0.0, 1.0)  - en otras
palabras: (0.0 <= x < 1.0).

El programa de ejemplo en el editor producirá cinco valores pseudoaleatorios, ya que


sus valores están determinados por el valor semilla (un valor impredecible) actual, no
se pueden adivinar. Ejecuta el programa.
La función  seed()  es capaz de establecer la semilla del generador. Te mostraremos
dos de sus variantes:

 seed()  - establece la semilla con la hora actual.


 seed(int_value)  - establece la semilla con el valor entero  int_value .

Hemos modificado el programa anterior; de hecho, hemos eliminado cualquier rastro


de aleatoriedad del código:

from random import random, seed

seed(0)

for i in range(5):
print(random())

Debido al hecho de que la semilla siempre se establece con el mismo valor, la


secuencia de valores generados siempre se ve igual.

Ejecuta el programa. Esto es lo que tenemos:

0.844421851525
0.75795440294
0.420571580831
0.258916750293
0.511274721369

¿Y tú?

Nota: sus valores pueden ser ligeramente diferentes si tu sistema utiliza aritmética de
punto flotante más precisa o menos precisa, pero la diferencia se verá bastante lejos
del punto decimal.

Funciones seleccionadas
del módulo random: continuación
Si deseas valores aleatorios enteros, una de las siguientes funciones encajaría mejor:

 randrange(fin) x
 randrange(inico, fin)
 randrange(inicio, fin, incremento)
 randint(izquierda, derecha)
Las primeras tres invocaciones generarán un número entero tomado
(pseudoaleatoriamente) del rango:

 range(fin)
 range(inicio, fin)
 range(inicio, fin, incremento)

Toma en cuenta la exclusión implícita del lado derecho.

La última función es equivalente a  randrange(izquierda, derecha+1)  - genera el


valor entero  i , el cual cae en el rango [izquierda, derecha] (sin exclusión en el lado
derecho).

Observa el código en el editor. Este programa generará una línea que consta de tres
ceros y un cero o un uno en el cuarto lugar.

Funciones seleccionadas del módulo random:


continuación
Las funciones anteriores tienen una desventaja importante: pueden producir valores
repetidos incluso si el número de invocaciones posteriores no es mayor que el rango
especificado. Observa el código en el editor. Es muy probable que el programa genere
un conjunto de números en el que algunos elementos no sean únicos.Esto es lo que se
obtiene al ejecutarlo:
9,4,5,4,5,8,9,4,8,4,

Como puedes ver, esta no es una buena herramienta para generar números para la
lotería. Afortunadamente, existe una mejor solución que escribir tu propio código para
verificar la singularidad de los números "sorteados".

Es una función con el nombre de -  choice :

 choice(secuencia)
 sample(secuencia, elementos_a_elegir=1)

La primera variante elige un elemento "aleatorio" de la secuencia de entrada y lo


devuelve. El segundo crea una lista (una muestra) que consta del
elemento  elementos_a_elegir  (que por defecto es  1 ) "sorteado" de la secuencia de
entrada. En otras palabras, la función elige algunos de los elementos de entrada,
devolviendo una lista con la elección. Los elementos de la muestra se colocan en
orden aleatorio. Nota que  elementos_a_elegir  no debe ser mayor que la longitud de
la secuencia de entrada.

Observa el código a continuación:

from random import choice, sample

lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(choice(lst))
print(sample(lst, 5))
print(sample(lst, 10))

Nuevamente, la salida del programa no es predecible. Nuestros resultados se ven así:

4
[3, 1, 8, 9, 10]
[10, 8, 5, 1, 6, 4, 3, 9, 7, 2]

¿Cómo saber dónde


estás?
A veces, puede ser necesario encontrar información no relacionada con Python.
Por ejemplo, es posible que necesites conocer la ubicación de tu programa
dentro del entorno de la computadora.

Imagina el entorno de tu programa como una pirámide que consta de varias


capas o plataformas.
Las capas son:

 El código (en ejecución) se encuentra en la parte superior.


 Python (mejor dicho, su entorno de ejecución) se encuentra
directamente debajo de él.
 La siguiente capa de la pirámide se llena con el SO (sistema operativo):
el entorno de Python proporciona algunas de sus funcionalidades
utilizando los servicios del sistema operativo. Python, aunque es muy
potente, no es omnipotente: se ve obligado a usar muchos ayudantes si
va a procesar archivos o comunicarse con dispositivos físicos.
 La capa más inferior es el hardware: el procesador (o procesadores), las
interfaces de red, los dispositivos de interfaz humana (ratones, teclados,
etc.) y toda otra maquinaria necesaria para hacer funcionar la
computadora: el sistema operativo sabe cómo emplearlos y utiliza
muchos trucos para trabajar con todas las partes en un ritmo constante.

Esto significa que algunas de las acciones del programa tienen que recorrer un
largo camino para ejecutarse con éxito, imagina que:

 Tu código quiere crear un archivo, por lo que invoca una de las


funciones de Python.
 Python acepta la orden, la reorganiza para cumplir con los requisitos del
sistema operativo local (es como poner el sello "aprobado" en una
solicitud) y lo envía.
 El SO comprueba si la solicitud es razonable y válida (por ejemplo, si el
nombre del archivo se ajusta a algunas reglas de sintaxis) e intenta crear
el archivo. Tal operación, aparentemente es muy simple, no es atómica:
consiste de muchos pasos menores tomados por...
 El hardware, el cual es responsable de activar los dispositivos de
almacenamiento (disco duro, dispositivos de estado sólido, etc.) para
satisfacer las necesidades del sistema operativo.

Por lo general, no eres consciente de todo ese alboroto: quieres que se cree el
archivo y eso es todo.

Pero a veces quieres saber más, por ejemplo, el nombre del sistema operativo
que aloja Python y algunas características que describen el hardware que aloja
el sistema operativo.

Hay un módulo que proporciona algunos medios para permitir saber dónde se
encuentra y qué componentes funcionan. El módulo se llama platform. Veamos
algunas de las funciones que brinda.

Funciones seleccionadas del módulo platform


El módulo  platform  permite acceder a los datos de la plataforma subyacente, es
decir, hardware, sistema operativo e información sobre la versión del intérprete. Existe
también una función que puede mostrar todas las capas subyacentes en un solo
vistazo, llamada  platform . Simplemente devuelve una cadena que describe el
entorno; por lo tanto, su salida está más dirigida a los humanos que al procesamiento
automatizado (lo veremos pronto). Así es como se puede invocar:  platform(aliased
= False, terse = False) .

Ahora:

 aliased  → cuando se establece a  True  (o cualquier valor distinto de cero)


puede hacer que la función presente los nombres de capa subyacentes
alternativos en lugar de los comunes.
 terse  → cuando se establece a  True  (o cualquier valor distinto de cero) puede
convencer a la función de presentar una forma más breve del resultado (si lo
fuera posible).

Ejecutamos el programa usando tres plataformas diferentes: esto es lo que se obtuvo:

Intel x86 + Windows ® Vista (32 bit):

Windows-Vista-6.0.6002-SP2
Windows-Vista-6.0.6002-SP2
Windows-Vista

Intel x86 + Gentoo Linux (64 bit):

Linux-3.18.62-g6-x86_64-Intel-R-_Core-TM-_i3-2330M_CPU_@_2.20GHz-
with-gentoo-2.3
Linux-3.18.62-g6-x86_64-Intel-R-_Core-TM-_i3-2330M_CPU_@_2.20GHz-
with-gentoo-2.3
Linux-3.18.62-g6-x86_64-Intel-R-_Core-TM-_i3-2330M_CPU_@_2.20GHz-
with-glibc2.3.4

Raspberry PI2 + Raspbian Linux (32 bit):

Linux-4.4.0-1-rpi2-armv7l-with-debian-9.0
Linux-4.4.0-1-rpi2-armv7l-with-debian-9.0
Linux-4.4.0-1-rpi2-armv7l-with-glibc2.9
Funciones seleccionadas del módulo platform:
continuación
A veces, es posible que solo se desee conocer el nombre genérico del procesador que
ejecuta el sistema operativo junto con Python y el código, una función
llamada  machine()  te lo dirá. Como anteriormente, la función devuelve una cadena.

Nuevamente, ejecutamos el programa en tres plataformas diferentes:

Intel x86 + Windows ® Vista (32 bit):

x86

Intel x86 + Gentoo Linux (64 bit):

x86_64

Raspberry PI2 + Raspbian Linux (32 bit):

armv7l
Funciones seleccionadas del módulo platform:
continuación
La función  processor()  devuelve una cadena con el nombre real del procesador (si lo
fuese posible).

Una vez más, ejecutamos el programa en tres plataformas diferentes:

Intel x86 + Windows ® Vista (32 bit):

x86

Intel x86 + Gentoo Linux (64 bit):

Intel(R) Core(TM) i3-2330M CPU @ 2.20GHz

Raspberry PI2 + Raspbian Linux (32 bit):

armv7l
Funciones seleccionadas del módulo platform:
continuación
Una función llamada  system()  devuelve el nombre genérico del sistema operativo en
una cadena.

Nuestras plataformas de ejemplo se presentaron así:

Intel x86 + Windows ® Vista (32 bit):

Windows

Intel x86 + Gentoo Linux (64 bit):

Linux

Raspberry PI2 + Raspbian Linux (32 bit):

Linux
Funciones seleccionadas del módulo platform:
continuación
La versión del sistema operativo se proporciona como una cadena por la
función  version() .

Ejecuta el código y verifica su salida. Esto es lo que tenemos:

Intel x86 + Windows ® Vista (32 bit):

6.0.6002

Intel x86 + Gentoo Linux (64 bit):

#1 SMP PREEMPT Fri Jul 21 22:44:37 CEST 2017

Raspberry PI2 + Raspbian Linux (32 bit):

#1 SMP Debian 4.4.6-1+rpi14 (2016-05-05)


Funciones seleccionadas del módulo platform:
continuación
Si necesitas saber qué versión de Python está ejecutando tu código, puedes verificarlo
utilizando una serie de funciones dedicadas, aquí hay dos de ellas:

 python_implementation()  → devuelve una cadena que denota la


implementación de Python (espera  CPython  aquí, a menos que decidas utilizar
cualquier rama de Python no canónica).
 python_version_tuple()  → devuelve una tupla de tres elementos la cual
contiene:
o la parte mayor de la versión de Python.
o la parte menor,
o el número de nivel del patch.

Nuestro programa ejemplo produjo el siguiente resultado:

CPython
3
6
4

Es muy probable que tu versión de Python sea diferente.


Índice del Módulo de Python
Aquí solo hemos cubierto los conceptos básicos de los módulos de Python. Los
módulos de Python conforman su propio universo, en el que Python es solo una
galaxia, y nos aventuraríamos a decir que explorar las profundidades de estos
módulos puede llevar mucho más tiempo que familiarizarse con Python "puro".

Además, la comunidad de Python en todo el mundo crea y mantiene cientos de


módulos adicionales utilizados en aplicaciones muy específicas como la
genética, la psicología o incluso la astrología.

Estos módulos no están (y no serán) distribuidos junto con Python, o a través de


canales oficiales, lo que hace que el universo de Python sea más amplio, casi
infinito.

Puedes leer sobre todos los módulos estándar de Python


aquí: https://docs.python.org/3/py-modindex.html.

No te preocupes, no necesitarás todos estos módulos. Muchos de ellos son muy


específicos.

Todo lo que se necesita hacer es encontrar los módulos que se desean y


aprender a cómo usarlos. Es fácil.
En la siguiente sección veremos cómo escribir módulos propios.

¿Qué es un paquete?
Escribir tus propios módulos no difiere mucho de escribir scripts comunes.

Existen algunos aspectos específicos que se deben tomar en cuenta, pero


definitivamente no es algo complicado. Lo verás pronto.

Resumamos algunos aspectos importantes:

 Un módulo es un contenedor lleno de funciones - puedes


empaquetar tantas funciones como desees en un módulo y distribuirlo
por todo el mundo.
 Por supuesto, no es una buena idea mezclar funciones con diferentes
áreas de aplicación dentro de un módulo (al igual que en una biblioteca:
nadie espera que los trabajos científicos se incluyan entre los cómics),
así que se deben agrupar las funciones cuidadosamente y asignar un
nombre claro e intuitivo al módulo que las contiene (por ejemplo, no le
des el nombre  videojuegos  a un módulo que contiene funciones
destinadas a particionar y formatear discos duros).
 Crear muchos módulos puede causar desorden: tarde que temprano
querrás agrupar tus módulos de la misma manera que previamente
has agrupado funciones: ¿Existe un contenedor más general que un
módulo?.
 Sí lo hay, es un paquete: en el mundo de los módulos, un paquete juega
un papel similar al de una carpeta o directorio en el mundo de los
archivos.
Tu primer módulo
En esta sección, trabajarás localmente en tu máquina. Comencemos desde
cero, de la siguiente manera:

Se necesitan dos archivos para realizar estos


experimentos. Uno de ellos será el módulo en
sí. Está vacío ahora. No te preocupes, lo vas
a llenar con el código real. El archivo lleva
por nombre module.py. No muy creativo,
pero es simple y claro.El segundo archivo
contiene el código usando el nuevo módulo.
Su nombre es main.py.

Su contenido es muy breve hasta ahora:

Nota: ambos archivos deben estar


ubicados en la misma carpeta. Te
recomendamos crear una carpeta
nueva y vacía para ambos archivos.
Esto hará que las cosas sean más
fáciles. Inicia el IDLE y ejecuta el
archivo main.py.¿Que ves?

No deberías ver nada. Esto significa que Python ha importado con éxito el
contenido del archivo module.py. No importa que el módulo esté vacío por
ahora. El primer paso ya está hecho, pero antes de dar el siguiente paso,
queremos que eches un vistazo a la carpeta en la que se encuentran ambos
archivos.

¿Notas algo interesante?

Ha aparecido una nueva subcarpeta, ¿puedes verla? Su nombre


es __pycache__. Echa un vistazo adentro. ¿Que ves?

Hay un archivo llamado (más o menos) module.cpython-


xy.pyc donde x y y son dígitos derivados de tu versión de Python (por ejemplo,
serán 3 y 4 si utilizas Python 3.4). El nombre del archivo es el mismo que el de
tu módulo. La parte posterior al primer punto dice qué implementación de
Python ha creado el archivo (CPython) y su número de versión. La ultima parte
(pyc) viene de las palabras Python y compilado. Puedes mirar dentro del
archivo: el contenido es completamente ilegible para los humanos. Tiene que
ser así, ya que el archivo está destinado solo para uso de Python. Cuando
Python importa un módulo por primera vez, traduce el contenido a una
forma "semi" compilada. El archivo no contiene código en lenguaje máquina:
es código semi-compilado interno de Python, listo para ser ejecutado por el
intérprete de Python. Como tal archivo no requiere tantas comprobaciones
como las de un archivo fuente, la ejecución comienza más rápido y también se
ejecuta más rápido.

Gracias a eso, cada importación posterior será más rápida que interpretar el
código fuente desde cero.

Python puede verificar si el archivo fuente del módulo ha sido modificado (en
este caso, el archivo pyc será reconstruido) o no (cuando el archivo pyc pueda
ser ejecutado al instante). Este proceso es completamente automático y
transparente, no se tiene que estar tomando en cuenta.

Tu primer módulo: continuación


Ahora hemos puesto algo en el archivo del módulo:

¿Puedes notar alguna diferencia entre un módulo y un script ordinario? No hay


ninguna hasta ahora. Es posible ejecutar este archivo como cualquier otro
script. Pruébalo por ti mismo.

¿Que es lo que pasa? Deberías de ver la siguiente línea dentro de tu consola:

Me gusta ser un módulo.

Volvamos al archivo main.py:

Ejecuta el archivo. ¿Que ves? Con suerte, verás algo como esto:

Me gusta ser un módulo.

¿Qué significa realmente?

Cuando un módulo es importado, su contenido es ejecutado implícitamente


por Python. Le da al módulo la oportunidad de inicializar algunos de sus
aspectos internos (por ejemplo, puede asignar a algunas variables valores
útiles). Nota: la inicialización se realiza solo una vez, cuando se produce la
primera importación, por lo que las asignaciones realizadas por el módulo no se
repiten innecesariamente.

Imagina el siguiente contexto:

 Existe un módulo llamado mod1.


 Existe un módulo llamado mod2 el cual contiene la instrucción  import
mod1 .
 Hay un archivo principal que contiene las instrucciones  import
mod1  y  import mod2 .

A primera vista, se puede pensar que mod1 será importado dos veces -


afortunadamente, solo se produce la primera importación. Python recuerda
los módulos importados y omite silenciosamente todas las importaciones
posteriores.

Python puede hacer mucho más. También crea una variable llamada  __name__ .

Además, cada archivo fuente usa su propia versión separada de la variable, no


se comparte entre módulos. Te mostraremos cómo usarlo. Modifica el módulo
un poco:

Ver código en Sandbox

Ahora ejecuta el archivo module.py. Deberías ver las siguientes líneas:

Me gusta ser un módulo.

__main__

Ahora ejecuta el archivo main.py. ¿Y? ¿Ves lo mismo que nosotros?

Me gusta ser un módulo.

module

Podemos decir que:

 Cuando se ejecuta un archivo directamente, su variable  __name__  se


establece a  __main__ .
 Cuando un archivo se importa como un módulo, su variable  __name__  se
establece al nombre del archivo (excluyendo a .py).
Así es como puedes hacer uso de la variable  __main__  para detectar el contexto
en el cual se activó tu código:

Ver código en Sandbox

Sin embargo, hay una forma más inteligente de utilizar la variable. Si escribes
un módulo lleno de varias funciones complejas, puedes usarla para colocar una
serie de pruebas para verificar si las funciones trabajan correctamente.

Cada vez que modifiques alguna de estas funciones, simplemente puedes


ejecutar el módulo para asegurarte de que sus enmiendas no estropeen el
código. Estas pruebas se omitirán cuando el código se importe como un
módulo.

Tu primer módulo: continuación


Este módulo contendrá dos funciones simples, y si deseas saber cuántas veces
se han invocado las funciones, necesitas un contador inicializado en cero
cuando se importa el módulo.
Puedes hacerlo de esta manera:

Ver código en Sandbox

El introducir tal variable es absolutamente correcto, pero puede causar


importantes efectos secundarios que debes tener en cuenta.

Analiza el archivo modificado main.py:

Ver código en Sandbox

Como puedes ver, el archivo principal intenta acceder a la variable de contador


del módulo. ¿Es esto legal? Sí lo es. ¿Es utilizable? Claro. ¿Es seguro? Eso
depende: si confías en los usuarios de tu módulo, no hay problema; sin
embargo, es posible que no desees que el resto del mundo vea tu variable
personal o privada.

A diferencia de muchos otros lenguajes de programación, Python no tiene


medios para permitirte ocultar tales variables a los ojos de los usuarios del
módulo. Solo puedes informar a tus usuarios que esta es tu variable, que
pueden leerla, pero que no deben modificarla bajo ninguna circunstancia.

Esto se hace anteponiendo al nombre de la variable  _  (un guión bajo) o  __  (dos


guiones bajos), pero recuerda, es solo un acuerdo. Los usuarios de tu módulo
pueden obedecerlo o no.

Nosotros por supuesto, lo respetaremos. Ahora pongamos dos funciones en el


módulo: evaluarán la suma y el producto de los números recopilados en una
lista.

Además, agreguemos algunos adornos allí y eliminemos los restos superfluos.

El módulo está listo:

Ver código en
Sandbox

Algunos elementos necesitan explicación:


 La línea que comienza con  #!  desde el punto de vista de Python, es solo
un comentario debido a que comienza con  # . Para sistemas operativos
Unix y similares a Unix (incluido MacOS), dicha línea indica al sistema
operativo cómo ejecutar el contenido del archivo (en otras
palabras, qué programa debe lanzarse para interpretar el texto). En
algunos entornos (especialmente aquellos conectados con servidores
web) la ausencia de esa línea causará problemas.
 Una cadena (quizás una multilínea) colocada antes de las instrucciones
de cualquier módulo (incluidas las importaciones) se denomina doc-
string, y debe explicar brevemente el propósito y el contenido del
módulo.
 Las funciones definidas dentro del módulo ( suml()  y  prodl() ) están
disponibles para ser importadas.
 Se ha utilizado la variable  __name__  para detectar cuándo se ejecuta el
archivo de forma independiente.

Ahora es posible usar el nuevo módulo, esta es una forma de hacerlo:

Tu primer
módulo: continuación
Es hora de hacer este ejemplo más complejo: hemos asumido aquí que el
archivo Python principal se encuentra en la misma carpeta o directorio que el
módulo que se va a importar.

Realicemos el siguiente experimento mental:

 Estamos utilizando el sistema operativo Windows ® (esta suposición es


importante, ya que la forma del nombre del archivo depende de ello).
 El script principal de Python se encuentra en C:\Users\user\py\progs y
se llama main.py.
 El módulo a importar se encuentra en C:\Users\user\py\modules .
¿Como lidiar con ello?

Para responder a esta pregunta, tenemos que hablar sobre cómo Python


busca módulos. Hay una variable especial (en realidad una lista) que
almacena todas las ubicaciones (carpetas o directorios) que se buscan para
encontrar un módulo que ha sido solicitado por la instrucción import. Python
examina estas carpetas en el orden en que aparecen en la lista: si el módulo no
se puede encontrar en ninguno de estos directorios, la importación falla. De lo
contrario, se tomará en cuenta la primera carpeta que contenga un módulo con
el nombre deseado (si alguna de las carpetas restantes contiene un módulo con
ese nombre, se ignorará).

La variable se llama  path  (ruta), y es accesible a través del módulo


llamado  sys . Así es como puedes verificar su valor:

Hemos lanzado el código dentro del directorio C:\User\user y obtenemos:

C:\Users\user

C:\Users\user\AppData\Local\Programs\Python\Python36-
32\python36.zip

C:\Users\user\AppData\Local\Programs\Python\Python36-32\DLLs

C:\Users\user\AppData\Local\Programs\Python\Python36-32\lib

C:\Users\user\AppData\Local\Programs\Python\Python36-32

C:\Users\user\AppData\Local\Programs\Python\Python36-32\lib\site-
packages

Nota: la carpeta en la que comienza la ejecución aparece en el primer elemento


de la ruta.

Ten en cuenta también que: hay un archivo zip listado como uno de los
elementos de la ruta, esto no es un error. Python puede tratar los archivos zip
como carpetas ordinarias, esto puede ahorrar mucho almacenamiento.

¿Puedes predecir cómo resolver este problema?

Puedes resolverlo agregando una carpeta que contenga el módulo a la variable


de ruta (path variable), es completamente modificable.
Una de las varias soluciones posibles se ve así:

Ver código en Sandbox

Nota:

 Se ha duplicado la  \  dentro del nombre de la carpeta, ¿sabes por qué?

Revisar
 Debido a que una diagonal invertida se usa para escapar de otros caracteres, si
deseas obtener solo una diagonal invertida, debe escapar.

 Hemos utilizado el nombre relativo de la carpeta: esto funcionará si inicia


el archivo main.py directamente desde su carpeta de inicio, y no
funcionará si el directorio actual no se ajusta a la ruta relativa; siempre
puedes usar una ruta absoluta, como esta:

path.append('C:\\Users\\user\\py\\modules')

 Hemos usado el método  append() , la nueva ruta ocupará el último


elemento en la lista de rutas; si no te gusta la idea, puedes usar en lugar
de ello el método  insert() .

Tu primer paquete
Imagina que en un futuro no muy lejano, tu y tus socios escriben una gran
cantidad de funciones en Python.

Tu equipo decide agrupar las funciones en módulos separados, y este es el


resultado final:
Nota: hemos presentado todo el contenido solo para el módulo omega:
supongamos que todos los módulos tienen un aspecto similar (contienen una
función denominada  funX , donde  X  es la primera letra del nombre del módulo).

De repente, alguien se da cuenta de que estos módulos forman su propia


jerarquía, por lo que colocarlos a todos en una estructura plana no será una
buena idea.

Después de algo de discusión, el equipo llega a la conclusión de que los


módulos deben agruparse. Todos los participantes están de acuerdo en que la
siguiente estructura de árbol refleja perfectamente las relaciones mutuas entre
los módulos:
Repasemos esto de abajo hacia arriba:

 El grupo ugly contiene dos módulos: psi y omega.


 El grupo best contiene dos módulos: sigma y tau.
 El grupo good contiene dos módulos: (alpha y beta) y un subgrupo
(best).
 El grupo extra contiene dos subgrupos: (good y bad) y un módulo (iota).

¿Se ve mal? De ninguna manera: analiza la estructura cuidadosamente. Se


parece a algo, ¿no?

Parece la estructura de un directorio.


Tu primer paquete: continuación
Así es como se ve actualmente la relación entre módulos:

Tal estructura es casi un paquete (en el sentido de Python). Carece del detalle
fino para ser funcional y operativo. Lo completaremos en un momento. Si
asumes que extra es el nombre de un recientemente creado
paquete (piensa en el como la raíz del paquete), impondrá una regla de
nomenclatura que te permitirá nombrar claramente cada entidad del árbol.

Por ejemplo:

 La ubicación de una función llamada  funT()  del paquete tau puede


describirse como: extra.good.best.tau.funT()
 Una función marcada como: extra.ugly.psi.funP()
proviene del módulo psi el cual esta almacenado en
subpaquete ugly del paquete extra.

Se deben responder dos preguntas:

 ¿Cómo se transforma este árbol (en realidad, un subárbol) en un paquete


real de Python (en otras palabras, ¿cómo convence a Python de que
dicho árbol no es solo un montón de archivos basura, sino un conjunto de
módulos)?
 ¿Dónde se coloca el subárbol para que Python pueda acceder a él?

La primer pregunta tiene una respuesta sorprendente: los paquetes, como


los módulos, pueden requerir inicialización. La inicialización de un módulo
se realiza mediante un código independiente (que no forma parte de ninguna
función) ubicado dentro del archivo del módulo. Como un paquete no es un
archivo, esta técnica es inútil para inicializar paquetes.

En su lugar, debes usar un truco diferente: Python espera que haya un archivo
con un nombre muy exclusivo dentro de la carpeta del paquete:  __init__.py .
El contenido del archivo se ejecuta cuando se importa cualquiera de los
módulos del paquete. Si no deseas ninguna inicialización especial, puedes dejar
el archivo vacío, pero no debes omitirlo.

Tu primer paquete: continuación


La presencia del archivo __init.py__ finalmente compone el paquete:

Nota: no solo la carpeta raiz puede contener el archivo __init.py__ - también


puedes ponerlo dentro de cualquiera de sus subcarpetas (subpaquetes). Puede
ser útil si algunos de los subpaquetes requieren tratamiento individual o un tipo
especial de inicialización.

Ahora es el momento de responder la segunda pregunta: la respuesta es


simple: donde quiera. Solo tienes que asegurarte de que Python conozca la
ubicación del paquete. Ya sabes cómo hacer eso.

Estás listo para usar tu primer paquete.

Supongamos que el entorno de trabajo se ve de la siguiente manera:


Hemos preparado un archivo zip que contiene todos los archivos de la rama de
paquetes. Puedes descargarlo y usarlo para tus propios experimentos, pero
recuerda desempaquetarlo en la carpeta presentada en el esquema, de lo
contrario, no será accesible para el código.

DESCARGAR   Archivo ZIP Módulos y paquetes

Continuarás tus experimentos usando el archivo main2.py.

Tu primer paquete: continuación


Vamos a acceder a la función  funI()  del módulo iota del paquete extra. Nos
obliga a usar nombres de paquetes calificados (asocia esto al nombramiento de
carpetas y subcarpetas).

Asi es como se hace:

Ver código Sandbox

Nota:

 Hemos modificado la variable  path  para que sea accesible a Python.


 El  import  no apunta directamente al módulo, pero especifica la ruta
completa desde la parte superior del paquete.

El reemplazar  import extra.iota  con  import iota  causará un error.

La siguiente variante también es válida:

Ver código en Sandbox

Nota el nombre calificado del módulo iota.


Tu primer paquete: continuación
Ahora vamos hasta el final del árbol: así es como se obtiene acceso a los
módulos sigma y t au.

Ver código en Sandbox

Puedes hacer tu vida más fácil usando un alias:

Ver código en Sandbox

Supongamos que hemos comprimido todo el subdirectorio, comenzando desde


la carpeta extra ( incluyéndola), y se obtuvo un archivo
llamado extrapack.zip. Después, colocamos el archivo dentro de la
carpeta packages.

Ahora podemos usar el archivo zip en un rol de paquetes:

Ver código en Sandbox


Errores, fallas y otras plagas.
Cualquier cosa que pueda salir mal, saldrá mal.

Esta es la ley de Murphy, y funciona en todo y siempre. Si la ejecución del código


puede salir mal, lo hará.

Observa el código en el editor. Hay al menos dos formas posibles de que "salga mal" la
ejecución. ¿Puedes verlas?

 Como el usuario puede ingresar una cadena de caracteres completamente


arbitraria, no hay garantía de que la cadena se pueda convertir en un valor
flotante - esta es la primera vulnerabilidad del código.
 La segunda es que la función  sqrt()  fallará si se le ingresa un valor
negativo.

Puedes recibir alguno de los siguientes mensajes de error.

Algo como esto:

Ingresa x: Abracadabra
Traceback (most recent call last):
File "sqrt.py", line 3, in
x = float(input("Ingresa x: "))
ValueError: could not convert string to float: 'Abracadabra'

O algo como esto:

Ingresa x: -1
Traceback (most recent call last):
File "sqrt.py", line 4, in
y = math.sqrt(x)
ValueError: math domain error

¿Puedes protegerte de tales sorpresas? Por supuesto que si. Además, tienes que
hacerlo para ser considerado un buen programador.
Excepciones
Cada vez que tu código intenta hacer algo erroneo, irresponsable o inaplicable,
Python hace dos cosas:

 Detiene tu programa.
 Crea un tipo especial de dato, llamado excepción.

Ambas actividades llevan por nombre lanzar una excepción. Podemos decir


que Python siempre lanza una excepción (o que una excepción ha sido
lanzada) cuando no tiene idea de qué hacer con el código.

¿Qué ocurre después?

 La excepción lanzada espera que alguien o algo lo note y haga algo al


respecto.
 Si la excepción no es resuelta, el programa será terminado
abruptamente, y verás un mensaje de error enviado a la consola por
Python.
 De otra manera, si se atiende la excepción y
es manejada apropiadamente, el programa puede reanudarse y su
ejecución puede continuar.

Python proporciona herramientas efectivas que permiten observar,


identificar y manejar las excepciones eficientemente. Esto es posible
debido a que todas las excepciones potenciales tienen un nombre específico,
por lo que se pueden clasificar y reaccionar a ellas adecuadamente.

Ya conoces algunos nombres de excepción.

Observa el siguiente mensaje de diagnóstico:

ValueError : math domain error


La palabra en rojo es solo el nombre de la excepción. Vamos a
familiarizarnos con algunas otras excepciones.

Excepciones: continuación
Observa el código en el editor. Ejecuta el (obviamente incorrecto) programa.

Verás el siguiente mensaje en respuesta:

Traceback (most recent call last):


File "div.py", line 2, in
valor /= 0
ZeroDivisionError: division by zero

Este error de excepción se llama ZeroDivisionError.

Excepciones:
continuación
Observa el código en el editor. ¿Qué pasará cuando lo ejecutes?

Verás el siguiente mensaje en respuesta:

Traceback (most recent call last):


File "lst.py", line 2, in
x = lista[0]
IndexError: list index out of range

Este es el IndexError (error de índice).


Excepciones: continuación
¿Cómo se manejan las excepciones? La palabra  try  es clave para la solución.

Además, también es una palabra reservada.

La receta para el éxito es la siguiente:

 Primero, se debe intentar (try) hacer algo.


 Después, tienes que comprobar si todo salió bien.

Pero, ¿no sería mejor verificar primero todas las circunstancias y luego hacer algo solo
si es seguro?

Justo como el ejemplo en el editor.

Es cierto que esta forma puede parecer la más natural y comprensible, pero en
realidad, este método no facilita la programación. Todas estas revisiones pueden
hacer el código demasiado grande e ilegible.

Python prefiere un enfoque completamente diferente.


Excepciones: continuación
Observa el código en el editor. Este es el enfoque favorito de Python.

Nota:

 La palabra reservada  try  comienza con un bloque de código el cual puede o


no estar funcionando correctamente.
 Después, Python intenta realizar la acción arriesgada: si falla, se genera una
excepción y Python comienza a buscar una solución.
 La palabra reservada  except  comienza con un bloque de código que
será ejecutado si algo dentro del bloque  try  sale mal - si se genera una
excepción dentro del bloque anterior  try , fallará aquí, entonces el código
ubicado después de la palabra clave except debería proporcionar una reacción
adecuada a la excepción planteada.
 Se regresa al nivel de anidación anterior, es decir, se termina la sección try-
except.

Ejecute el código y prueba su comportamiento.

Resumamos esto:

try:
:
:
except:
:
:

 En el primer paso, Python intenta realizar todas las instrucciones colocadas


entre las instrucciones  try:  y  except: .
 Si no hay ningún problema con la ejecución y todas las instrucciones se realizan
con éxito, la ejecución salta al punto después de la última línea del
bloque  except:  , y la ejecución del bloque se considera completa.
 Si algo sale mal dentro del bloque  try:  o  except: , la ejecución salta
inmediatamente fuera del bloque y entra en la primera instrucción ubicada
después de la palabra reservada  except:  : esto significa que algunas de las
instrucciones del bloque pueden ser silenciosamente omitidas.
Excepciones: continuación
Observa el código en el editor. Te ayudará a comprender este mecanismo.

Esta es la salida que produce:

Oh cielos, algo salio mal...

Nota: la instrucción  print("2")  se perdió en el proceso.


Excepciones: continuación
Este enfoque tiene una desventaja importante: si existe la posibilidad de que más de
una excepción se salte a un apartado  except: , puedes tener problemas para
descubrir lo que realmente sucedió.

Al igual que en el código en el editor. Ejecútalo y ve lo qué pasa.

El mensaje:  Oh cielos, algo salio mal...  que aparece en la consola no dice nada
acerca de la razón, mientras que hay dos posibles causas de la excepción:

 Datos no enteros fueron ingresados por el usuario.


 Un valor entero igual a  0  fue asignado a la variable  x .

Técnicamente, hay dos formas de resolver el problema:

 Construir dos bloques consecutivos try-except, uno por cada posible motivo


de excepción (fácil, pero provocará un crecimiento desfavorable del código).
 Emplear una variante más avanzada de la instrucción.

Se parece a esto:

try:
:
except exc1:
:
except exc2:
:
except:
:

Así es como funciona:

 Si el  try  lanza la excepción  exc1 , esta será manejada por el bloque  except
exc1: .
 De la misma manera, si el  try  lanza la excepción  exc2 , esta será manejada por
el bloque  except exc2: .
 Si el  try  lanza cualquier otra excepción, será manejado por el bloque sin
nombre  except .
Pasemos a la siguiente parte del curso y veámoslo en acción.

Excepciones: continuación
Mira el código en el editor. Nuestra solucion esta ahí.

El código, cuando se ejecute, producirá una de las siguientes cuatro variantes de


salida:

 Si se ingresa un valor entero válido distinto de cero (por ejemplo,  5 ) dirá:

0.2
 FIN.
 Si se ingresa  0 , dirá:

No puedes dividir entre cero, lo siento.


 FIN.
 Si se ingresa cualquier cadena no entera, verás:

Debes ingresar un valor entero.


 FIN.
 (Localmente en tu máquina) si presionas Ctrl-C mientras el programa está
esperando la entrada del usuario (provocará una excepción
denominada KeyboardInterrupt), el programa dirá:

Oh cielos, algo salio mal...


 FIN.
Excepciones: continuación
No olvides que:

 Los bloques  except  son analizados en el mismo orden en que aparecen en el


código.
 No debes usar más de un bloque de excepción con el mismo nombre.
 El número de diferentes bloques  except  es arbitrario, la única condición es
que si se emplea el  try , debes poner al menos un  except  (nombrado o no)
después de el.
 La palabra reservada  except  no debe ser empleada sin que le preceda un  try .
 Si uno de los bloques  except  es ejecutado, ningún otro lo será.
 Si ninguno de los bloques  except  especificados coincide con la excepción
planteada, la excepción permanece sin manejar (lo discutiremos pronto).
 Si un  except  sin nombre existe, tiene que especificarse como el último.
try:
:
except exc1:
:
except exc2:
:
except:
:

Continuemos ahora con los experimentos.

Observa el código en el editor. Hemos modificado el programa anterior, hemos


eliminado el bloque  ZeroDivisionError .

¿Qué sucede ahora si el usuario ingresa un  0  como entrada?

Como no existe un bloque declarado para la división entre cero, la excepción cae


dentro del bloque general (sin nombre): esto significa que en este caso, el programa
dirá:

Oh cielos, algo salio mal...


FIN.
Excepciones: continuación
Echemos a perder el código una vez más.

Observa el programa en el editor. Esta vez, hemos eliminado el bloque sin nombre.

El usuario ingresa nuevamente un  0 , y:

 La excepción no será manejada por  ValueError  - no tiene nada que ver con
ello.
 Como no hay otro bloque, deberías ver este mensaje:

Traceback (most recent call last):


 File "exc.py", line 3, in
 y = 1 / x
 ZeroDivisionError: division by zero

Has aprendido mucho sobre el manejo de excepciones en Python. En la siguiente


sección, nos centraremos en las excepciones integradas de Python y sus jerarquías.
Excepciones
Python 3 define 63 excepciones integradas, y todos ellos forman
una jerarquía en forma de árbol, aunque el árbol es un poco extraño ya que
su raíz se encuentra en la parte superior.

Algunas de las excepciones integradas son más generales (incluyen otras


excepciones) mientras que otras son completamente concretas (solo se
representan a sí mismas). Podemos decir que cuanto más cerca de la raíz se
encuentra una excepción, más general (abstracta) es. A su vez, las
excepciones ubicadas en los extremos del árbol (podemos llamarlas hojas) son
concretas.

Echa un vistazo a la figura:

Muestra una pequeña sección del árbol completo de excepciones. Comencemos


examinando el árbol desde la hoja ZeroDivisionError.

Nota:

 ZeroDivisionError es un caso especial de una clase de excepción más


general llamada ArithmeticError.
 ArithmeticError es un caso especial de una clase de excepción más
general llamada solo Exception.
 Exception es un caso especial de una clase más general
llamada BaseException.
Podemos describirlo de la siguiente manera (observa la dirección de las flechas;
siempre apuntan a la entidad más general):

BaseException

Exception

ArithmeticError

ZeroDivisionError

Te mostraremos cómo funciona esta generalización. Comencemos con un


código realmente simple.

Excepciones: continuación
Observa el código en el editor. Es un ejemplo simple para comenzar. Ejecutalo.

La salida que esperamos ver se ve así:

Uuuppsss...
FIN.

Ahora observa el código a continuación:

try:
y = 1 / 0
except ArithmeticError:
print("Uuuppsss...")

print("FIN.")

Algo ha cambiado: hemos reemplazado  ZeroDivisionError  con  ArithmeticError .

Ya se sabe que  ArithmeticError  es una clase general que incluye (entre otras) la
excepción  ZeroDivisionError .

Por lo tanto, la salida del código permanece sin cambios. Pruébalo.

Esto también significa que reemplazar el nombre de la excepción ya sea


con  Exception  o  BaseException  no cambiará el comportamiento del programa.

Vamos a resumir:

 Cada excepción cae en la primer coincidencia.


 La coincidencia correspondiente no tiene que especificar exactamente la
misma excepción, es suficiente que la excepción sea mas general (mas
abstracta) que la lanzada.
Excepciones: continuación
Mira el código en el editor. ¿Qué pasará aquí?

La primera coincidencia es la que contiene  ZeroDivisionError . Significa que la


consola mostrará:

¡División entre Cero!


FIN.

¿Cambiará algo si intercambiamos los dos  except ? Justo como aquí abajo:

try:
y = 1 / 0
except ArithmeticError:
print("¡Problema aritmético!")
except ZeroDivisionError:
print("¡División entre Cero!")

print("FIN.")

El cambio es radical: la salida del código es ahora:

¡Problema aritmético!
FIN.

¿Por qué, si la excepción planteada es la misma que antes?

La excepción es la misma, pero la excepción más general ahora aparece primero:


también capturará todas las divisiones entre cero. También significa que no hay
posibilidad de que alguna excepción llegue a ZeroDivisionError. Ahora es
completamente inalcanzable.

Recuerda:

 ¡El orden de las excepciones importa!


 No pongas excepciones más generales antes que otras más concretas.
 Esto hará que el último sea inalcanzable e inútil.
 Además, hará que el código sea desordenado e inconsistente.
 Python no generará ningún mensaje de error con respecto a este problema.

Excepciones: continuación
Si deseas manejar dos o mas excepciones de la misma manera, puedes usar la
siguiente sintaxis:

try:
:
except (exc1, exc2):
:

Simplemente tienes que poner todos los nombres de excepción empleados en una
lista separada por comas y no olvidar los paréntesis.

Si una excepción se genera dentro de una función, puede ser manejada:

 Dentro de la función.
 Fuera de la función.

Comencemos con la primera variante: observa el código en el editor.

La excepción ZeroDivisionError (la cual es un caso concreto de la


clase ArithmeticError) es lanzada dentro de la función  badfun() , y la función en sí
misma se encarga de su caso.

La salida del programa es:

¡Problema aritmético!
FIN.

También es posible dejar que la excepción se propague fuera de la función.


Probémoslo ahora.

Observa el código a continuación:


def badFun(n):
return 1 / n

try:
badFun(0)
except ArithmeticError:
print("¿Que pasó? ¡Se lanzo una excepción!")

print("FIN.")

El problema tiene que ser resuelto por el invocador (o por el invocador del invocador, y
así sucesivamente.).

La salida del programa es:

¿Que pasó? ¡Se lanzo una excepción!


FIN.

Nota: la excepción planteada puede cruzar la función y los límites del módulo, y
viajar a través de la cadena de invocación buscando una cláusula  except  capaz de
manejarla.

Si no existe tal cláusula, la excepción no se controla y Python resuelve el problema de


la manera estándar - terminando el código y emitiendo un mensaje de
diagnóstico.

Ahora vamos a suspender esta discusión, ya que queremos presentarte una nueva
instrucción de Python.
Excepciones: continuación
La instrucción  raise  genera la excepción especificada denominada  exc  como si fuese
generada de manera natural:

raise exc

Nota:  raise  es una palabra reservada.

La instrucción permite:

 Simular excepciones reales (por ejemplo, para probar tu estrategia de


manejo de excepciones).
 Parcialmente manejar una excepción y hacer que otra parte del código sea
responsable de completar el manejo.

Observa el código en el editor. Así es como puedes usarlo en la práctica.

La salida del programa permanece sin cambios.

De esta manera, puedes probar tu rutina de manejo de excepciones sin forzar al


código a hacer cosas incorrectas.
Excepciones: continuación
La instrucción  raise  también se puede utilizar de la siguiente manera (toma en
cuenta la ausencia del nombre de la excepción):

raise

Existe una seria restricción: esta variante de la instrucción  raise  puede ser
utilizada solamente dentro de la rama  except ; usarla en cualquier otro contexto
causa un error.

La instrucción volverá a generar la misma excepción que se maneja actualmente.

Gracias a esto, puedes distribuir el manejo de excepciones entre diferentes partes del
código.

Observa el código en el editor. Ejecútalo, lo veremos en acción.

La excepción ZeroDivisionError es generada dos veces:

 Primero, dentro del  try  debido a que se intentó realizar una división entre
cero.
 Segundo, dentro de la parte  except  por la instrucción  raise .

En efecto, la salida del código es:

¡Lo hice otra vez!


¡Ya veo!
FIN.
Excepciones: continuación
Ahora es un buen momento para mostrarte otra instrucción de Python,
llamada  assert  (afirmar). Esta es una palabra reservada.

assert expresión

¿Como funciona?

 Evalúa la expresión.
 Si la expresión se evalúa como  True (verdadero) , o un valor numérico
distinto de cero, o una cadena no vacía, o cualquier otro valor diferente
de  None , no hará nada más.
 De lo contrario, automáticamente e inmediatamente genera una excepción
llamada AssertionError (en este caso, decimos que la afirmación ha fallado).

¿Cómo puede ser utilizada?

 Puedes ponerlo en la parte del código donde quieras estar absolutamente a


salvo de datos incorrectos, y donde no estés absolutamente seguro de que
los datos hayan sido examinados cuidadosamente antes (por ejemplo, dentro
de una función utilizada por otra persona).
 El generar una excepción AssertionError asegura que tu código no
produzca resultados no válidos y muestra claramente la naturaleza de la falla.
 Las aserciones no reemplazan las excepciones ni validan los datos, son
suplementos.

Si las excepciones y la validación de datos son como conducir con cuidado, la aserción
puede desempeñar el papel de una bolsa de aire.

Veamos a la instrucción  assert  en acción. Mira el código en el editor. Ejecutarlo.

El programa se ejecuta sin problemas si se ingresa un valor numérico válido mayor o


igual a cero; de lo contrario, se detiene y emite el siguiente mensaje:

Traceback (most recent call last):


File ".main.py", line 4, in
assert x >= 0.0
AssertionError
Excepciones integradas
Te mostraremos una breve lista de las excepciones más útiles. Si bien puede
sonar extraño llamar "útil" a una cosa o un fenómeno que es un signo visible de
una falla o retroceso, como sabes, errar es humano y si algo puede salir mal,
saldrá mal.

Las excepciones son tan rutinarias y normales como cualquier otro aspecto de
la vida de un programador.

Para cada excepción, te mostraremos:

 Su nombre.
 Su ubicación en el árbol de excepciones.
 Una breve descripción.
 Un fragmento de código conciso que muestre las circunstancias en las
que se puede generar la excepción.

Hay muchas otras excepciones para explorar: simplemente no tenemos el


espacio para revisarlas todas aquí.

ArithmeticError
Ubicación:

BaseException ← Exception ← ArithmeticError

Descripción:

Una excepción abstracta que incluye todas las excepciones causadas por
operaciones aritméticas como división cero o dominio inválido de un
argumento.

AssertionError
Ubicación:

BaseException ← Exception ← AssertionError

Descripción:

Una excepción concreta generada por la instrucción de aserción cuando su


argumento se evalúa como  False  (falso),  None  (ninguno),  0 , o una cadena
vacía.

Código :

from math import tan, radians

angle = int(input('Ingresa el angulo entero en grados: '))

# debemos estar seguros de ese angulo != 90 + k * 180


assert angle % 180 != 90

print(tan(radians(angle)))

BaseException
Ubicación:

BaseException

Descripción:

La excepción más general (abstracta) de todas las excepciones de Python:


todas las demás excepciones se incluyen en esta; se puede decir que las
siguientes dos excepciones son equivalentes:  except:  y  except
BaseException: .

IndexError
Ubicación:

BaseException ← Exception ← LookupError ← IndexError

Descripción:

Una excepción concreta que surge cuando se intenta acceder al elemento de


una secuencia inexistente (por ejemplo, el elemento de una lista).

Código:

# el codigo muestra una forma extravagante

# de dejar el bucle

lista = [1, 2, 3, 4, 5]

ix = 0

doit = True

while doit:

try:

print(lista[ix])

ix += 1

except IndexError:

doit = False

print('Listo')
KeyboardInterrupt
Ubicación:

BaseException ← KeyboardInterrupt

Descripción:

Una excepción concreta que surge cuando el usuario usa un atajo de teclado
diseñado para terminar la ejecución de un programa (Ctrl-C en la mayoría de los
Sistemas Operativos); si manejar esta excepción no conduce a la terminación
del programa, el programa continúa su ejecución. Nota: esta excepción no se
deriva de la clase Exception. Ejecuta el programa en IDLE.

Código:

# este código no puede ser terminado


# presionando Ctrl-C
from time import sleep
seconds = 0
while True:
try:
print(seconds)
seconds += 1
sleep(1)
except KeyboardInterrupt:
print("¡No hagas eso!")

LookupError
Ubicación:

BaseException ← Exception ← LookupError

Descripción:

Una excepción abstracta que incluye todas las excepciones causadas por
errores resultantes de referencias no válidas a diferentes colecciones (listas,
diccionarios, tuplas, etc.).

MemoryError
Ubicación:

BaseException ← Exception ← MemoryError


Descripción:

Se produce una excepción concreta cuando no se puede completar una


operación debido a la falta de memoria libre.

Código:

# este código causa la excepción MemoryError


# advertencia: ejecutar este código puede ser crucial
# para tu sistema operativo
# ¡no lo ejecutes en entornos de producción!

string = 'x'
try:
while True:
string = string + string
print(len(string))
except MemoryError:
print('¡Esto no es gracioso!')

OverflowError
Ubicación:

BaseException ← Exception ← ArithmeticError ← OverflowError

Descripción:

Una excepción concreta que surge cuando una operación produce un número
demasiado grande para ser almacenado con éxito.

Código:

# el código imprime los valores subsequentes

# de exp(k), k = 1, 2, 4, 8, 16, ...

from math import exp


ex = 1
try:
while True:
print(exp(ex))
ex *= 2
except OverflowError:
print('El número es demasiado grande.')
ImportError
Ubicación: BaseException ← Exception ← StandardError ← ImportError

Descripción:

Se produce una excepción concreta cuando falla una operación de importación.

Código:

# una de estas importaciones fallará, ¿cuál será?


try:
import math
import time
import abracadabra
except:
print('Una de sus importaciones ha fallado.')

KeyError
Ubicación: BaseException ← Exception ← LookupError ← KeyError

Descripción:

Una excepción concreta que surge cuando intentas acceder al elemento


inexistente de una colección (por ejemplo, el elemento de un diccionario).

Código:

# como abusar del diccionario


# y cómo lidiar con ello
dict = { 'a' : 'b', 'b' : 'c', 'c' : 'd' }
ch = 'a'
try:
while True:
ch = dict[ch]
print(ch)
except KeyError:
print('No existe tal clave:', ch)

Hemos terminado con excepciones por ahora, pero volverán cuando discutamos
la programación orientada a objetos en Python. Puedes usarlos para proteger tu
código de accidentes graves, pero también tienes que aprender a sumergirte en
ellos, explorando la información que llevan. De hecho, las excepciones son
objetos; sin embargo, no podemos decirle nada sobre este aspecto hasta que te
presentemos clases, objetos y similares.
Por el momento, si deseas obtener más información sobre las excepciones por
tu cuenta, consulta la Biblioteca estándar de Python
en https://docs.python.org/3.6/library/exceptions.html.

Cómo las computadoras entienden los caracteres


individuales
Has escrito algunos programas interesantes desde que comenzó este curso,
pero todos ellos han procesado solo un tipo de datos: los números. Como sabes
(puedes ver esto en todas partes), muchos datos de la computadora no son
números: nombres, apellidos, direcciones, títulos, poemas, documentos
científicos, correos electrónicos, sentencias judiciales, confesiones de amor y
mucho, mucho más.

Todos estos datos deben ser almacenados, ingresados, emitidos, buscados y


transformados por computadoras como cualquier otro dato, sin importar si son
caracteres únicos o enciclopedias de múltiples volúmenes.

¿Como es posible?

¿Cómo puedes hacerlo en Python? Esto es lo que discutiremos ahora.


Comencemos con cómo las computadoras entienden los caracteres
individuales.

Las computadoras almacenan los caracteres como números. Cada


carácter utilizado por una computadora corresponde a un número único, y
viceversa. Esta asignación debe incluir más caracteres de los que podrías
esperar. Muchos de ellos son invisibles para los humanos, pero esenciales para
las computadoras.

Algunos de estos caracteres se llaman espacios en blanco, mientras que otros


se nombran caracteres de control, porque su propósito es controlar
dispositivos de entrada / salida.

Un ejemplo de un espacio en blanco que es completamente invisible a simple


vista es un código especial, o un par de códigos (diferentes sistemas operativos
pueden tratar este asunto de manera diferente), que se utilizan para marcar el
final de las líneas dentro de los archivos de texto.

Las personas no ven este signo (o estos signos), pero pueden observar el efecto
de su aplicación donde ven un salto de línea.

Podemos crear prácticamente cualquier cantidad de asignaciones de números


con caracteres, pero la vida en un mundo en el que cada tipo de computadora
utiliza una codificación de caracteres diferentes no sería muy conveniente. Este
sistema ha llevado a la necesidad de introducir un estándar universal y
ampliamente aceptado, implementado por (casi) todas las computadoras y
sistemas operativos en todo el mundo.

El denominado ASCII (por sus siglas en íngles American Standard Code for


Information Interchange). El Código Estándar Americano para Intercambio de
Información es el más utilizado, y es posible suponer que casi todos los
dispositivos modernos (como computadoras, impresoras, teléfonos móviles,
tabletas, etc.) usan este código.

El código proporciona espacio para 256 caracteres diferentes, pero solo nos


interesan los primeros 128. Si deseas ver cómo se construye el código, mira la
tabla a continuación. Haz clic en la tabla para ampliarla. Mírala
cuidadosamente: hay algunos datos interesantes. Observa el código del
caracter más común: el espacio. El cual es el 32.
Ahora verifica el código de la letra minúscula a. El cual es 97. Ahora encuentra
la A mayúscula. Su codigo es 65. Ahora calcula la diferencia entre el código de
la a y la A. Es igual a 32. Ese es el códgo del espacio. Interesante, ¿no es así?

También ten en cuenta que las letras están ordenadas en el mismo orden que
en el alfabeto latino.
I18N
Ahora, el alfabeto latino no es suficiente para toda la humanidad. Los usuarios
de ese alfabeto son minoría. Era necesario idear algo más flexible y capaz que
ASCII, algo capaz de hacer que todo el software del mundo sea susceptible
de internacionalización, porque diferentes idiomas usan alfabetos
completamente diferentes, y a veces estos alfabetos no son tan simples como
el latino.

La palabra internacionalización se acorta comúnmente a I18N.

¿Por qué? Observa con cuidado, hay una I al inicio de la palabra, a continuación
hay 18 letras diferentes, y una N al final. A pesar del origen ligeramente
humorístico, el término se utiliza oficialmente en muchos documentos y
normas. El software I18N es un estándar en los tiempos actuales. Cada
programa tiene que ser escrito de una manera que permita su uso en todo el
mundo, entre diferentes culturas, idiomas y alfabetos. El código ASCII emplea
ocho bits para cada signo. Ocho bits significan 256 caracteres diferentes. Los
primeros 128 se usan para el alfabeto latino estándar (tanto en mayúsculas
como en minúsculas). ¿Es posible colocar todos los otros caracteres utilizados
en todo el mundo a los 128 lugares restantes?

No, no lo es.

Puntos de código y páginas de códigos


Necesitamos un nuevo término: un punto de código. Un punto de código es
un numero que compone un caracter. Por ejemplo, 32 es un punto de
código que compone un espacio en codificación ASCII. Podemos decir que el
código ASCII estándar consta de 128 puntos de código. Como el ASCII estándar
ocupa 128 de 256 puntos de código posibles, solo puedes hacer uso de los 128
restantes. No es suficiente para todos los idiomas posibles, pero puede ser
suficiente para un idioma o para un pequeño grupo de idiomas similares. ¿Se
puede establecer la mitad superior de los puntos de código de manera
diferente para diferentes idiomas? Si, por supuesto. A tal concepto se le
denomina una página de códigos. Una página de códigos es un estándar
para usar los 128 puntos de código superiores para almacenar
caracteres específicos. Por ejemplo, hay diferentes páginas de códigos para
Europa Occidental y Europa del Este, alfabetos cirílicos y griegos, idiomas árabe
y hebreo, etc.

Esto significa que el mismo punto de código puede formar diferentes caracteres
cuando se usa en diferentes páginas de códigos.

Por ejemplo, el punto de código 200 forma una Č (una letra usada por algunas
lenguas eslavas) cuando lo utiliza la página de códigos ISO/IEC 8859-2, pero
forma un Ш (una letra cirílica) cuando es usado por la página de códigos
ISO/IEC 8859-5.

En consecuencia, para determinar el significado de un punto de código


específico, debes conocer la página de códigos de destino.

En otras palabras, los puntos de código derivados del código de páginas son
ambiguos

Unicode
Las páginas de códigos ayudaron a la industria informática a resolver
problemas de I18N durante algún tiempo, pero pronto resultó que no serían una
solución permanente.

El concepto que resolvió el problema a largo plazo fue el Unicode.

Unicode asigna caracteres únicos (letras, guiones, ideogramas, etc.) a


más de un millón de puntos de código. Los primeros 128 puntos de código
Unicode son idénticos a ASCII, y los primeros 256 puntos de código Unicode son
idénticos a la página de códigos ISO / IEC 8859-1 (una página de códigos
diseñada para idiomas de Europa occidental).

UCS-4
El estándar Unicode no dice nada sobre cómo codificar y almacenar los
caracteres en la memoria y los archivos. Solo nombra todos los caracteres
disponibles y los asigna a planos (un grupo de caracteres de origen, aplicación
o naturaleza similares).

Existe más de un estándar que describe las técnicas utilizadas para


implementar Unicode en computadoras y sistemas de almacenamiento
informáticos reales. El más general de ellos es UCS-4.

El nombre viene de Universal Character Set (Conjunto de Caracteres


Universales).
UCS-4 emplea 32 bits (cuatro bytes) para almacenar cada caracter, y el
código es solo el número único de los puntos de código Unicode. Un archivo que
contiene texto codificado UCS-4 puede comenzar con un BOM (byte order mark
- marca de orden de bytes), una combinación no imprimible de bits que anuncia
la naturaleza del contenido del archivo. Algunas utilidades pueden requerirlo.

Como puedes ver, UCS-4 es un estándar bastante derrochador: aumenta el


tamaño de un texto cuatro veces en comparación con el estándar ASCII.
Afortunadamente, hay formas más inteligentes de codificar textos Unicode.

UTF-8
Uno de los más utilizados es UTF-8.

El nombre se deriva de Unicode Transformation Format (Formato de


Transformación Unicode).

El concepto es muy inteligente. UTF-8 emplea tantos bits para cada uno de


los puntos de código como realmente necesita para representarlos.

Por ejemplo:

 Todos los caracteres latinos (y todos los caracteres ASCII estándar)


ocupan ocho bits.
 Los caracteres no latinos ocupan 16 bits.
 Los ideógrafos CJK (China-Japón-Corea) ocupan 24 bits.
Debido a las características del método utilizado por UTF-8 para almacenar los
puntos de código, no es necesario usar el BOM, pero algunas de las
herramientas lo buscan al leer el archivo, y muchos editores lo configuran
durante el guardado.

Python 3 es totalmente compatible con Unicode y UTF-8:

 Puedes usar caracteres codificados Unicode / UTF-8 para nombrar


variables y otras entidades.
 Puedes usarlos durante todas las entradas y salidas.

Esto significa que Python3 está completamente Internacionalizado.

Cadenas - una breve reseña


Hagamos un breve repaso de la naturaleza de las cadenas en Python.

En primer lugar, las cadenas de Python (o simplemente cadenas, ya que no vamos a


discutir las cadenas de ningún otro lenguaje) son secuencias inmutables.

Es muy importante tener en cuenta esto, porque significa que debes esperar un
comportamiento familiar.

Por ejemplo, la función  len()  empleada por cadenas devuelve el número de


caracteres que contiene el argumento.

Observa el Ejemplo 1 en el editor. La salida del código es  3 .

Cualquier cadena puede estar vacía. Si es el caso, su longitud es  0  como en el Ejemplo
2.

No olvides que la diagonal invertida ( \ ) empleada como un caracter de escape, no esta


incluida en la longitud total de la cadena.

El código en el Ejemplo 3, da como salida un  3 .

Ejecuta los tres ejemplos de código y verificalo.


Cadenas multilínea
Ahora es un muy buen momento para mostrarte otra forma de especificar cadenas
dentro del código fuente de Python. Ten en cuenta que la sintaxis que ya conoces no
te permitirá usar una cadena que ocupe más de una línea de texto.

Por esta razón, el código aquí es erróneo:

multiLinea = 'Linea #1

Linea #2'

print(len(multiLinea))

Afortunadamente, para este tipo de cadenas, Python ofrece una sintaxis simple,
conveniente y separada. Observa el código en el editor. Así es comos se ve.

Como puedes ver, la cadena comienza con tres apóstrofes, no uno. El mismo


apóstrofe triplicado se usa para terminar la cadena. El número de líneas de texto
dentro de una cadena de este tipo es arbitrario.

La salida del código es  17 .

Cuenta los caracteres con cuidado. ¿Es este resultado correcto o no? Se ve bien a
primera vista, pero cuando cuentas los caracteres, no lo es. La Linea #1  contiene
ocho caracteres. Las dos líneas juntas contienen 16 caracteres. ¿Perdimos un caracter?
¿Dónde? ¿Cómo?

No, no lo hicimos.

El caracter que falta es simplemente invisible: es un espacio en blanco. Se


encuentra entre las dos líneas de texto. Se denota como:  \n . ¿Lo recuerdas? Es un
caracter especial (de control) utilizado para forzar un avance de línea. No puedes
verlo, pero cuenta. Las cadenas multilínea pueden ser delimitadas también
por comillas triples, como aqui:

multiLinea = """Linea #1

Linea #2"""

print(len(multiLinea))

Elije el método que sea más cómodo. Ambos funcionan igual.

Operaciones con Cadenas


Al igual que otros tipos de datos, las cadenas tienen su propio conjunto de
operaciones permitidas, aunque son bastante limitadas en comparación con los
números.

En general, las cadenas pueden ser:

 Concatenadas (unidas).
 Replicadas.

La primera operación la realiza el operador  +  (toma en cuenta que no es una adición o


suma) mientras que la segunda por el operador  *  (toma en cuenta de nuevo que no es
una multiplicación).

La capacidad de usar el mismo operador en tipos de datos completamente diferentes


(como números o cadenas) se llama overloading - sobrecarga (debido a que el
operador está sobrecargado con diferentes tareas).

Analiza el ejemplo:
 El operador  +  es empleado en dos o más cadenas y produce una nueva cadena
que contiene todos los caracteres de sus argumentos (nota: el orden es
relevante aquí, en contraste con su versión numérica, la cual es conmutativa).
 El operador  *  necesita una cadena y un número como argumentos; en este
caso, el orden no importa: puedes poner el número antes de la cadena, o
viceversa, el resultado será el mismo: una nueva cadena creada por la enésima
replicación de la cadena del argumento.

El fragmento de código produce el siguiente resultado:

ab
ba
aaaaa
bbbb

Nota: Los atajos de los operadores anteriores también son aplicables para las cadenas
( +=  y  *= ).

Operaciones con cadenas: ord()


Si deseas saber el valor del punto de código ASCII/UNICODE de un caracter
específico, puedes usar la función  ord()  (proveniente de ordinal).

La función necesita una cadena de un caracter como argumento - incumplir este


requisito provoca una excepción TypeError, y devuelve un número que representa el
punto de código del argumento.

Observa el código en el editor y ejecútalo. Las salida del fragmento de código es:

97

32

Ahora asigna diferentes valores a  ch1  y  ch2 , por ejemplo,  α  (letra griega alfa), y  ę  (una
letra en el alfabeto polaco); luego ejecuta el código y ve qué resultado produce. Realiza
tus propios experimentos.
Operaciones con cadenas: chr()
Si conoces el punto de código (número) y deseas obtener el carácter correspondiente,
puedes usar la función llamada  chr() .

La función toma un punto de código y devuelve su carácter.

Invocándolo con un argumento inválido (por ejemplo, un punto de código negativo o


inválido) provoca las excepciones ValueError o TypeError.

Ejecuta el código en el editor, su salida es la siguiente:

a
α

Nota:

 chr(ord(x)) == x
 ord(chr(x)) == x
De nuevo, realiza tus propios experimentos.

Cadenas como secuencias: indexación


Ya dijimos antes que las cadenas de Python son secuencias. Es hora de mostrarte lo
que significa realmente.

Las cadenas no son listas, pero pueden ser tratadas como tal en muchos casos.

Por ejemplo, si deseas acceder a cualquiera de los caracteres de una cadena, puedes
hacerlo usando indexación, al igual que en el ejemplo en el editor. Ejecuta el
programa.

Ten cuidado, no intentes pasar los límites de la cadena, ya que provocará una
excepción.

El resultado de ejemplo es:

s i l l y w a l k s
Por cierto, los índices negativos también se comportan como se esperaba. Comprueba
esto tú mismo.

Cadenas como secuencias: iterando


Iterar a través de las cadenas funciona también. Observa el siguiente ejemplo:

# Iterando a través de una cadena

exampleString = 'silly walks'

for ch in exampleString:

print(ch, end=' ')

print()

La salida es la misma que el ejemplo anterior, revisalo.

Rodajas o Rebanadas
Todo lo que sabes sobre rodajas o rebanadas es utilizable.

Hemos reunido algunos ejemplos que muestran cómo funcionan las rodajas en el
mundo de las cadenas. Mira el código en el editor, analizalo y ejecútalo.

No verás nada nuevo en el ejemplo, pero queremos que estés seguro de entender
todas las líneas del código.

La salida del código es:

bd

efg

abd
e

adf

beg

Ahora haz tus propios experimentos.

Los operadores in y not in
El operador  in  no debería sorprenderte cuando se aplica a cadenas,
simplemente comprueba si el argumento izquierdo (una cadena) se puede
encontrar en cualquier lugar dentro del argumento derecho (otra cadena).

El resultado es simplemente  True (Verdadero)  o  False (Falso) .

Observa el ejemplo en el editor. Así es como el operador  in  funciona.

El resultado de ejemplo es:

True
False
False
True
False

Como probablemente puedas deducir, el operador  not in  también es aplicable aquí.

Así es como funciona:

alfabeto = "abcdefghijklmnopqrstuvwxyz"

print("f" not in alfabeto)


print("F" not in alfabeto)
print("1" not in alfabeto)
print("ghi" not in alfabeto)
print("Xyz" not in alfabeto)

El resultado de ejemplo es:

False
True
True
False
True

Las cadenas de
Python son inmutables
También te hemos dicho que las cadenas de Python son inmutables. Esta es una
característica muy importante. ¿Qué significa?

Esto significa principalmente que la similitud de cadenas y listas es limitada. No todo lo


que puede hacerse con una lista puede hacerse con una cadena.

La primera diferencia importante no te permite usar la instrucción  del  para


eliminar cualquier cosa de una cadena.

El ejemplo siguiente no funcionará:

alfabeto = "abcdefghijklmnopqrstuvwxyz"
del alfabeto[0]

Lo único que puedes hacer con  del  y una cadena es eliminar la cadena como un
todo. Intenta hacerlo.

Las cadenas de Python no tienen el método  append()  - no se pueden expander de


ninguna manera.

El siguiente ejemplo es erróneo:

alfabeto = "abcdefghijklmnopqrstuvwxyz"

alfabeto.append("A")

Con la ausencia del método  append() , el método  insert()  también es ilegal:

alfabeto = "abcdefghijklmnopqrstuvwxyz"

alfabeto.insert(0, "A")

Operaciones con cadenas: continuación


No pienses que la inmutabilidad de una cadena limita tu capacidad de operar con
ellas.

La única consecuencia es que debes recordarlo e implementar tu código de una


manera ligeramente diferente: observa el código en el editor.

Esta forma de código es totalmente aceptable, funcionará sin doblar las reglas de
Python y traerá el alfabeto latino completo a tu pantalla:

abcdefghijklmnopqrstuvwxyz
Es posible que desees preguntar si el crear una nueva copia de una cadena cada
vez que se modifica su contenido empeora la efectividad del código.

Sí lo hace. Un poco. Sin embargo, no es un problema en absoluto.

Operaciones con cadenas: min()


Ahora que comprendes que las cadenas son secuencias, podemos mostrarte algunas
capacidades de secuencia menos obvias. Las presentaremos utilizando cadenas, pero
no olvides que las listas también pueden adoptar los mismos trucos.

Comenzaremos con la función llamada  min() .

Esta función encuentra el elemento mínimo de la secuencia pasada como


argumento. Existe una condición - la secuencia (cadena o lista) no puede estar vacía,
de lo contrario obtendrás una excepción ValueError.
El programa Ejemplo 1 da la siguiente salida:

Nota: Es una A mayúscula. ¿Por qué? Recuerda la tabla ASCII, ¿qué letras ocupan las
primeras posiciones, mayúsculas o minúsculas?

Hemos preparado dos ejemplos más para analizar: Ejemplos 2 y 3.

Como puedes ver, presentan más que solo cadenas. El resultado esperado se ve de la
siguiente manera:

[ ]

Nota: hemos utilizado corchetes para evitar que el espacio se pase por alto en tu
pantalla.

Operaciones con cadenas: max()


Del mismo modo, una función llamada  max()  encuentra el elemento máximo de la
secuencia.

Observa el Ejemplo 1 en el editor. La salida del programa es:

Nota: es una z minúscula.
Ahora veamos la función  max()  a los mismos datos del ejemplo anterior. Observa
los Ejemplos 2 y 3 en el editor.

La salida esperada es:

[¡]

Realiza tus propios experimentos.

Operaciones con cadenas: el método index()


El método  index()  (es un método, no una función) busca la secuencia desde el
principio, para encontrar el primer elemento del valor especificado en su
argumento.

Nota: el elemento buscado debe aparecer en la secuencia - su ausencia causará una
excepción ValueError.
El método devuelve el índice de la primera aparición del argumento (lo que significa
que el resultado más bajo posible es 0, mientras que el más alto es la longitud del
argumento decrementado por 1).

Por lo tanto, el ejemplo en la salida del editor es:

Operaciones con cadenas: la función list()


La función  list()  toma su argumento (una cadena) y crea una nueva lista que
contiene todos los caracteres de la cadena, uno por elemento de la lista.
Nota: no es estrictamente una función de cadenas -  list()  es capaz de crear una
nueva lista de muchas otras entidades (por ejemplo, de tuplas y diccionarios).

Observa el código de ejemplo en el editor.

La salida es:

['a', 'b', 'c', 'a', 'b', 'c']

Operaciones con cadenas: el método count()


El método  count()  cuenta todas las apariciones del elemento dentro de la
secuencia. La ausencia de tal elemento no causa ningún problema.

Observa el segundo ejemplo en el editor. ¿Puedes adivinar su salida?

Es:

Las cadenas de Python tienen un número significativo de métodos destinados


exclusivamente al procesamiento de caracteres. No esperes que trabajen con otras
colecciones. La lista completa se presenta
aquí: https://docs.python.org/3.4/library/stdtypes.html#string-methods.

Te mostraremos los que consideramos más útiles.

El método capitalize()
Veamos algunos métodos estándar de cadenas en Python. Vamos a analizarlos en
orden alfabético, cualquier orden tiene tanto desventajas como ventajas, por lo que la
elección puede ser aleatoria. El método  capitalize()  hace exactamente lo que dice
- crea una nueva cadena con los caracteres tomados de la cadena fuente, pero
intenta modificarlos de la siguiente manera:
 Si el primer caracter dentro de la cadena es una letra (nota: el primer
carácter es el elemento con un índice igual a 0, no es el primer caracter
visible), se convertirá a mayúsculas.
 Todas las letras restantes de la cadena se convertirán a minúsculas.

No olvides que:

 La cadena original (desde la cual se invoca el método) no se cambia de ninguna


manera (la inmutabilidad de una cadena debe obedecerse sin reservas).
 La cadena modificada (en mayúscula en este caso) se devuelve como resultado;
si no se usa de alguna manera (asígnala a una variable o pásala a una función /
método) desaparecerá sin dejar rastro.

Nota: los métodos no tienen que invocarse solo dentro de las variables. Se pueden
invocar directamente desde dentro de literales de cadena. Usaremos esa convención
regularmente: simplificará los ejemplos, ya que los aspectos más importantes no
desaparecerán entre asignaciones innecesarias.

Echa un vistazo al ejemplo en el editor. Ejecutalo.

Esto es lo que imprime:

Abcd

Prueba algunos ejemplos más avanzados y prueba su salida:

print("Alpha".capitalize())
print('ALPHA'.capitalize())
print(' Alpha'.capitalize())
print('123'.capitalize())
print("αβγδ".capitalize())

El método center()
La variante de un parámetro del método  center()  genera una copia de la cadena
original, tratando de centrarla dentro de un campo de un ancho especificado.

El centrado se realiza realmente al agregar algunos espacios antes y después de la


cadena.
No esperes que este método demuestre habilidades sofisticadas. Es bastante simple.

El ejemplo en el editor usa corchetes para mostrar claramente dónde comienza y


termina realmente la cadena centrada.

Su salida se ve de la siguiente manera:

[ alfa ]

Si la longitud del campo de destino es demasiado pequeña para ajustarse a la cadena,


se devuelve la cadena original.

Puedes ver el método  center()  en más ejemplos aquí:

print('[' + 'Beta'.center(2) + ']')

print('[' + 'Beta'.center(4) + ']')

print('[' + 'Beta'.center(6) + ']')

Ejecuta el código anterior y verifica qué salidas produce.

La variante de dos parámetros de  center()  hace uso del caracter del segundo
argumento, en lugar de un espacio. Analiza el siguiente ejemplo:

print('[' + 'gamma'.center(20, '*') + ']')

Es por eso que la salida ahora se ve así:

[*******gamma********]

Realiza más experimentos.

El método endswith()
El método  endswith()  comprueba si la cadena dada termina con el argumento
especificado y devuelve  True (verdadero)  o  False (falso) , dependiendo del
resultado.

Nota: la subcadena debe adherirse al último carácter de la cadena; no se puede ubicar


en algún lugar cerca del final de la cadena.

Observa el ejemplo en el editor, analizalo y ejecútalo. Produce:


si

Ahora deberías poder predecir la salida del fragmento de código a continuación:

t = "zeta"

print(t.endswith("a"))

print(t.endswith("A"))

print(t.endswith("et"))

print(t.endswith("eta"))

Ejecuta el código para verificar tus predicciones.

El método find()
El método  find()  es similar al método  index() , el cual ya conoces - busca una
subcadena y devuelve el índice de la primera aparición de esta subcadena, pero:

 Es más seguro, no genera un error para un argumento que contiene una


subcadena inexistente (devuelve  -1  en dicho caso).
 Funciona solo con cadenas - no intentes aplicarlo a ninguna otra secuencia.

Analiza el código en el editor. Así es como puedes usarlo.


El ejemplo imprime:

1
-1

Nota: no se debe de emplear  find()  si deseas verificar si un solo carácter aparece
dentro de una cadena - el operador  in  será significativamente más rápido.

Aquí hay otro ejemplo:

t = 'teta'
print(t.find('eta'))
print(t.find('et'))
print(t.find('te'))
print(t.find('ha'))

¿Puedes predecir la salida? Ejecútalo y verifica tus predicciones.

Si deseas realizar la búsqueda, no desde el principio de la cadena, sino desde


cualquier posición, puedes usar una variante de dos parámetros del
método  find() . Mira el ejemplo:

print('kappa'.find('a', 2))

El segundo argumento especifica el índice en el que se iniciará la búsqueda.

De las dos letras a, solo se encontrará la segunda. Ejecuta el código y verifica.

Se puede emplear el método  find()  para buscar todas las ocurrencias de la


subcadena, como aquí:

txt = """A variation of the ordinary lorem ipsum


text has been used in typesetting since the 1960s
or earlier, when it was popularized by advertisements
for Letraset transfer sheets. It was introduced to
the Information Age in the mid-1980s by the Aldus Corporation,
which employed it in graphics and word-processing templates
for its desktop publishing program PageMaker (from Wikipedia)"""

fnd = txt.find('the')
while fnd != -1:
print(fnd)
fnd = txt.find('the', fnd + 1)

El código imprime los índices de todas las ocurrencias del artículo the, y su salida se ve
así:

15
80
198
221
238

Existe también una mutación de tres parámetros del método  find()  - el tercer


argumento apunta al primer índice que no se tendrá en cuenta durante la
búsqueda (en realidad es el límite superior de la búsqueda).

Observa el ejemplo a continuación:

print('kappa'.find('a', 1, 4))
print('kappa'.find('a', 2, 4))

El segundo argumento especifica el índice en el que se iniciará la búsqueda (no tiene


que caber dentro de la cadena).

Por lo tanto, las salidas de ejemplo son:

1
-1

a no se puede encontrar dentro de los límites de búsqueda dados en el


segundo  print() .

El método isalnum()
El método sin parámetros llamado  isalnum()  comprueba si la cadena contiene
solo dígitos o caracteres alfabéticos (letras) y devuelve  True
(verdadero)  o  False (falso)  de acuerdo al resultado.

Observa el ejemplo en el editor y ejecútalo.


Nota: cualquier elemento de cadena que no sea un dígito o una letra hace que el
método regrese  False (falso) . Una cadena vacía también lo hace.

El resultado de ejemplo es:

True
True
True
False
False
False

Hay tres más ejemplos aquí:

t = 'Six lambdas'
print(t.isalnum())

t = 'ΑβΓδ'
print(t.isalnum())

t = '20E1'
print(t.isalnum())

Ejecútalos y verifica su salida.

Nota: la causa del primer resultado es un espacio, no es ni un dígito ni una letra.

El método isalpha()
El método  isalpha()  es más especializado, se interesa en letras solamente.

Observa el Ejemplo 1, su salida es:

True
False

El método isdigit()
Al contrario, el método  isdigit()  busca sólo dígitos - cualquier otra cosa
produce  False (falso)  como resultado.

Observa el Ejemplo 2, su salida es:

True

False

Realiza más experimentos.

El método islower()
El método  islower()  es una variante de  isalpha()  - solo acepta letras minúsculas.

Observa el Ejemplo 1 en el editor, genera:

False
True

El método isspace()
El método  isspace()  identifica espacios en blanco solamente - no tiene en cuenta
ningún otro caracter (el resultado es entonces  False ).

Observa el Ejemplo 2 en el editor, genera:

True

True

False

El método isupper()
El método  isupper()  es la versión en mayúscula de  islower()  - se concentra solo
en letras mayúsculas.

De nuevo, observa el Ejemplo 3 en el editor, genera:

False

False

True

El método join()
El método  join()  es algo complicado, así que déjanos guiarte paso a paso:
 Como su nombre lo indica, el método realiza una unión y espera un
argumento del tipo lista; se debe asegurar que todos los elementos de la lista
sean cadenas: de lo contrario, el método generará una excepción TypeError.
 Todos los elementos de la lista serán unidos en una sola cadena pero...
 ...la cadena desde la que se ha invocado el método será utilizada como
separador, puesta entre las cadenas.
 La cadena recién creada se devuelve como resultado.

Echa un vistazo al ejemplo en el editor. Vamos a analizarlo:

 El método  join()  se invoca desde una cadena que contiene una coma (la
cadena puede ser larga o puede estar vacía).
 El argumento del  join  es una lista que contiene tres cadenas.
 El método devuelve una nueva cadena.

Aquí está:

omicron,pi,rh

El método lower()
El método  lower()  genera una copia de una cadena, reemplaza todas las letras
mayúsculas con sus equivalentes en minúsculas, y devuelve la cadena como
resultado. Nuevamente, la cadena original permanece intacta.

Si la cadena no contiene caracteres en mayúscula, el método devuelve la cadena


original.

Nota: El método  lower()  no toma ningún parámetro.

La salida del ejemplo del editor es:

sigma=60

Como ya sabes, realiza tus propios experimentos.

El método lstrip()
El método sin parámetros  lstrip()  devuelve una cadena recién creada formada a
partir de la original eliminando todos los espacios en blanco iniciales.

Analiza el ejemplo en el editor.

Los corchetes no son parte del resultado, solo muestran los límites del resultado.

Las salida del ejemplo es:

[tau ]

El método con un parámetro  lstrip()  hace lo mismo que su versión sin


parámetros, pero elimina todos los caracteres incluidos en el argumento (una
cadena), no solo espacios en blanco:

print("www.cisco.com".lstrip("w."))

Aquí no se necesitan corchetes, ya que el resultado es el siguiente:

cisco.com

¿Puedes adivinar la salida del fragmento a continuación? Piensa cuidadosamente.


Ejecuta el código y verifica tus predicciones.

print("pythoninstitute.org".lstrip(".org"))

¿Sorprendido? Nuevamente, experimenta con tus propios ejemplos.

El método replace()
El método  replace()  con dos parámetros devuelve una copia de la cadena
original en la que todas las apariciones del primer argumento han sido
reemplazadas por el segundo argumento.

Analiza el código en el editor y ejecútalo.

El segundo argumento puede ser una cadena vacía (lo que hace es eliminar en lugar
de reemplazar), pero el primer argumento no puede estar vacío.

La salida del ejemplo es:

www.pyhoninstitute.org

Thare are it!

Apple

La variante del métdodo  replace()  con tres parámetros emplea un tercer


argumento (un número) para limitar el número de reemplazos.

Observa el código modificado a continuación:

print("This is it!".replace("is", "are", 1))

print("This is it!".replace("is", "are", 2))

¿Puedes adivinar su salida? Ejecuta el código y verifica tus conjeturas.

El método rfind()
Los métodos de uno, dos y tres parámetros denominados  rfind()  hacen casi lo
mismo que sus contrapartes (las que carecen del prefijo r), pero comienzan sus
búsquedas desde el final de la cadena, no el principio (de ahí el prefijo r, de reversa).

Echa un vistazo al código en el editor e intenta predecir su salida. Ejecuta el código


para verificar si tenías razón.

El método rstrip()
Dos variantes del método  rstrip()  hacen casi lo mismo que el método  lstrip ,
pero afecta el lado opuesto de la cadena. Mira el ejemplo en el editor. ¿Puedes
adivinar su salida? Ejecuta el código para verificar tus conjeturas.

Como de costumbre, te recomendamos experimentar con tus propios ejemplos.

El método split()
El método  split()  divide la cadena y crea una lista de todas las subcadenas
detectadas. El método asume que las subcadenas están delimitadas por espacios
en blanco - los espacios no participan en la operación y no se copian en la lista
resultante.

Si la cadena está vacía, la lista resultante también está vacía. Observa el código en el
editor. El ejemplo produce el siguiente resultado:

['phi', 'chi', 'psi']

Nota: la operación inversa se puede realizar por el método  join() .


El método startswith()
El método  startswith()  es un espejo del método  endswith()  - comprueba si una
cadena dada comienza con la subcadena especificada.

Mira el ejemplo en el editor. Este es el resultado:

False

True

El método strip()
El método  strip()  combina los efectos causados por  rstrip()  y  lstrip()  - crea
una nueva cadena que carece de todos los espacios en blanco iniciales y finales.

Observa el segundo ejemplo en el editor. Este es el resultado que devuelve:

[aleph]

Ahora realiza tus propios experimentos con los dos métodos.


El método swapcase()
El método  swapcase()  crea una nueva cadena intercambiando todas las letras
por mayúsculas o minúsculas dentro de la cadena original: los caracteres en
mayúscula se convierten en minúsculas y viceversa.

Todos los demás caracteres permanecen intactos.

Observa el primer ejemplo en el editor. ¿Puedes adivinar la salida?

yO SÉ QUE NO SÉ NADA.

El método title()
El método  title()  realiza una función algo similar cambia la primera letra de cada
palabra a mayúsculas, convirtiendo todas las demás a minúsculas.

Mira el segundo ejemplo en el editor. ¿Puedes adivinar su salida? Este es el resultado:

Yo Sé Que No Sé Nada. Parte 1.

El método upper()
Por último, pero no menos importante, el método  upper()  hace una copia de la
cadena de origen, reemplaza todas las letras minúsculas con sus equivalentes en
mayúsculas, y devuelve la cadena como resultado.

Mira el tercer ejemplo en el editor. Produce:

YO SÉ QUE NO SÉ NADA. PARTE 2.

¡Hurra! Hemos llegado al final de esta sección. ¿Te sorprende alguno de los métodos
de cadena que hemos discutido hasta ahora? Toma un par de minutos para revisarlos
y pasemos a la siguiente parte del curso, donde te mostraremos qué cosas podemos
hacer con las cadenas.
Comparando cadenas
Las cadenas en Python pueden ser comparadas usando el mismo conjunto de
operadores que se emplean con los números.

Eche un vistazo a estos operadores: también pueden comparar cadenas:

 ==
 !=
 >
 >=
 <
 <=

Existe un "pero": los resultados de tales comparaciones a veces pueden ser un poco
sorprendentes. No olvides que Python no es consciente (no puede ser de ninguna
manera) de problemas lingüísticos sutiles, simplemente compara valores de puntos
de código, caracter por caracter.

Los resultados que obtienen de una operación de este tipo a veces son sorprendentes.
Comencemos con los casos más simples.

Dos cadenas son iguales cuando consisten en los mismos caracteres en el mismo
orden. Del mismo modo, dos cadenas no son iguales cuando no consisten en los
mismos caracteres en el mismo orden.

Ambas comparaciones dan  True  (verdadero) como resultado:

'alfa' == 'alfa'
'alfa' != 'Alfa'

La relación final entre cadenas está determinada por comparar el primer caracter


diferente en ambas cadenas (ten en cuenta los puntos de código ASCII / UNICODE en
todo momento). Cuando se comparan dos cadenas de diferentes longitudes y la más
corta es idéntica a la más larga, la cadena más larga se considera mayor.

Justo como aquí:

'alfa' < 'alfabeto'

La relación es  True  (verdadera).

La comparación de cadenas siempre distingue entre mayúsculas y minúsculas (las


letras mayúsculas se consideran menores en comparación con las minúsculas).

La expresión es  True  (verdadera):

'beta' > 'Beta'


Comparando cadenas: continuación
Aún si una cadena contiene solo dígitos, todavía no es un número. Se interpreta
como lo que es, como cualquier otra cadena regular, y su aspecto numérico (potencial)
no se toma en cuenta, en ninguna manera.

Mira los ejemplos:

'10' == '010'
'10' > '010'
'10' > '8'
'20' < '8'
'20' < '80'

Producen los siguientes resultados:

False
True
False
True
True

Comparar cadenas contra números generalmente es una mala idea.

Las únicas comparaciones que puede realizar con impunidad son aquellas
simbolizadas por los operadores  ==  y  != . El primero siempre devuelve  False ,
mientras que el segundo siempre devuelve  True .

El uso de cualquiera de los operadores de comparación restantes generará una


excepción TypeError.

Vamos a verlo:

'10' == 10
'10' != 10
'10' == 1
'10' != 1
'10' > 10

Los resultados en este caso son:

False
True
False
True
TypeError exception
Ordenamiento
La comparación está estrechamente relacionada con el ordenamiento (o más bien, el
ordenamiento es, de hecho, un caso muy sofisticado de comparación). Esta es una
buena oportunidad para mostrar dos formas posibles de ordenar listas que
contienen cadenas. Dicha operación es muy común en el mundo real: cada vez que
ves una lista de nombres, productos, títulos o ciudades, esperas que este ordenada.

Supongamos que deseas ordenar la siguiente lista:

greek = ['omega', 'alfa', 'pi', 'gama']

En general, Python ofrece dos formas diferentes de ordenar las listas.

El primero se implementa con una función llamada  sorted() .

La función toma un argumento (una lista) y devuelve una nueva lista, con los
elementos ordenados del argumento. (Nota: esta descripción está un poco
simplificada en comparación con la implementación real; lo discutiremos más
adelante). La lista original permanece intacta.

Mira el código en el editor y ejecútalo. El fragmento produce el siguiente resultado:

['omega', 'alfa', 'pi', 'gama']


['alfa', 'gama', 'omega', 'pi']

El segundo método afecta a la lista misma - no se crea una nueva lista. El
ordenamiento se realiza por el método denominado  sort() .

El resultado no ha cambiado:

['omega', 'alfa', 'pi', 'gama']


['alfa', 'gama', 'omega', 'pi']

Si necesitas un orden que no sea descendente, debes convencer a la función o método


de cambiar su comportamiento predeterminado. Lo discutiremos pronto.
Cadenas contra números
Hay dos cuestiones adicionales que deberían discutirse aquí: cómo convertir un
número (un entero o un flotante) en una cadena, y viceversa. Puede ser necesario
realizar tal transformación. Además, es una forma rutinaria de procesar datos de
entrada o salida. La conversión de cadena a número es simple, ya que siempre es
posible. Se realiza mediante una función llamada  str() .

Justo como aquí:

itg = 13
flt = 1.3
si = str(itg)
sf = str(flt)

print(si + ' ' + sf)

La salida del código es:


13 1.3

La transformación inversa solo es posible cuando la cadena representa un número


válido. Si no se cumple la condición, espera una excepción ValueError. Emplea la
función  int()  si deseas obtener un entero, y  float()  si necesitas un valor punto
flotante.

Justo como aquí:

si = '13'
sf = '1.3'
itg = int(si)
flt = float(sf)

print(itg + flt)

Esto es lo que verás en la consola:


14.3

En la siguiente sección, te mostraremos algunos programas simples que procesan


cadenas.

Los conceptos básicos del enfoque orientado a


objetos
Demos un paso fuera de la programación y las computadoras, y analicemos
temas de programación orientada a objetos.

Casi todos los programas y técnicas que has utilizado hasta ahora pertenecen al
estilo de programación procedimental. Es cierto que has utilizado algunos
objetos incorporados, pero cuando nos referimos a ellos, se mencionan lo
mínimo posible.

La programación procedimental fue el enfoque dominante para el desarrollo de


software durante décadas de TI, y todavía se usa en la actualidad. Además, no
va a desaparecer en el futuro, ya que funciona muy bien para proyectos
específicos (en general, no muy complejos y no grandes, pero existen muchas
excepciones a esa regla).

El enfoque orientado a objetos es bastante joven (mucho más joven que el


enfoque procedimental) y es particularmente útil cuando se aplica a proyectos
grandes y complejos llevados a cabo por grandes equipos formados por muchos
desarrolladores.

Este tipo de programación en un proyecto facilita muchas tareas importantes,


por ejemplo, dividir el proyecto en partes pequeñas e independientes y el
desarrollo independiente de diferentes elementos del proyecto.

Python es una herramienta universal para la programación


procedimental y orientada a objetos. Se puede utilizar con éxito en ambas.

Además, puedes crear muchas aplicaciones útiles, incluso si no se sabe nada


sobre clases y objetos, pero debes tener en cuenta que algunos de los
problemas (por ejemplo, el manejo de la interfaz gráfica de usuario) puede
requerir un enfoque estricto de objetos.

Afortunadamente, la programación orientada a objetos es relativamente simple.


Enfoque procedimental versus el enfoque orientado
a objetos
En el enfoque procedimental, es posible distinguir dos mundos diferentes y
completamente separados: el mundo de los datos y el mundo del código.
El mundo de los datos está poblado con variables de diferentes tipos, mientras
que el mundo del código está habitado por códigos agrupados en módulos y
funciones. Las funciones pueden usar datos, pero no al revés. Además, las
funciones pueden abusar de los datos, es decir, usar el valor de manera no
autorizada (por ejemplo, cuando la función seno recibe el saldo de una cuenta
bancaria como parámetro).

Los datos no pueden usar funciones. ¿Pero es esto completamente cierto? ¿Hay
algunos tipos especiales de datos que pueden usar funciones?

Sí, los hay, los llamados métodos. Estas son funciones que se invocan desde
dentro de los datos, no junto con ellos. Si puedes ver esta distinción, has dado
el primer paso en la programación de objetos. El enfoque orientado a
objetos sugiere una forma de pensar completamente diferente. Los datos y el
código están encapsulados juntos en el mismo mundo, divididos en clases.
Cada clase es como una receta que se puede usar cuando quieres crear
un objeto útil. Puedes producir tantos objetos como necesites para resolver tu
problema. Cada objeto tiene un conjunto de rasgos (se denominan propiedades
o atributos; usaremos ambas palabras como sinónimos) y es capaz de realizar
un conjunto de actividades (que se denominan métodos). Las recetas pueden
modificarse si son inadecuadas para fines específicos y, en efecto, pueden
crearse nuevas clases. Estas nuevas clases heredan propiedades y métodos de
los originales, y generalmente agregan algunos nuevos, creando nuevas
herramientas más específicas.

Los objetos son encarnaciones de las ideas expresadas en clases, como un


pastel de queso en tu plato, es una encarnación de la idea expresada en una
receta impresa en un viejo libro de cocina. Los objetos interactúan entre sí,
intercambian datos o activan sus métodos. Una clase construida
adecuadamente (y, por lo tanto, sus objetos) puede proteger los datos sensibles
y ocultarlos de modificaciones no autorizadas. No existe un límite claro entre los
datos y el código: viven como uno solo dentro de los objetos. Todos estos
conceptos no son tan abstractos como pudieras pensar al principio. Por el
contrario, todos están tomados de experiencias de la vida real y, por lo tanto,
son extremadamente útiles en la programación de computadoras: no crean vida
artificial reflejan hechos reales, relaciones y circunstancias.
Jerarquías de clase
La palabra clases tiene muchos significados, pero no todos son compatibles con
las ideas que queremos discutir aquí. La clase que nos concierne es como
una categoría, como resultado de similitudes definidas con precisión.
Intentaremos señalar algunas clases que son buenos ejemplos de este
concepto.

Veamos por un momento los vehículos. Todos los vehículos existentes (y los
que aún no existen) estan relacionados por una sola característica
importante: la capacidad de moverse. Puedes argumentar que un perro
también se mueve; ¿Es un perro un vehículo? No lo es. Tenemos que mejorar la
definición, es decir, enriquecerla con otros criterios, distinguir los vehículos de
otros seres y crear una conexión más fuerte. Consideremos las siguientes
circunstancias: los vehículos son entidades creadas artificialmente que se
utilizan para el transporte, movidos por fuerzas de la naturaleza y dirigidos
(conducidos) por humanos.

Según esta definición, un perro no es un vehículo. La clase vehículos es muy


amplia. Tenemos que definir clases especializadas. Las clases especializadas
son las subclases. La clase vehículos será una superclase para todas ellas.

Nota: la jerarquía crece de arriba hacia abajo, como raíces de árboles,


no ramas. La clase más general y más amplia siempre está en la parte
superior (la superclase) mientras que sus descendientes se encuentran abajo
(las subclases).A estas alturas, probablemente puedas señalar algunas
subclases potenciales para la superclase Vehículos. Hay muchas clasificaciones
posibles. Elegimos subclases basadas en el medio ambiente y decimos que hay
(al menos) cuatro subclases:
 Vehículos Terrestres.
 Vehículos Acuáticos.
 Vehículos Aéreos.
 Vehículos Espaciales.

En este ejemplo, discutiremos solo la primera subclase: vehículos terrestres. Si


lo deseas, puedes continuar con las clases restantes. Los vehículos terrestres
pueden dividirse aún más, según el método con el que impactan el suelo.
Entonces, podemos enumerar:

 Vehículos de ruedas.
 Vehículos oruga.
 Aerodeslizadores.

La figura ilustra la jerarquía que hemos creado.Ten en cuenta la dirección de las


flechas: siempre apuntan a la superclase. La clase de nivel superior es una
excepción: no tiene su propia superclase.

Jerarquías de clase: continuación


Otro ejemplo es la jerarquía del reino taxonómico de los animales.

Podemos decir que todos los animales (nuestra clase de nivel superior) se


puede dividir en cinco subclases:

 Mamíferos.
 Reptiles.
 Pájaros.
 Peces.
 Anfibios.

Tomaremos el primero para un análisis más detallado.

Hemos identificado las siguientes subclases:

 Mamíferos salvajes.
 Mamíferos domesticados.

Intenta extender la jerarquía de la forma que quieras y encuentra el lugar


adecuado para los humanos.
¿Qué es un objeto?
Una clase (entre otras definiciones) es un conjunto de objetos. Un objeto
es un ser perteneciente a una clase.

Un objeto es una encarnación de los requisitos, rasgos y cualidades


asignados a una clase específica. Esto puede sonar simple, pero ten en
cuenta las siguientes circunstancias importantes. Las clases forman una
jerarquía. Esto puede significar que un objeto que pertenece a una clase
específica pertenece a todas las superclases al mismo tiempo. También puede
significar que cualquier objeto perteneciente a una superclase puede no
pertenecer a ninguna de sus subclases.

Por ejemplo: cualquier automóvil personal es un objeto que pertenece a la


clase vehículos terrestres. También significa que el mismo automóvil pertenece
a todas las superclases de su clase local; por lo tanto, también es miembro de
la clase vehículos. Tu perro (o tu gato) es un objeto incluido en la
clase Mamíferos domesticados, lo que significa explícitamente que también está
incluido en la clase animales.

Cada subclase es más especializada (o más específica) que su superclase.


Por el contrario, cada superclase es más general (más abstracta) que
cualquiera de sus subclases. Ten en cuenta que hemos supuesto que una clase
solo puede tener una superclase; esto no siempre es cierto, pero discutiremos
este tema más adelante.
Herencia
Definamos uno de los conceptos fundamentales de la programación de objetos,
llamado herencia. Cualquier objeto vinculado a un nivel específico de una
jerarquía de clases hereda todos los rasgos (así como los requisitos y
cualidades) definidos dentro de cualquiera de las superclases.

La clase de inicio del objeto puede definir nuevos rasgos (así como requisitos y
cualidades) que serán heredados por cualquiera de sus superclases.

No deberías tener ningún problema para hacer coincidir esta regla con ejemplos
específicos, ya sea que se aplique a animales o vehículos.

¿Qué contiene un objeto?


La programación orientada a objetos supone que cada objeto existente
puede estar equipado con tres grupos de atributos:

 Un objeto tiene un nombre que lo identifica de forma exclusiva dentro


de su namespace (aunque también puede haber algunos objetos
anónimos).
 Un objeto tiene un conjunto de propiedades individuales que lo
hacen original, único o sobresaliente (aunque es posible que algunos
objetos no tengan propiedades).
 Un objeto tiene un conjunto de habilidades para realizar
actividades específicas, capaz de cambiar el objeto en sí, o algunos de
los otros objetos.

Hay una pista (aunque esto no siempre funciona) que te puede ayudar a
identificar cualquiera de las tres esferas anteriores. Cada vez que se describe
un objeto y se usa:

 Un sustantivo: probablemente se este definiendo el nombre del objeto.


 Un adjetivo: probablemente se este definiendo una propiedad del objeto.
 Un verbo: probablemente se este definiendo una actividad del objeto.
Dos ejemplos deberían servir como un buen ejemplo:

 Max es un gato grande que duerme todo el día.


Nombre del objeto = Max
Clase de inicio = Gato
Propiedad = Tamaño (grande)
Actividad = Dormir (todo el día)

 Un Cadillac rosa pasó rápidamente.


Nombre del objeto = Cadillac
Clase de inicio = Vehículo terrestre
Propiedad = Color (rosa)
Actividad = Pasar (rápidamente)

Tu primera clase
La programación orientada a objetos es el arte de definir y expandir clases.
Una clase es un modelo de una parte muy específica de la realidad, que refleja
las propiedades y actividades que se encuentran en el mundo real. Las clases
definidas al principio son demasiado generales e imprecisas para cubrir el
mayor número posible de casos reales. No hay obstáculo para definir nuevas
subclases más precisas. Heredarán todo de su superclase, por lo que el trabajo
que se utilizó para su creación no se desperdicia. La nueva clase puede agregar
nuevas propiedades y nuevas actividades y, por lo tanto, puede ser más útil en
aplicaciones específicas. Obviamente, se puede usar como una superclase para
cualquier número de subclases recién creadas. El proceso no necesita tener un
final. Puedes crear tantas clases como necesites. La clase que se define no
tiene nada que ver con el objeto: la existencia de una clase no significa
que ninguno de los objetos compatibles se creará automáticamente. La
clase en sí misma no puede crear un objeto: debes crearlo tu mismo y Python te
permite hacerlo. Es hora de definir la clase más simple y crear un objeto. Echa
un vistazo al siguiente ejemplo:

class ClaseSimple:
pass

Hemos definido una clase. La clase es bastante pobre: no contiene propiedades


ni actividades. Esta vacía, pero eso no importa por ahora. Cuanto más simple
sea la clase, mejor para nuestros propósitos. La definición comienza con la
palabra clave reservada  class . La palabra clave reservada es seguida por
un identificador que nombrará la clase (nota: no lo confundas con el
nombre del objeto: estas son dos cosas diferentes). A continuación, se
agregan dos puntos:), como clases, como funciones, forman su propio bloque
anidado. El contenido dentro del bloque define todas las propiedades y
actividades de la clase. La palabra clave reservada  pass  llena la clase con nada.
No contiene ningún método ni propiedades.

Tu primer objeto
La clase recién definida se convierte en una herramienta que puede crear
nuevos objetos. La herramienta debe usarse explícitamente, bajo demanda.
Imagina que deseas crear un objeto (exactamente uno) de la
clase  ClaseSimple . Para hacer esto, debes asignar una variable para almacenar
el objeto recién creado de esa clase y crear un objeto al mismo tiempo. Se hace
de la siguiente manera:

miPrimerObjeto = ClaseSimple()

Nota:

 El nombre de la clase intenta fingir que es una función, ¿puedes ver


esto? Lo discutiremos pronto.
 El objeto recién creado está equipado con todo lo que trae la clase;
Como esta clase está completamente vacía, el objeto también está
vacío.

El acto de crear un objeto de la clase seleccionada también se


llama instanciación (ya que el objeto se convierte en una instancia de
la clase).

Dejemos las clases en paz por un breve momento, ya que ahora diremos
algunas palabras sobre pilas. Sabemos que el concepto de clases y
objetos puede no estar completamente claro todavía. No te preocupes,
te explicaremos todo muy pronto.

¿Qué es una pila?


Una pila es una estructura desarrollada para almacenar datos de una
manera muy específica.. Imagina una pila de monedas. No puedes poner una
moneda en ningún otro lugar sino en la parte superior de la pila. Del mismo
modo, no puedes sacar una moneda de la pila desde ningún lugar que no sea la
parte superior de la pila. Si deseas obtener la moneda que se encuentra en la
parte inferior, debes eliminar todas las monedas de los niveles superiores.

El nombre alternativo para una pila (pero solo en la terminología de TI) es UEPS
(LIFO son sus siglas en íngles). Es una abreviatura para una descripción
muy clara del comportamiento de la pila: Último en Entrar - Primero en
Salir (Last In - First Out). La moneda que quedó en último lugar en la pila
saldrá primero.

Una pila es un objeto con dos operaciones elementales, denominadas


convencionalmente push (cuando un nuevo elemento se coloca en la parte
superior) y pop (cuando un elemento existente se retira de la parte superior).

Las pilas se usan muy a menudo en muchos algoritmos clásicos, y es difícil


imaginar la implementación de muchas herramientas ampliamente utilizadas
sin el uso de pilas.

Implementemos una pila en Python. Esta será una pila muy simple, y te
mostraremos cómo hacerlo en dos enfoques independientes: de manera
procedimental y orientado a objetos.

Comencemos con el primero.

La pila: el enfoque procedimental


Primero, debes decidir cómo almacenar los valores que llegarán a la pila. Sugerimos
utilizar el método más simple, y emplear una lista para esta tarea. Supongamos que
el tamaño de la pila no está limitado de ninguna manera. Supongamos también que el
último elemento de la lista almacena el elemento superior. La pila en sí ya está creada:
pila = []

Estamos listos para definir una función que pone un valor en la pila. Aquí están las
presuposiciones para ello:

 El nombre para la función es  push .


 La función obtiene un parámetro (este es el valor que se debe colocar en la
pila).
 La función no devuelve nada.
 La función agrega el valor del parámetro al final de la pila.

Así es como lo hemos hecho, echa un vistazo:

def push(val):
pila.append(val)

Ahora es tiempo de que una función quite un valor de la pila. Así es como puedes
hacerlo:

 El nombre de la función es pop.


 La función no obtiene ningún parámetro.
 La función devuelve el valor tomado de la pila.
 La función lee el valor de la parte superior de la pila y lo elimina.

La función esta aqui:

def pop():
val = pila[-1]
del pila[-1]
return val
Nota: la función no verifica si hay algún elemento en la pila.

Armemos todas las piezas juntas para poner la pila en movimiento. El programa
completo empuja (push) tres números a la pila, los saca e imprime sus valores en
pantalla. Puedes verlo en la ventana del editor.

El programa muestra el siguiente texto en pantalla:

1
2
3

La pila: el enfoque procedimental frente al enfoque


orientado a objetos
La pila procedimental está lista. Por supuesto, hay algunas debilidades, y la
implementación podría mejorarse de muchas maneras (aprovechar las
excepciones es una buena idea), pero en general la pila está completamente
implementada, y puedes usarla si lo necesitas.

Pero cuanto más la uses, más desventajas encontrarás. Éstas son algunas de
ellas:

 La variable esencial (la lista de la pila) es altamente vulnerable;


cualquiera puede modificarla de forma incontrolable, destruyendo la pila;
esto no significa que se haya hecho de manera maliciosa; por el
contrario, puede ocurrir como resultado de un descuido, por ejemplo,
cuando alguien confunde nombres de variables; imagina que
accidentalmente has escrito algo como esto:

pila[0] = 0
El funcionamiento de la pila estará completamente desorganizado.

 También puede suceder que un día necesites más de una pila; tendrás
que crear otra lista para el almacenamiento de la pila, y probablemente
otras funciones  push  y  pop .

 También puede suceder que no solo necesites funciones  push  y  pop ,
pero también algunas otras funciones; ciertamente podrías
implementarlas, pero intenta imaginar qué sucedería si tuvieras docenas
de pilas implementadas por separado.

El enfoque orientado a objetos ofrece soluciones para cada uno de los


problemas anteriores. Vamos a nombrarlos primero:

 La capacidad de ocultar (proteger) los valores seleccionados contra el


acceso no autorizado se llama encapsulamiento; no se puede
acceder a los valores encapsulados ni modificarlos si deseas
utilizarlos exclusivamente.

 Cuando tienes una clase que implementa todos los comportamientos de


pila necesarios, puedes producir tantas pilas como desees; no necesitas
copiar ni replicar ninguna parte del código.

 La capacidad de enriquecer la pila con nuevas funciones proviene de la


herencia; puedes crear una nueva clase (una subclase) que herede todos
los rasgos existentes de la superclase y agregue algunos nuevos.
Ahora escribamos una nueva implementación de pila desde cero. Esta vez,
utilizaremos el enfoque orientado a objetos, que te guiará paso a paso en el
mundo de la programación de objetos.

La pila - el enfoque orientado a objetos


Por supuesto, la idea principal sigue siendo la misma. Usaremos una lista como
almacenamiento de la pila. Solo tenemos que saber cómo poner la lista en la clase.

Comencemos desde el principio: así es como comienza la pila de orientada a objetos:

class Pila:

Ahora, esperamos dos cosas de la clase:

 Queremos que la clase tenga una propiedad como el almacenamiento de la


pila - tenemos que "instalar" una lista dentro de cada objeto de la
clase (nota: cada objeto debe tener su propia lista; la lista no debe compartirse
entre diferentes pilas).
 Despues, queremos que la lista esté oculta de la vista de los usuarios de la
clase.

¿Cómo se hace esto?

A diferencia de otros lenguajes de programación, Python no tiene medios para


permitirte declarar una propiedad como esa.

En su lugar, debes agregar una instrucción específica. Las propiedades deben


agregarse a la clase manualmente.

¿Cómo garantizar que dicha actividad tiene lugar cada vez que se crea una nueva pila?

Hay una manera simple de hacerlo - tienes que equipar a la clase con una función
específica:

 Tiene que ser nombrada de forma estricta.


 Se invoca implícitamente cuando se crea el nuevo objeto.

Tal función es llamada el constructor, ya que su propósito general es construir un


nuevo objeto. El constructor debe saber todo acerca de la estructura del objeto y
debe realizar todas las inicializaciones necesarias.

Agreguemos un constructor muy simple a la nueva clase. Echa un vistazo al código:

class Pila:
def __init__(self):
print("¡Hola!")
objetoPila = Pila()

Expliquemos más a detalle:

 El nombre del constructor es siempre  __init__ .


 Tiene que tener al menos un parámetro (discutiremos esto más tarde); el
parámetro se usa para representar el objeto recién creado: puedes usar el
parámetro para manipular el objeto y enriquecerlo con las propiedades
necesarias; harás uso de esto pronto.
 Nota: el parámetro obligatorio generalmente se denomina  self  - es solo una
sugerencía, pero deberías seguirla - simplifica el proceso de lectura y
comprensión de tu código.

El código está en el editor. Ejecútalo ahora.

Aquí está su salida:

¡Hola!

Nota: no hay rastro de la invocación del constructor dentro del código. Ha sido
invocado implícita y automáticamente. Hagamos uso de eso ahora.
La pila - el enfoque orientado a objetos: continuación
Cualquier cambio que realices dentro del constructor que modifique el estado del
parámetro  self  se verá reflejado en el objeto recien creado. Esto significa que puedes
agregar cualquier propiedad al objeto y la propiedad permanecerá allí hasta que el
objeto termine su vida o la propiedad se elimine explícitamente.

Ahora agreguemos solo una propiedad al nuevo objeto - una lista para la pila. La
nombraremos  listaPila .

Justo como aqui:

class Pila:
def __init__(self):
self.listaPila = []

objetoPila = Pila()
print(len(objetoPila.listaPila))

Nota:

 Hemos usado la notación punteada, al igual que cuando se invocan métodos.


Esta es la manera general para acceder a las propiedades de un objeto: debes
nombrar el objeto, poner un punto ( . ) después de el, y especificar el nombre
de la propiedad deseada, ¡no uses paréntesis! No deseas invocar un método,
deseas acceder a una propiedad.
 Si estableces el valor de una propiedad por primera vez (como en el
constructor), lo estás creando; a partir de ese momento, el objeto tiene la
propiedad y está listo para usar su valor.
 Hemos hecho algo más en el código: hemos intentado acceder a la
propiedad  listaPila  desde fuera de la clase inmediatamente después de que
se haya creado el objeto; queremos verificar la longitud actual de la pila, ¿lo
hemos logrado?

Sí, por supuesto: el código produce el siguiente resultado:

Esto no es lo que queremos de la pila. Nosotros queremos


que  listaPila  este escondida del mundo exterior. ¿Es eso posible?

Sí, y es simple, pero no muy intuitivo.


La pila - el enfoque orientado a objetos: continuación
Echa un vistazo: hemos agregado dos guiones bajos antes del nombre  listaPila  -
nada mas:

class Pila:

def __init__(self):

self.__listaPila = []

objetoPila = Pila()

print(len(objetoPila.__listaPila))

El cambio invalida el programa..

¿Por qué?

Cuando cualquier componente de la clase tiene un nombre que comienza con dos
guiones bajos ( __ ), se vuelve privado - esto significa que solo se puede acceder
desde la clase.

No puedes verlo desde el mundo exterior. Así es como Python implementa el concepto
de encapsulación.

Ejecuta el programa para probar nuestras suposiciones: una


excepción AttributeError debe ser lanzada.
El enfoque orientado a objetos: una pila desde cero
Ahora es el momento de que las dos funciones (métodos) implementen las
operaciones push y pop. Python supone que una función de este tipo debería
estar inmersa dentro del cuerpo de la clase - como el constructor.

Queremos invocar estas funciones para  agregar (push)  y  quitar (pop)  valores de
la pila. Esto significa que ambos deben ser accesibles para el usuario de la clase (en
contraste con la lista previamente construida, que está oculta para los usuarios de la
clase ordinaria).

Tal componente es llamado publico, por ello no puede comenzar su nombre con


dos (o más) guiones bajos. Hay un requisito más - el nombre no debe tener más de
un guión bajo.

Las funciones en sí son simples. Echa un vistazo:

class Pila:
def __init__(self):
self.__listaPila = []

def push(self, val):


self.__listaPila.append(val)

def pop(self):
val = self.__listaPila[-1]
del self.__listaPila[-1]
return val

objetoPila = Pila()

objetoPila.push(3)
objetoPila.push(2)
objetoPila.push(1)

print(objetoPila.pop())
print(objetoPila.pop())
print(objetoPila.pop())

Sin embargo, hay algo realmente extraño en el código. Las funciones parecen
familiares, pero tienen más parámetros que sus contrapartes procedimentales.

Aquí, ambas funciones tienen un parámetro llamado  self  en la primera posición de la
lista de parámetros.
¿Es necesario? Si, lo es.

Todos los métodos deben tener este parámetro. Desempeña el mismo papel que el
primer parámetro constructor.

Permite que el método acceda a entidades (propiedades y actividades / métodos)


del objeto. No puedes omitirlo. Cada vez que Python invoca un método, envía
implícitamente el objeto actual como el primer argumento.

Esto significa que el método está obligado a tener al menos un parámetro, que
Python mismo utiliza - no tienes ninguna influencia sobre el.

Si tu método no necesita ningún parámetro, este debe especificarse de todos modos.


Si está diseñado para procesar solo un parámetro, debes especificar dos, ya que la
función del primero sigue siendo la misma.

Hay una cosa más que requiere explicación: la forma en que se invocan los métodos
desde la variable  __listaPila .

Afortunadamente, es mucho más simple de lo que parece:

 La primera etapa entrega el objeto como un todo →  self .


 A continuación, debes llegar a la lista  __listaPila  →  self.__listaPila .
 Con  __listaPila  lista para ser usada, puedes realizar el tercer y último paso
→  self.__listaPila.append(val) .

La declaración de la clase está completa y se han enumerado todos sus componentes.


La clase está lista para usarse.
El enfoque orientado a objetos: una pila desde cero
Tener tal clase abre nuevas posibilidades. Por ejemplo, ahora puedes hacer que más
de una pila se comporte de la misma manera. Cada pila tendrá su propia copia de
datos privados, pero utilizará el mismo conjunto de métodos.

Esto es exactamente lo que queremos para este ejemplo.

Existen dos pilas creadas a partir de la misma clase base.


Trabajan independientemente. Puedes crear más si quieres.
El enfoque orientado a objetos: una pila desde cero
(continuación)
Analiza el fragmento a continuación: hemos creado tres objetos de la clase  Pila .
Después, hemos hecho malabarismos. Intenta predecir el valor que se muestra en la
pantalla.
El enfoque orientado a objetos: una pila desde cero
(continuación)
Ahora vamos un poco más lejos. Vamos a agregar una nueva clase para manejar
pilas.

La nueva clase debería poder evaluar la suma de todos los elementos almacenados


actualmente en la pila.

No queremos modificar la pila previamente definida. Ya es lo suficientemente buena


en sus aplicaciones, y no queremos que cambie de ninguna manera. Queremos una
nueva pila con nuevas capacidades. En otras palabras, queremos construir una
subclase de la ya existente clase  Pila .

El primer paso es fácil: solo define una nueva subclase que apunte a la clase que se
usará como superclase.

Así es como se ve:

class SumarPila(Pila):
pass

La clase aún no define ningún componente nuevo, pero eso no significa que esté
vacía. Obtiene (hereda) todos los componentes definidos por su superclase - el
nombre de la superclase se escribe después de los dos puntos, después del nombre
de la nueva clase.

Esto es lo que queremos de la nueva pila:

 Queremos que el método  push  no solo inserte el valor en la pila, sino que
también sume el valor a la variable  sum .
 Queremos que la función  pop  no solo extraiga el valor de la pila, sino que
también reste el valor de la variable  sum .

En primer lugar, agreguemos una nueva variable a la clase. Sera una variable privada,
al igual que la lista de pila. No queremos que nadie manipule el valor de la
variable  sum .

Como ya sabes, el constructor agrega una nueva propiedad a la clase. Ya sabes cómo
hacerlo, pero hay algo realmente intrigante dentro del constructor. Echa un vistazo:

class SumarPila(Pila):
def __init__(self):
Pila.__init__(self)
self.__sum = 0
La segunda línea del cuerpo del constructor crea una propiedad llamada  __sum  -
almacenará el total de todos los valores de la pila.

Pero la línea anterior se ve diferente. ¿Qué hace? ¿Es realmente necesaria? Sí lo es.

Al contrario de muchos otros lenguajes, Python te obliga a invocar explícitamente el


constructor de una superclase. Omitir este punto tendrá efectos nocivos: el objeto
se verá privado de la lista  __listaPila . Tal pila no funcionará correctamente.

Esta es la única vez que puedes invocar a cualquiera de los constructores disponibles
explícitamente; se puede hacer dentro del constructor de la superclase.

Ten en cuenta la sintaxis:

 Se especifica el nombre de la superclase (esta es la clase cuyo constructor se


desea ejecutar).
 Se pone un punto ( . ) después del nombre.
 Se especifica el nombre del constructor.
 Se debe señalar al objeto (la instancia de la clase) que debe ser inicializado por
el constructor; es por eso que se debe especificar el argumento y utilizar la
variable  self  aquí; recuerda: invocar cualquier método (incluidos los
constructores) desde fuera de la clase nunca requiere colocar el
argumento  self  en la lista de argumentos - invocar un método desde
dentro de la clase exige el uso explícito del argumento  self , y tiene que ser el
primero en la lista.

Nota: generalmente es una práctica recomendada invocar al constructor de la


superclase antes de cualquier otra inicialización que desees realizar dentro de la
subclase. Esta es la regla que hemos seguido en el código.
El enfoque orientado a objetos: una pila desde cero
(continuación)
En segundo lugar, agreguemos dos métodos. Pero, ¿realmente estamos agregándolos?
Ya tenemos estos métodos en la superclase. ¿Podemos hacer algo así?

Si podemos. Significa que vamos a cambiar la funcionalidad de los métodos, no sus


nombres. Podemos decir con mayor precisión que la interfaz (la forma en que se
manejan los objetos) de la clase permanece igual al cambiar la implementación al
mismo tiempo. Comencemos con la implementación de la función  push . Esto es lo que
esperamos de la función:

 Agregar el valor a la variable  __sum .


 Agregar el valor a la pila.

Nota: la segunda actividad ya se implementó dentro de la superclase, por lo que


podemos usarla. Además, tenemos que usarla, ya que no hay otra forma de acceder a
la variable  __listaPila . Así es como se mira el método  push  dentro de la subclase:

def push(self, val):


self.__sum += val
Pila.push(self, val)

Toma en cuenta la forma en que hemos invocado la implementación anterior del


método  push  (el disponible en la superclase):

 Tenemos que especificar el nombre de la superclase; esto es necesario para


indicar claramente la clase que contiene el método, para evitar confundirlo con
cualquier otra función del mismo nombre.
 Tenemos que especificar el objeto de destino y pasarlo como primer
argumento (no se agrega implícitamente a la invocación en este contexto).

Se dice que el método  push  ha sido anulado - el mismo nombre que en la superclase
ahora representa una funcionalidad diferente.
El enfoque orientado a objetos: una pila desde cero
(continuación)
Esta es la nueva función  pop :

def pop(self):
val = Pila.pop(self)
self.__sum -= val
return val

Hasta ahora, hemos definido la variable  __sum , pero no hemos proporcionado un


método para obtener su valor. Parece estar escondido. ¿Cómo podemos mostrarlo y
que al mismo tiempo se proteja de modificaciones? Tenemos que definir un nuevo
método. Lo nombraremos  getSuma . Su única tarea será devolver el valor de  __sum .

Aquí está:

def getSuma(self):
return self.__sum

Entonces, veamos el programa en el editor. El código completo de la clase está ahí.


Podemos ahora verificar su funcionamiento, y lo hacemos con la ayuda de unas pocas
líneas de código adicionales.Como puedes ver, agregamos cinco valores subsiguientes
en la pila, imprimimos su suma y los sacamos todos de la pila.Bien, esta ha sido una
breve introducción a la programación de orientada a objetos de Python. Pronto te
contaremos todo con más detalle.

Variables de
instancia
En general, una clase puede equiparse con dos tipos diferentes de datos para
formar las propiedades de una clase. Ya viste uno de ellos cuando estábamos
estudiando pilas.

Este tipo de propiedad existe solo cuando se crea explícitamente y se agrega a


un objeto. Como ya sabes, esto se puede hacer durante la inicialización del
objeto, realizada por el constructor.

Además, se puede hacer en cualquier momento de la vida del objeto. Es


importante mencionar también que cualquier propiedad existente se puede
eliminar en cualquier momento.

Tal enfoque tiene algunas consecuencias importantes:

 Diferentes objetos de la misma clase pueden poseer diferentes


conjuntos de propiedades.
 Debe haber una manera de verificar con seguridad si un objeto
específico posee la propiedad que deseas utilizar (a menos que
quieras provocar una excepción, siempre vale la pena considerarlo).
 Cada objeto lleva su propio conjunto de propiedades - no interfieren
entre sí de ninguna manera.

Tales variables (propiedades) se llaman variables de instancia.

La palabra instancia sugiere que están estrechamente conectadas a los objetos


(que son instancias de clase), no a las clases mismas. Echemos un vistazo más
de cerca a ellas.

Aquí hay un ejemplo:

class ClaseEjemplo:
def __init__(self, val = 1):
self.primera = val

def setSegunda(self, val):


self.segunda = val

objetoEjemplo1 = ClaseEjemplo()
objetoEjemplo2 = ClaseEjemplo(2)

objetoEjemplo2.setSegunda(3)

objetoEjemplo3 = ClaseEjemplo(4)
objetoEjemplo3.tercera = 5

print(objetoEjemplo1.__dict__)
print(objetoEjemplo2.__dict__)
print(objetoEjemplo3.__dict__)

Se necesita una explicación adicional antes de entrar en más detalles. Echa un


vistazo a las últimas tres líneas del código.
Los objetos de Python, cuando se crean, están dotados de un pequeño
conjunto de propiedades y métodos predefinidos. Cada objeto los tiene,
los quieras o no. Uno de ellos es una variable llamada  __dict__  (es un
diccionario).

La variable contiene los nombres y valores de todas las propiedades (variables)


que el objeto contiene actualmente. Vamos a usarla para presentar de forma
segura el contenido de un objeto.

Vamos a sumergirnos en el código ahora:

 La clase llamada  ClaseEjemplo  tiene un constructor, el cual crea


incondicionalmente una variable de instancia llamada  primera , y le
asigna el valor pasado a través del primer argumento (desde la
perspectiva del usuario de la clase) o el segundo argumento (desde la
perspectiva del constructor); ten en cuenta el valor predeterminado del
parámetro: cualquier cosa que puedas hacer con un parámetro de
función regular también se puede aplicar a los métodos.

 La clase también tiene un método que crea otra variable de


instancia, llamada  segunda .

 Hemos creado tres objetos de la clase  ClaseEjemplo , pero todas estas


instancias difieren:

o objetoEjemplo1  solo tiene una propiedad llamada  primera .

o objetoEjemplo2  tiene dos propiedades:  primera  y  segunda .

o objetoEjemplo3  ha sido enriquecido sobre la marcha con una


propiedad llamada  tercera , fuera del código de la clase: esto es
posible y totalmente permisible.

La salida del programa muestra claramente que nuestras suposiciones son


correctas: aquí están:

{'primera': 1}
{'primera': 2, 'segunda': 3}
{'primera': 4, 'tercera': 5}

Hay una conclusión adicional que debería mencionarse aquí: el modificar una
variable de instancia de cualquier objeto no tiene impacto en todos los
objetos restantes. Las variables de instancia están perfectamente aisladas
unas de otras.
Variables de instancia: continuación
Observa el ejemplo modificado en el editor. Es casi lo mismo que el anterior. La única
diferencia está en los nombres de las propiedades. Hemos agregado dos guiones
bajos ( __ ) en frente de ellos. Como sabes, tal adición hace que la variable de instancia
sea privada - se vuelve inaccesible desde el mundo exterior. El comportamiento real
de estos nombres es un poco más complicado, así que ejecutemos el programa. Esta
es la salida:

{'_ClaseEjemplo__primera': 1}
{'_ClaseEjemplo__primera': 2, '_ClaseEjemplo__segunda': 3}
{'_ClaseEjemplo__primera': 4, '__tercera': 5}

¿Puedes ver estos nombres extraños llenos de guiones bajos? ¿De dónde provienen?

Cuando Python ve que deseas agregar una variable de instancia a un objeto y lo vas a
hacer dentro de cualquiera de los métodos del objeto, maneja la operación de la
siguiente manera:

 Coloca un nombre de clase antes de tu nombre.


 Coloca un guión bajo adicional al principio.

Es por ello que  __primera  se convierte en  _ClaseEjemplo__primera . El nombre


ahora es completamente accesible desde fuera de la clase. Puedes ejecutar un
código como este:

print(objetoEjemplo1._ClaseEjemplo__primera)

y obtendrás un resultado válido sin errores ni excepciones. Como puedes ver, hacer
que una propiedad sea privada es limitado. No funcionará si agregas una variable
de instancia fuera del código de clase. En este caso, se comportará como cualquier
otra propiedad ordinaria.
Variables de Clase
Una variable de clase es una propiedad que existe en una sola copia y se
almacena fuera de cualquier objeto. Nota: no existe una variable de
instancia si no hay ningún objeto en la clase; existe una variable de clase en
una copia, incluso si no hay objetos en la clase.

Las variables de clase se crean de manera diferente. El ejemplo te dirá más:

class ClaseEjemplo:
contador = 0
def __init__(self, val = 1):
self.__primera = val
ClaseEjemplo.contador += 1

objetoEjemplo1 = ClaseEjemplo()
objetoEjemplo2 = ClaseEjemplo(2)
objetoEjemplo3 = ClaseEjemplo(4)

print(objetoEjemplo1.__dict__, objetoEjemplo1.contador)
print(objetoEjemplo2.__dict__, objetoEjemplo2.contador)
print(objetoEjemplo3.__dict__, objetoEjemplo3.contador)

Observa:

 Hay una asignación en la primera linea de la definición de clase:


establece la variable denominada  contador  a 0; inicializando la variable
dentro de la clase pero fuera de cualquiera de sus métodos hace que la
variable sea una variable de clase.
 El acceder a dicha variable tiene el mismo aspecto que acceder a
cualquier atributo de instancia; está en el cuerpo del constructor; como
puedes ver, el constructor incrementa la variable en uno; en efecto, la
variable cuenta todos los objetos creados.

Ejecutar el código causará el siguiente resultado:

{'_ClaseEjemplo__primera': 1} 3
{'_ClaseEjemplo__primera': 2} 3
{'_ClaseEjemplo__primera': 4} 3

Dos conclusiones importantes provienen del ejemplo:

 Las variables de clase no se muestran en el diccionario de un


objeto  __dict__  (esto es natural ya que las variables de clase no son
partes de un objeto), pero siempre puedes intentar buscar en la variable
del mismo nombre, pero a nivel de clase, te mostraremos esto muy
pronto.
 Una variable de clase siempre presenta el mismo valor en todas las
instancias de clase (objetos).

Variables de Clase: continuación


Mira el ejemplo en el editor. ¿Puedes adivinar su salida?

Ejecuta el programa y verifica si tus predicciones fueron correctas. Todo funciona


como se esperaba, ¿no?
Variables de Clase: continuación
Hemos dicho antes que las variables de clase existen incluso cuando no se creó
ninguna instancia de clase (objeto).

Ahora aprovecharemos la oportunidad para mostrarte la diferencia entre estas dos


variables  __dict__ , la de la clase y la del objeto.

Observa el código en el editor. La prueba está ahí.

Echemos un vistazo más de cerca:

 Definimos una clase llamada  ClaseEjemplo .


 La clase define una variable de clase llamada  varia .
 El constructor de la clase establece la variable con el valor del parámetro.
 Nombrar la variable es el aspecto más importante del ejemplo porque:
o El cambiar la asignación a  self.varia = val  crearía una variable
de instancia con el mismo nombre que la clase.
o El cambiar la asignación a  varia = val  operaría en la variable
local de un método; (te recomendamos probar los dos casos
anteriores; esto te facilitará recordar la diferencia).
 La primera línea del código fuera de la clase imprime el valor del
atributo  ClaseEjemplo.varia . Nota: utilizamos el valor antes de instanciar el
primer objeto de la clase.

Ejecuta el código en el editor y verifica su salida.

Como puedes ver  __dict__  contiene muchos más datos que la contraparte de su
objeto. La mayoría de ellos son inútiles ahora - el que queremos que verifiques
cuidadosamente muestra el valor actual de  varia .

Nota que el  __dict__  del objeto está vacío - el objeto no tiene variables de instancia.
Comprobando la existencia de un atributo
La actitud de Python hacia la instanciación de objetos plantea una cuestión
importante: en contraste con otros lenguajes de programación, es posible que no
esperes que todos los objetos de la misma clase tengan los mismos conjuntos de
propiedades.

Justo como en el ejemplo en el editor. Míralo cuidadosamente.

El objeto creado por el constructor solo puede tener uno de los dos atributos
posibles:  a  o  b .

La ejecución del código producirá el siguiente resultado:

Traceback (most recent call last):

File ".main.py", line 11, in

print(objetoEjemplo.b)

AttributeError: 'ClaseEjemplo' object has no attribute 'b'

Como puedes ver, acceder a un atributo de objeto (clase) no existente provoca una
excepción AttributeError.
Comprobando la existencia de un atributo:
continuación
La instrucción try-except te brinda la oportunidad de evitar problemas con
propiedades inexistentes. Es fácil: mira el código en el editor. Como puedes ver, esta
acción no es muy sofisticada. Esencialmente, acabamos de barrer el tema debajo de la
alfombra.

Afortunadamente, hay una forma más de hacer frente al problema. Python


proporciona una función que puede verificar con seguridad si algún objeto / clase
contiene una propiedad específica. La función se llama  hasattr , y espera que le
pasen dos argumentos:

 La clase o el objeto que se verifica.


 El nombre de la propiedad cuya existencia se debe informar (Nota: debe ser
una cadena que contenga el nombre del atributo).

La función retorna True o False.

Así es como puedes utilizarla:

class ClaseEjemplo:
def __init__(self, val):
if val % 2 != 0:
self.a = 1
else:
self.b = 1

objetoEjemplo = ClaseEjemplo(1)
print(objetoEjemplo.a)

if hasattr(objetoEjemplo, 'b'):
print(objetoEjemplo.b)
Comprobando la existencia de un atributo:
continuación
No olvides que la función  hasattr()  también puede operar en clases. Puedes
usarlo para averiguar si una variable de clase está disponible, como en el ejemplo
en el editor.

La función devuelve True si la clase especificada contiene un atributo dado,


y False de lo contrario.

¿Puedes adivinar la salida del código? Ejecútalo para verificar tus conjeturas.

Un ejemplo más: analiza el código a continuación e intenta predecir su salida:

class ClaseEjemplo:

a = 1

def __init__(self):

self.b = 2

objetoEjemplo = ClaseEjemplo()

print(hasattr(objetoEjemplo, 'b'))

print(hasattr(objetoEjemplo, 'a'))

print(hasattr(ClaseEjemplo, 'b'))

print(hasattr(ClaseEjemplo, 'a'))

¿Tuviste éxito? Ejecuta el código para verificar tus predicciones.

Bien, hemos llegado al final de esta sección. En la siguiente sección vamos a hablar
sobre los métodos, ya que los métodos dirigen los objetos y los activan.
Métodos a detalle
Resumamos todos los hechos relacionados con el uso de métodos en las clases de
Python.Como ya sabes, un método es una función que está dentro de una clase.
Hay un requisito fundamental: un método está obligado a tener al menos un
parámetro (no existen métodos sin parámetros; un método puede invocarse sin un
argumento, pero no puede declararse sin parámetros). El primer (o único) parámetro
generalmente se denomina  self . Te sugerimos que lo sigas nombrando de esta
manera, darle otros nombres puede causar sorpresas inesperadas. El
nombre self sugiere el propósito del parámetro - identifica el objeto para el cual se
invoca el método. Si vas a invocar un método, no debes pasar el argumento para el
parámetro  self  - Python lo configurará por ti. El ejemplo en el editor muestra la
diferencia.

El código da como salida:

método

Toma en cuenta la forma en que hemos creado el objeto - hemos tratado el nombre


de la clase como una función, y devuelve un objeto recién instanciado de la clase.

Si deseas que el método acepte parámetros distintos a  self , debes:

 Colocarlos después de  self  en la definición del método.


 Pasarlos como argumentos durante la invocación sin especificar  self .

Justo como aqui:

class conClase:
def metodo(self, par):
print("método:", par)

obj = conClase()
obj.metodo(1)
obj.metodo(2)
obj.metodo(3)

El código da como salida:

método: 1
método: 2
método: 3

Métodos a detalle:
continuación
El parámetro  self  es usado para obtener acceso a la instancia del objeto y las
variables de clase.

El ejemplo muestra ambas formas de utilizar el parámetro  self :

El parámetro  self  también se usa para invocar otros métodos desde dentro de la


clase.

Justo como aquí:


Métodos a detalle: continuación
Si se nombra un método de esta manera:  __init__ , no será un método regular, será
un constructor.

Si una clase tiene un constructor, este se invoca automática e implícitamente cuando


se instancia el objeto de la clase.

El constructor:

 Esta obligado a tener el parámetro  self  (se configura automáticamente).


 Pudiera (pero no necesariamente) tener mas parámetros que solo  self ; si
esto sucede, la forma en que se usa el nombre de la clase para crear el objeto
debe tener la definición  __init__ .
 Se puede utilizar para configurar el objeto, es decir, inicializa
adecuadamente su estado interno, crea variables de instancia, crea instancias
de cualquier otro objeto si es necesario, etc.

Observa el código en el editor. El ejemplo muestra un constructor muy simple pero


funcional.

Ejecutalo. El código da como salida:

objeto

Ten en cuenta que el constructor:

 No puede retornar un valor, ya que está diseñado para devolver un objeto


recién creado y nada más.
 No se puede invocar directamente desde el objeto o desde dentro de la
clase (puedes invocar un constructor desde cualquiera de las superclases del
objeto, pero discutiremos esto más adelante).
Métodos a detalle: continuación
Como  __init__  es un método, y un método es una función, puedes hacer los mismos
trucos con constructores y métodos que con las funciones ordinarias.

El ejemplo en el editor muestra cómo definir un constructor con un valor de


argumento predeterminado. Pruébalo.

El código da como salida:

objeto

None

Todo lo que hemos dicho sobre el manejo de los nombres también se aplica a los
nombres de métodos, un método cuyo nombre comienza con  __  está (parcialmente)
oculto.

El ejemplo muestra este efecto:


La vida interna de clases y objetos
Cada clase de Python y cada objeto de Python está pre-equipado con un conjunto de
atributos útiles que pueden usarse para examinar sus capacidades.

Ya conoces uno de estos: es la propiedad  __dict__ .

Observemos cómo esta propiedad trata con los métodos: mira el código en el editor.

Ejecútalo para ver qué produce. Verifica el resultado.

Encuentra todos los métodos y atributos definidos. Localiza el contexto en el que


existen: dentro del objeto o dentro de la clase.
La vida interna de clases y objetos: continuación
__dict__  es un diccionario. Otra propiedad incorporada que vale la pena mencionar
es una cadena llamada  __name__ .

La propiedad contiene el nombre de la clase. No es nada emocionante, es solo una


cadena.

Nota: el atributo  __name__  está ausente del objeto - existe solo dentro de las clases.

Si deseas encontrar la clase de un objeto en particular, puedes usar una función


llamada  type() , la cual es capaz (entre otras cosas) de encontrar una clase que se
haya utilizado para crear instancias de cualquier objeto.

Mira el código en el editor, ejecútalo y compruébalo tu mismo.

La salida del código es:

conClase

conClase

Nota: algo como esto  print(obj.__name__)  causará un error.


La vida interna de clases y objetos: continuación
__module__  es una cadena, también almacena el nombre del módulo que contiene
la definición de la clase.

Vamos a comprobarlo: ejecuta el código en el editor.

La salida del código es:

__main__

__main__

Como sabes, cualquier módulo llamado  __main__  en realidad no es un módulo, sino
es el archivo actualmente en ejecución.
La vida interna de clases y objetos: continuación
__bases__  es una tupla. La tupla contiene clases (no nombres de clases) que son
superclases directas para la clase.

El orden es el mismo que el utilizado dentro de la definición de clase.

Te mostraremos solo un ejemplo muy básico, ya que queremos resaltar como


funciona la herencia.

Además, te mostraremos cómo usar este atributo cuando discutamos los aspectos
orientados a objetos de las excepciones.

Nota: solo las clases tienen este atributo - los objetos no.

Hemos definido una función llamada  printBases() , diseñada para presentar


claramente el contenido de la tupla.

Observa el código en el editor. Ejecútalo. Su salida es:

( object )

( object )

( SuperUno SuperDos )

Nota: una clase sin superclases explícitas apunta al objeto (una clase de Python


predefinida) como su antecesor directo.
Reflexión e
introspección
Todo esto permite que el
programador de Python realice
dos actividades importantes específicas para muchos lenguajes objetivos. Las
cuales son:

 Introspección, que es la capacidad de un programa para examinar el


tipo o las propiedades de un objeto en tiempo de ejecución.
 Reflexión, que va un paso más allá, y es la capacidad de un programa
para manipular los valores, propiedades y/o funciones de un objeto en
tiempo de ejecución.

En otras palabras, no tienes que conocer la definición completa de clase/objeto


para manipular el objeto, ya que el objeto y/o su clase contienen los metadatos
que te permiten reconocer sus características durante la ejecución del
programa.
Investigando Clases
¿Qué puedes descubrir acerca de las clases en Python? La respuesta es simple: todo.

Tanto la reflexión como la introspección permiten al programador hacer cualquier


cosa con cada objeto, sin importar de dónde provenga.

Analiza el código en el editor.

La función llamada  incIntsI()  obtiene un objeto de cualquier clase, escanea su


contenido para encontrar todos los atributos enteros con nombres que comienzan
con i, y los incrementa en uno.

¿Imposible? ¡De ningúna manera!

Así es como funciona:

 La línea 1: define una clase simple...


 Líneas 3 a la 10: ... la llenan con algunos atributos.
 Línea 12: ¡esta es nuestra función!
 Línea 13: escanea el atributo  __dict__ , buscando todos los nombres de
atributos.
 Línea 14: si un nombre comienza con i...
 Línea 15: ... utiliza la función  getattr()  para obtener su valor actual;
nota:  getattr()  toma dos argumentos: un objeto y su nombre de propiedad
(como una cadena) y devuelve el valor del atributo actual.
 Línea 16: comprueba si el valor es de tipo entero, emplea la
función  isinstance()  para este propósito (discutiremos esto más adelante).
 Línea 17: si la comprobación sale bien, incrementa el valor de la propiedad
haciendo uso de la función  setattr() ; la función toma tres argumentos: un
objeto, el nombre de la propiedad (como una cadena) y el nuevo valor de la
propiedad.

El código da como salida:

{'a': 1, 'b': 2, 'i': 3, 'ireal': 3.5, 'entero': 4, 'z': 5}


{'a': 1, 'b': 2, 'i': 4, 'ireal': 3.5, 'entero': 4, 'z': 5}

¡Eso es todo!
Herencia: ¿por qué y cómo?
Antes de comenzar a hablar sobre la herencia, queremos presentar un nuevo y
práctico mecanismo utilizado por las clases y los objetos de Python: es la forma en
que el objeto puede presentarse a si mismo.

Comencemos con un ejemplo. Observa el código en el editor.

El programa imprime solo una línea de texto, que en nuestro caso es:

<__main__.Estrella object at 0x7f377e552160>

Si ejecutas el mismo código en tu computadora, verás algo muy similar, aunque el


número hexadecimal (la subcadena que comienza con 0x) será diferente, ya que es
solo un identificador de objeto interno utilizado por Python, y es poco probable que
aparezca igual cuando se ejecuta el mismo código en un entorno diferente.

Como puedes ver, la impresión aquí no es realmente útil, y algo más específico, es
preferible.

Afortunadamente, Python ofrece tal función.


Herencia: ¿por qué y cómo?
Cuando Python necesita que alguna clase u objeto deba ser presentado como una
cadena (es recomendable colocar el objeto como argumento en la invocación de la
función  print() ), intenta invocar un método llamado  __str__()  del objeto y
emplear la cadena que devuelve.

El método por default  __str__()  devuelve la cadena anterior: fea y poco informativa.
Puedes cambiarlo definiendo tu propio método del nombre.

Lo acabamos de hacer: observa el código en el editor.

El método nuevo  __str__()  genera una cadena que consiste en los nombres de la
estrella y la galaxia, nada especial, pero los resultados de impresión se ven mejor
ahora, ¿no?

¿Puedes adivinar la salida? Ejecuta el código para verificar si tenías razón.


Herencia: ¿por qué y cómo?
El término herencia es más antiguo que la
programación de computadoras, y describe la
práctica común de pasar diferentes bienes de una
persona a otra después de la muerte de esa
persona. El término, cuando se relaciona con la
programación de computadoras, tiene un
significado completamente diferente.

Definamos el término para nuestros propósitos:

La herencia es una práctica común (en la programación de objetos) de pasar


atributos y métodos de la superclase (definida y existente) a una clase
recién creada, llamada subclase. En otras palabras, la herencia es una
forma de construir una nueva clase, no desde cero, sino utilizando un
repertorio de rasgos ya definido. La nueva clase hereda (y esta es la clave)
todo el equipamiento ya existente, pero puedes agregar algo nuevo si es
necesario. Gracias a eso, es posible construir clases más especializadas
(más concretas) utilizando algunos conjuntos de reglas y comportamientos
generales predefinidos.
El factor más importante del proceso es la relación entre la superclase y todas
sus subclases (nota: si B es una subclase de A y C es una subclase de B, esto
también significa que C es una subclase de A, ya que la relación es totalmente
transitiva).

Aquí se presenta un ejemplo muy simple de herencia de dos niveles:

class Vehiculo:
pass

class VehiculoTerrestre(Vehiculo):
pass

class VehiculoOruga(VehiculoTerrestre):
pass

Todas las clases presentadas están vacías por ahora, ya que te mostraremos
cómo funcionan las relaciones mutuas entre las superclases y las subclases. Las
llenaremos con contenido pronto.

Podemos decir que:

 La clase  Vehiculo  es la superclase para


clases  VehiculoTerrestre  y  VehiculoOruga .
 La clase  VehiculoTerrestre  es una subclase de  Vehiculo  y la superclase
de  VehiculoOruga  al mismo tiempo.
 La clase  VehiculoOruga  es una subclase tanto
de  Vehiculo  y  VehiculoTerrestre .

El conocimiento anterior proviene de la lectura del código (en otras palabras, lo


sabemos porque podemos verlo).

¿Python sabe lo mismo? ¿Es posible preguntarle a Python al respecto? Sí lo es.

Herencia: issubclass()
Python ofrece una función que es capaz de identificar una relación entre dos clases,
y aunque su diagnóstico no es complejo, puede verificar si una clase particular es
una subclase de cualquier otra clase.

Así es como se ve:

issubclass(ClaseUno, ClaseDos)

La función devuelve True si  ClaseUno  es una subclase de  ClaseDos , y False de lo


contrario. Vamos a verlo en acción, puede sorprenderte. Mira el código en el editor.
Léelo cuidadosamente. Hay dos bucles anidados. Su propósito es verificar todos los
pares de clases ordenadas posibles y que imprima los resultados de la
verificación para determinar si el par coincide con la relación subclase-
superclase.

Ejecuta el código. El programa produce el siguiente resultado:


True False False
True True False
True True True

Hagamos que el resultado sea más legible:

↓ es una subclase de Vehicul VehiculoTerrestr VehiculoOrug


→ o e a

Vehiculo True False False

VehiculoTerrestre True True False

VehiculoOruga True True True

Existe una observación importante que hacer: cada clase se considera una subclase
de sí misma.

Herencia: isinstance()
Como ya sabes, un objeto es la encarnación de una clase. Esto significa que el
objeto es como un pastel horneado usando una receta que se incluye dentro de la
clase.

Esto puede generar algunos problemas.

Supongamos que tienes un pastel (por ejemplo, resultado de un argumento pasado a


tu función). Deseas saber qué receta se ha utilizado para prepararlo. ¿Por qué? Porque
deseas saber qué esperar de él, por ejemplo, si contiene nueces o no, lo cual es
información crucial para ciertas personas.

Del mismo modo, puede ser crucial si el objeto tiene (o no tiene) ciertas características.
En otras palabras, si es un objeto de cierta clase o no.
Tal hecho podría ser detectado por la función llamada  isinstance() :

isinstance(nombreObjeto, nombreClase)

La función devuelve True si el objeto es una instancia de la clase, o False de lo


contrario.

Ser una instancia de una clase significa que el objeto (el pastel) se ha preparado
utilizando una receta contenida en la clase o en una de sus superclases.

No lo olvides: si una subclase contiene al menos las mismas caracteristicas que


cualquiera de sus superclases, significa que los objetos de la subclase pueden hacer lo
mismo que los objetos derivados de la superclase, por lo tanto, es una instancia de su
clase de inicio y cualquiera de sus superclases.

Probémoslo. Analiza el código en el editor.

Hemos creado tres objetos, uno para cada una de las clases. Luego, usando dos bucles
anidados, verificamos todos los pares posibles de clase de objeto para averiguar si
los objetos son instancias de las clases.

Ejecuta el código.

Esto es lo que obtenemos:

True False False


True True False
True True True

Hagamos que el resultado sea más legible:

↓ es una instancia de Vehicul miVehiculoTerrest VehiculoOrug


→ o re a

miVehiculo True False False

miVehiculoTerrestre True True False

VehiculoOruga True True True


¿La tabla confirma nuestras expectativas?
Herencia: el operador is
También existe un operador de Python que vale la pena mencionar, ya que se refiere
directamente a los objetos: aquí está:

objetoUno is objetoDos

El operador  is  verifica si dos variables (en este caso  objetoUno  y  objetoDos ) se
refieren al mismo objeto. No olvides que las variables no almacenan los objetos
en sí, sino solo los identificadores que apuntan a la memoria interna de Python.
Asignar un valor de una variable de objeto a otra variable no copia el objeto, sino solo
su identificador. Es por ello que un operador como  is  puede ser muy útil en ciertas
circunstancias. Echa un vistazo al código en el editor. Analicémoslo:

 Existe una clase muy simple equipada con un constructor simple, que crea una
sola propiedad. La clase se usa para instanciar dos objetos. El primero se
asigna a otra variable, y su propiedad  val  se incrementa en uno.
 Luego, el operador  is  se aplica tres veces para verificar todos los pares de
objetos posibles, y todos los valores de la propiedad  val  son mostrados en
pantalla.
 La última parte del código lleva a cabo otro experimento. Después de tres
tareas, ambas cadenas contienen los mismos textos, pero estos textos se
almacenan en diferentes objetos.

El código imprime:

False
False
True
1 2 1
True False

Los resultados prueban que  ob1  y  ob3  son en realidad los mismos objetos, mientras
que  str1  y  str2  no lo son, a pesar de que su contenido sea el mismo.

Cómo Python
encuentra propiedades y métodos
Ahora veremos cómo Python trata con los métodos de herencia.

Echa un vistazo al ejemplo en el editor. Vamos a analizarlo:

 Existe una clase llamada  Super , que define su propio constructor utilizado para
asignar la propiedad del objeto, llamada  nombre .
 La clase también define el método  __str__() , lo que permite que la clase
pueda presentar su identidad en forma de texto.
 La clase se usa luego como base para crear una subclase llamada Sub . La
clase  Sub  define su propio constructor, que invoca el de la superclase. Toma
nota de cómo lo hemos hecho:  Super.__init__(self, nombre) .
 Hemos nombrado explícitamente la superclase y hemos apuntado al método
para invocar a  __init__() , proporcionando todos los argumentos necesarios.
 Hemos instanciado un objeto de la clase  Sub  y lo hemos impreso.

El código da como salida:

Mi nombre es Andy.

Nota: Como no existe el método  __str__()  dentro de la clase  Sub , la cadena a


imprimir se producirá dentro de la clase  Super . Esto significa que el
método  __str__()  ha sido heredado por la clase  Sub .
Cómo Python encuentra propiedades y métodos:
continuación
Mira el código en el editor. Lo hemos modificado para mostrarte otro método de
acceso a cualquier entidad definida dentro de la superclase.

En el ejemplo anterior, nombramos explícitamente la superclase. En este ejemplo,


hacemos uso de la función  super() , la cual accede a la superclase sin necesidad de
conocer su nombre:

super().__init__(nombre)

La función  super()  crea un contexto en el que no tiene que (además, no debe) pasar
el argumento propio al método que se invoca; es por eso que es posible activar el
constructor de la superclase utilizando solo un argumento.

Nota: puedes usar este mecanismo no solo para invocar al constructor de la


superclase, pero también para obtener acceso a cualquiera de los recursos
disponibles dentro de la superclase.
Cómo Python encuentra propiedades y métodos:
continuación
Intentemos hacer algo similar, pero con propiedades (más precisamente
con: variables de clase).

Observa el ejemplo en el editor.

Como puedes observar, la clase  Super  define una variable de clase llamada  supVar , y
la clase  Sub  define una variable llamada  subVar .

Ambas variables son visibles dentro del objeto de clase  Sub  - es por ello que el código
da como salida:

1
Cómo Python encuentra propiedades y métodos:
continuación
El mismo efecto se puede observar con variables de instancia - observa el segundo
ejemplo en el editor.

El constructor de la clase  Sub  crea una variable de instancia llamada  subVar , mientras
que el constructor de  Super  hace lo mismo con una variable de nombre  supVar . Al
igual que el ejemplo anterior, ambas variables son accesibles desde el objeto de
clase  Sub .

La salida del programa es:

12

11

Nota: La existencia de la variable  supVar  obviamente está condicionada por la


invocación del constructor de la clase  Super . Omitirlo daría como resultado la
ausencia de la variable en el objeto creado (pruébalo tu mismo).
Cómo Python encuentra propiedades y métodos:
continuación
Ahora es posible formular una declaración general que describa el comportamiento de
Python.

Cuando intentes acceder a una entidad de cualquier objeto, Python intentará (en este
orden):

 Encontrarla dentro del objeto mismo.


 Encontrarla en todas las clases involucradas en la línea de herencia del objeto
de abajo hacia arriba.

Si ambos intentos fallan, una excepción ( AttributeError ) será lanzada.

La primera condición puede necesitar atención adicional. Como sabes, todos los
objetos derivados de una clase en particular pueden tener diferentes conjuntos de
atributos, y algunos de los atributos pueden agregarse al objeto mucho tiempo
después de la creación del objeto. El ejemplo en el editor resume esto en una línea de
herencia de tres niveles. Analízalo cuidadosamente. Todos los comentarios que
hemos hecho hasta ahora están relacionados con casos de herencia única, cuando
una subclase tiene exactamente una superclase. Esta es la situación más común (y
también la recomendada). Python, sin embargo, ofrece mucho más aquí. En las
próximas lecciones te mostraremos algunos ejemplos de herencia múltiple.
Cómo Python encuentra propiedades y métodos:
continuación
La herencia múltiple ocurre cuando una clase tiene más de una superclase.

Sintácticamente, dicha herencia se presenta como una lista de superclases separadas


por comas entre paréntesis después del nombre de la nueva clase, al igual que aquí:
La clase  Sub  tiene dos superclases:  SuperA  y  SuperB . Esto significa que la
clase  Sub  hereda todos los bienes ofrecidos por ambas clases  SuperA  y  SuperB .

El código imprime:

Ahora es el momento de introducir un nuevo término - overriding (anulación).

¿Qué crees que sucederá si más de una de las superclases define una entidad con un
nombre en particular?

Cómo Python encuentra propiedades y métodos:


continuación
Analicemos el ejemplo en el editor.

Tanto la clase  Nivel1  como la  Nivel2  definen un método llamado  fun()  y una
propiedad llamada  var . ¿Significará esto el objeto de la clase  Nivel3  podrá acceder a
dos copias de cada entidad? De ningún modo.

La entidad definida después (en el sentido de herencia) anula la misma entidad


definida anteriormente. Es por eso que el código produce el siguiente resultado:

200 201

Como puedes ver, la variable de clase  var  y el método  fun()  de la clase  Nivel2  anula
las entidades de los mismos nombres derivados de la clase  Nivel1 .

Esta característica se puede usar intencionalmente para modificar el comportamiento


predeterminado de las clases (o definido previamente) cuando cualquiera de tus
clases necesite actuar de manera diferente a su ancestro.
También podemos decir que Python busca una entidad de abajo hacia arriba, y
está completamente satisfecho con la primera entidad del nombre deseado que
encuentre.

¿Qué ocurre cuando una clase tiene dos ancestros que ofrecen la misma entidad y se
encuentran en el mismo nivel? En otras palabras, ¿Qué se debe esperar cuando surge
una clase usando herencia múltiple? Miremos lo siguiente.

Cómo Python encuentra propiedades y métodos:


continuación
Echemos un vistazo al ejemplo en el editor.

La clase  Sub  hereda todos los bienes de dos superclases,  Izquierda  y  Derecha  (estos
nombres están destinados a ser significativos). No hay duda de que la variable de
clase  varDerecha  proviene de la clase  Derecha , y la variable  varIzquierda  proviene
de la clase  Izquierda  respectivamente. Esto es claro. Pero, ¿De donde proviene la
variable  var ? ¿Es posible adivinarlo? El mismo problema se encuentra con el
método  fun()  - ¿Será invocado desde  Izquierda  o desde  Derecha ? Ejecutemos el
programa: la salida será:

I II DD Izquierda

Esto prueba que ambos casos poco claros tienen una solución dentro de la
clase  Izquierda . ¿Es esta una premisa suficiente para formular una regla general? Sí
lo es.
Podemos decir que Python busca componentes de objetos en el siguiente orden:

 Dentro del objeto mismo.


 En sus superclases, de abajo hacia arriba.
 Si hay más de una clase en una ruta de herencia, Python las escanea de
izquierda a derecha.

¿Necesitas algo más? Simplemente haz una pequeña enmienda en el código -


reemplaza: class Sub(Izquierda, Derecha):  con:  class Sub(Derecha,
Izquierda): , luego ejecuta el programa nuevamente y observa qué sucede.

¿Qué ves ahora? Vemos:

D II DD Derecha

Cómo
construir
una
jerarquía de
clases
Construir una jerarquía de clases no es solo por amor al arte.

Si divides un problema entre las clases y decides cuál de ellas debe ubicarse en la
parte superior y cuál debe ubicarse en la parte inferior de la jerarquía, debes analizar
cuidadosamente el problema, pero antes de mostrarte cómo hacerlo (y cómo no
hacerlo), queremos resaltar un efecto interesante. No es nada extraordinario (es solo
una consecuencia de las reglas generales presentadas anteriormente), pero recordarlo
puede ser clave para comprender cómo funcionan algunos códigos y cómo se puede
usar este efecto para construir un conjunto flexible de clases.

Echa un vistazo al código en el editor. Analicémoslo:

 Existen dos clases llamadas  Uno  y  Dos , se entiende que  Dos  es derivada
de  Uno . Nada especial. Sin embargo, algo es notable: el método  doit() .
 El método  doit()  está definido dos veces: originalmente dentro de  Uno  y
posteriormente dentro de  Dos . La esencia del ejemplo radica en el hecho de
que es invocado solo una vez - dentro de  Uno .

La pregunta es: ¿cuál de los dos métodos será invocado por las dos últimas líneas del
código?

La primera invocación parece ser simple, el invocar el método  haz_algo()  del


objeto  uno  obviamente activará el primero de los métodos.

La segunda invocación necesita algo de atención. También es simple si tienes en


cuenta cómo Python encuentra los componentes de la clase. La segunda invocación
lanzará el método  hazlo()  en la forma existente dentro de la clase  Dos ,
independientemente del hecho de que la invocación se lleva a cabo dentro de la
clase  Uno .

En efecto, el código genera el siguiente resultado:

hazlo de Uno
hazlo de Dos

Nota: la situación en la cual la subclase puede modificar el comportamiento de su


superclase (como en el ejemplo) se llama polimorfismo. La palabra proviene del
griego (polys: "muchos, mucho" y morphe, "forma, forma"), lo que significa que una
misma clase puede tomar varias formas dependiendo de las redefiniciones realizadas
por cualquiera de sus subclases.

El método, redefinido en cualquiera de las superclases, que cambia el


comportamiento de la superclase, se llama virtual.

En otras palabras, ninguna clase se da por hecho. El comportamiento de cada clase


puede ser modificado en cualquier momento por cualquiera de sus subclases.

Te mostraremos cómo usar el polimorfismo para extender la flexibilidad de la


clase.
Cómo construir una jerarquía de clases:
continuación
Mira el ejemplo en el editor. ¿Se parece a algo? Sí, por supuesto que lo hace. Se refiere
al ejemplo que se muestra al comienzo del módulo cuando hablamos de los conceptos
generales de la programación orientada a objetos. Puede parecer extraño, pero no
utilizamos herencia en este ejemplo, solo queríamos mostrarte que no nos limita.

Definimos dos clases separadas capaces de producir dos tipos diferentes de vehículos
terrestres. La principal diferencia entre ellos está en cómo giran. Un vehículo con
ruedas solo gira las ruedas delanteras (generalmente). Un vehículo oruga tiene que
detener una de las pistas.

¿Puedes seguir el código?


 Un vehículo oruga realiza un giro deteniéndose y moviéndose en una de sus
pistas (esto lo hace el método  control_de_pista() , el cual se implementará
más tarde).
 Un vehículo con ruedas gira cuando sus ruedas delanteras giran (esto lo hace el
método  girar_ruedas_delanteras() ).
 El método  girar()  utiliza el método adecuado para cada vehículo en
particular.

¿Puedes detectar el error del código?

Los métodos  girar() son muy similares como para dejarlos en esta forma.

Vamos a reconstruir el código: vamos a presentar una superclase para reunir todos los
aspectos similares de los vehículos, trasladando todos los detalles a las subclases.

Cómo construir una jerarquía de clases:


continuación
Mira el código en el editor nuevamente. Esto es lo que hemos hecho:

 Definimos una superclase llamada  Vehiculo , la cual utiliza el


método  girar()  para implementar un esquema para poder girar, mientras
que el giro en si es realizado por  cambiardireccion() ; nota: dicho método
está vacío, ya que vamos a poner todos los detalles en la subclase (dicho
método a menudo se denomina método abstracto, ya que solo demuestra
alguna posibilidad que será instanciada más tarde).
 Definimos una subclase llamada  VehiculoOruga  (nota: es derivada de la
clase  Vehiculo ) la cual instancia el método  cambiardireccion()  utilizando el
método denominado  control_de_pista().
 Respectivamente, la subclase llamada  VehiculoTerrestre  hace lo mismo,
pero usa el método  girar_ruedas_delanteras()  para obligar al vehículo a
girar.

La ventaja más importante (omitiendo los problemas de legibilidad) es que esta forma
de código te permite implementar un nuevo algoritmo de giro simplemente
modificando el método  girar() , lo cual se puede hacer en un solo lugar, ya que todos
los vehículos lo obedecerán.

Así es como el el polimorfismo ayuda al desarrollador a mantener el código limpio


y consistente.

Cómo construir una jerarquía de clases:


continuación
La herencia no es la única forma de construir clases adaptables. Puedes lograr los
mismos objetivos (no siempre, pero muy a menudo) utilizando una técnica llamada
composición.

La composición es el proceso de componer un objeto usando otros objetos


diferentes. Los objetos utilizados en la composición entregan un conjunto de rasgos
deseados (propiedades y / o métodos), podemos decir que actúan como bloques
utilizados para construir una estructura más complicada.

Puede decirse que:

 La herencia extiende las capacidades de una clase agregando nuevos


componentes y modificando los existentes; en otras palabras, la receta
completa está contenida dentro de la clase misma y todos sus ancestros; el
objeto toma todas las pertenencias de la clase y las usa.
 La composición proyecta una clase como contenedor capaz de almacenar y
usar otros objetos (derivados de otras clases) donde cada uno de los objetos
implementa una parte del comportamiento de una clase.

Permítenos ilustrar la diferencia usando los vehículos previamente definidos. El


enfoque anterior nos condujo a una jerarquía de clases en la que la clase más alta
conocía las reglas generales utilizadas para girar el vehículo, pero no sabía cómo
controlar los componentes apropiados (ruedas o pistas).

Las subclases implementaron esta capacidad mediante la introducción de mecanismos


especializados. Hagamos (casi) lo mismo, pero usando composición. La clase, como en
el ejemplo anterior, sabe cómo girar el vehículo, pero el giro real lo realiza un objeto
especializado almacenado en una propiedad llamada  controlador .
El  controlador  es capaz de controlar el vehículo manipulando las partes relevantes
del vehículo.

Echa un vistazo al editor: así es como podría verse.

Existen dos clases llamadas  Pistas  y  Ruedas  - ellas saben cómo controlar la dirección
del vehículo. También hay una clase llamada  Vehiculo  que puede usar cualquiera de
los controladores disponibles (los dos ya definidos o cualquier otro definido en el
futuro): el  controlador  se pasa a la clase durante la inicialización.

De esta manera, la capacidad de giro del vehículo se compone de un objeto externo,


no implementado dentro de la clase  Vehiculo .

En otras palabras, tenemos un vehículo universal y podemos instalar pistas o ruedas


en él.
Herencia simple versus herencia múltiple
Como ya sabes, no hay obstáculos para usar la herencia múltiple en Python.
Puedes derivar cualquier clase nueva de más de una clase definida
previamente.

Solo hay un "pero". El hecho de que puedas hacerlo no significa que tengas que
hacerlo.

No olvides que:

 Una sola clase de herencia siempre es más simple, segura y fácil de


entender y mantener.

 La herencia múltiple siempre es arriesgada, ya que tienes muchas más


oportunidades de cometer un error al identificar estas partes de las
superclases que influirán efectivamente en la nueva clase.
 La herencia múltiple puede hacer que la anulación sea extremadamente
difícil; además, el emplear la función  super()  se vuelve ambiguo.

 La herencia múltiple viola el principio de responsabilidad única (mas


detalles aquí: https://en.wikipedia.org/wiki/Single_responsibility_principle)
ya que forma una nueva clase de dos (o más) clases que no saben nada
una de la otra.

 Sugerimos encarecidamente la herencia múltiple como la última de


todas las posibles soluciones: si realmente necesitas las diferentes
funcionalidades que ofrecen las diferentes clases, la composición puede
ser una mejor alternativa.

Diamantes y porque no los quieres


El espectro de problemas que posiblemente provienen de la herencia múltiple
se ilustra mediante un problema clásico denominado problema de
diamantes. El nombre refleja la forma del diagrama de herencia: echa un
vistazo a la imagen.

 Existe la superclase superior nombrada A.


 Aquí hay dos subclases derivadas de A - B y C.
 Y también está la subclase inferior llamada D, derivada de B y C (o C y B,
ya que estas dos variantes significan cosas diferentes en Python).

¿Puedes ver el diamante allí?

A Python, sin embargo, no le gustan los diamantes, y no te permitirá


implementar algo como esto. Si intentas construir una jerarquía como esta:
class A:

pass

class B(A):

pass

class C(A):

pass

class D(A, B):

pass

d = D()

Obtendrás una excepción TypeError, junto con el siguiente mensaje:

Cannot create a consistent method resolution

order (MRO) for bases B, A

Donde  MRO  significa Method Resolution Order. Este es el algoritmo que Python


utiliza para buscar el árbol de herencia y encontrar los métodos necesarios.

Los diamantes son preciosos y valiosos ... pero no en la programación. Evítalos


por tu propio bien.

Más sobre excepciones


El discutir sobre la programación orientada a objetos ofrece una muy buena
oportunidad para volver a las excepciones. La naturaleza orientada a objetos de las
excepciones de Python las convierte en una herramienta muy flexible, capaz de
adaptarse a necesidades específicas, incluso aquellas que aún no conoces.

Antes de adentrarnos en el lado orientado a objetos de las excepciones, queremos


mostrarte algunos aspectos sintácticos y semánticos de la forma en que Python trata
el bloque try-except, ya que ofrece un poco más de lo que hemos presentado hasta
ahora.

La primera característica que queremos analizar aquí es una rama adicional posible
que se puede colocar dentro (o más bien, directamente detrás) del bloque try-except:
es la parte del código que comienza con  else  - justo como el ejemplo en el editor.
Un código etiquetado de esta manera se ejecuta cuando (y solo cuando) no se ha
generado ninguna excepción dentro de la parte del  try: . Podemos decir que esta
rama se ejecuta después del  try:  - ya sea el que comienza con  except  (no olvides
que puede haber más de una rama de este tipo) o la que comienza con  else .

Nota: la rama  else:  debe ubicarse después de la última rama  except .

Más sobre excepciones


El bloque try-except se puede extender de una manera más: agregando una parte
encabezada por la palabra clave reservada  finally  (debe ser la última rama del
código diseñada para manejar excepciones).

Nota: estas dos variantes ( else  y  finally ) no son dependientes entre si, y pueden
coexistir u ocurrir de manera independiente.

El bloque  finally  siempre se ejecuta (finaliza la ejecución del bloque try-except, de


ahí su nombre), sin importar lo que sucedió antes, incluso cuando se genera o lanza
una excepción, sin importar si esta se ha manejado o no.

Mira el código en el editor. Su salida es:


Las excepciones son clases
Los ejemplos anteriores se centraron en detectar un tipo específico de excepción y
responder de manera apropiada. Ahora vamos a profundizar más y mirar dentro de la
excepción misma.

Probablemente no te sorprenderá saber que las excepciones son clases. Además,


cuando se genera una excepción, se crea una instancia de un objeto de la clase y pasa
por todos los niveles de ejecución del programa, buscando la rama "except" que está
preparada para tratar con la excepción.

Tal objeto lleva información útil que puede ayudarte a identificar con precisión todos
los aspectos de la situación pendiente. Para lograr ese objetivo, Python ofrece una
variante especial de la cláusula de excepción: puedes encontrarla en el editor.
Como puedes ver, la sentencia  except  se extendió y contiene una frase adicional que
comienza con la palabra clave reservada  as , seguida por un identificador. El
identificador está diseñado para capturar la excepción con el fin de analizar su
naturaleza y sacar conclusiones adecuadas.

Nota: el alcance del identificador solo es dentro del  except , y no va más allá.

El ejemplo presenta una forma muy simple de utilizar el objeto recibido: simplemente
imprímelo (como puedes ver, la salida es producida por el método del
objeto  __str__() ) y contiene un breve mensaje que describe la razón.

Se imprimirá el mismo mensaje si no hay un bloque  except  en el código, y Python se


verá obligado a manejarlo por si mismo.

Las excepciones son clases


Todas las excepciones integradas de Python forman una jerarquía de clases.

Analiza el código en el editor. Este programa muestra todas las clases de las
excepciónes predefinidas en forma de árbol. Como un árbol es un ejemplo perfecto
de una estructura de datos recursiva, la recursión parece ser la mejor manera de
recorrerlo. La función  printExcTree()  toma dos argumentos:

 Un punto dentro del árbol desde el cual comenzamos a recorrerlo.


 Un nivel de anidación (lo usaremos para construir un dibujo simplificado de las
ramas del árbol).
Comencemos desde la raíz del árbol: la raíz de las clases de excepciónes de Python es
la clase  BaseException  (es una superclase de todas las demás excepciones).

Para cada una de las clases encontradas, se realiza el mismo conjunto de operaciones:

 Imprimir su nombre, tomado de la propiedad  __name__ .


 Iterar a través de la lista de subclases provistas por el
método  __subclasses__() , e invocar recursivamente la
función  printExcTree() , incrementando el nivel de anidación
respectivamente.

Ten en cuenta cómo hemos dibujado las ramas. La impresión no está ordenada de
alguna manera: si deseas un desafío, puedes intentar ordenarla tu mismo. Además,
hay algunas imprecisiones sutiles en la forma en que se presentan algunas ramas. Eso
también se puede arreglar, si lo deseas.

Anatomía
detallada de las excepciones
Echemos un vistazo más de cerca al objeto de la excepción, ya que hay algunos
elementos realmente interesantes aquí (volveremos al tema pronto cuando
consideremos las técnicas base de entrada y salida de Python, ya que su
subsistema de excepción extiende un poco estos objetos). La
clase  BaseException  introduce una propiedad llamada  args . Es una
tupla diseñada para reunir todos los argumentos pasados al
constructor de la clase. Está vacío si la construcción se ha invocado sin
ningún argumento, o solo contiene un elemento cuando el constructor recibe un
argumento (no se considera el argumento  self  aquí), y así sucesivamente.
Hemos preparado una función simple para imprimir la propiedad  args  de una
manera elegante, puedes ver la función en el editor. Hemos utilizado la función
para imprimir el contenido de la propiedad  args  en tres casos diferentes, donde
la excepción de la clase  Exception  es lanzada de tres maneras distintas. Para
hacerlo más espectacular, también hemos impreso el objeto en sí, junto con el
resultado de la invocación  __str__() .

El primer caso parece de rutina, solo hay el nombre Exception despues de la


palabra clave reservada  raise . Esto significa que el objeto de esta clase se ha
creado de la manera más rutinaria. El segundo y el tercer caso pueden parecer
un poco extraños a primera vista, pero no hay nada extraño, son solo las
invocaciones del constructor. En la segunda sentencia  raise , el constructor se
invoca con un argumento, y en el tercero, con dos. Como puedes ver, la salida
del programa refleja esto, mostrando los contenidos apropiados de la
propiedad  args :

: :

mi excepción : mi excepción : mi excepción

('mi', 'excepción') : ('mi', 'excepción') : ('mi', 'excepción')

Cómo crear tu propia excepción


La jerarquía de excepciones no está cerrada ni terminada, y siempre puedes ampliarla
si deseas o necesitas crear tu propio mundo poblado con tus propias excepciones.
Puede ser útil cuando se crea un módulo complejo que detecta errores y genera
excepciones, y deseas que las excepciones se distingan fácilmente de cualquier otra de
Python.

Esto se puede hacer al definir tus propias excepciones como subclases derivadas
de las predefinidas.

Nota: si deseas crear una excepción que se utilizará como un caso especializado de
cualquier excepción incorporada, derivala solo de esta. Si deseas construir tu propia
jerarquía, y no quieres que esté estrechamente conectada al árbol de excepciones de
Python, derivala de cualquiera de las clases de excepción principales, tal
como: Exception.

Imagina que has creado una aritmética completamente nueva, regida por sus propias
leyes y teoremas. Está claro que la división también se ha redefinido, y tiene que
comportarse de una manera diferente a la división de rutina. También está claro que
esta nueva división debería plantear su propia excepción, diferente de la
incorporada ZeroDivisionError, pero es razonable suponer que, en algunas
circunstancias, tu (o el usuario de tu aritmética) pueden tratar todas las divisiones
entre cero de la misma manera.

Demandas como estas pueden cumplirse en la forma presentada en el editor. Mira el


código y analicémoslo:

 Hemos definido nuestra propia excepción, llamada  MyZeroDivisionError ,


derivada de la incorporada  ZeroDivisionError . Como puedes ver, hemos
decidido no agregar ningún componente nuevo a la clase.

En efecto, una excepción de esta clase puede ser, dependiendo del punto de
vista deseado, tratada como una simple excepción ZeroDivisionError, o
puede ser considerada por separado.

 La función  doTheDivision()  lanza una


excepción  MyZeroDivisionError  o  ZeroDivisionError , dependiendo del
valor del argumento.

La función se invoca cuatro veces en total, mientras que las dos primeras
invocaciones se manejan utilizando solo una rama  except  (la más general), las
dos últimas invocan dos ramas diferentes, capaces de distinguir las
excepciones (no lo olvides: el orden de las ramas hace una diferencia
fundamental).
Cómo crear tu propia excepción: continuación
Cuando vas a construir un universo completamente nuevo lleno de criaturas
completamente nuevas que no tienen nada en común con todas las cosas familiares,
es posible que desees construir tu propia estructura de excepciones. Por ejemplo,
si trabajas en un gran sistema de simulación destinado a modelar las actividades de
un restaurante de pizza, puede ser conveniente formar una jerarquía de excepciones
por separado. Puedes comenzar a construirla definiendo una excepción general
como una nueva clase base para cualquier otra excepción especializada. Lo hemos
hecho de la siguiente manera:

class PizzaError(Exception):
def __init__(self, pizza, mensaje):
Exception.__init__(mensaje)
self.pizza = pizza

Nota: vamos a recopilar más información específica aquí de lo que recopila


una Excepción regular, entonces nuestro constructor tomará dos argumentos:

 Uno que especifica una pizza como tema del proceso.


 Otro que contiene una descripción más o menos precisa del problema.

Como puedes ver, pasamos el segundo parámetro al constructor de la superclase y


guardamos el primero dentro de nuestra propiedad.

Un problema más específico (como un exceso de queso) puede requerir una


excepción más específica. Es posible derivar la nueva clase de la ya
definida  PizzaError , como hemos hecho aquí:

class DemasiadoQuesoError(PizzaError):
def __init__(self, pizza, queso, mensaje):
PizzaError._init__(self, pizza, mensaje)
self.queso = queso

La excepción  DemasiadoQuesoError  necesita más información que la excepción


regular  PizzaError , así que lo agregamos al constructor, el nombre  queso  es
entonces almacenado para su posterior procesamiento.
Cómo crear tu propia excepción: continuación
Mira el código en el editor. Combinamos las dos excepciones previamente definidas y
las aprovechamos para que funcionen en un pequeño ejemplo. Una de ellas es
lanzada dentro de la función  hacerPizza()  cuando ocurra cualquiera de estas dos
situaciones erróneas: una solicitud de pizza incorrecta o una solicitud de una pizza con
demasiado queso.

Nota:

 El remover la rama que comienza con  except DemasiadoQuesoError  hará


que todas las excepciones que aparecen se clasifiquen como  PizzaError .
 El remover la rama que comienza con  except PizzaError  provocará que la
excepción  DemasiadoQuesoError  no pueda ser manejada, y hará que el
programa finalice.

La solución anterior, aunque elegante y eficiente, tiene una debilidad importante.


Debido a la manera algo fácil de declarar los constructores, las nuevas excepciones no
se pueden usar tal cual, sin una lista completa de los argumentos requeridos.
Eliminaremos esta debilidad estableciendo valores predeterminados para todos
los parámetros del constructor. Observa:
Generadores, dónde encontrarlos
Generador - ¿Con qué asocias esta palabra? Quizás se refiere a algún dispositivo
electrónico. O tal vez se refiere a una máquina pesada diseñada para producir energía
eléctrica u otra cosa.

Un generador de Python es un fragmento de código especializado capaz de


producir una serie de valores y controlar el proceso de iteración. Esta es la razón
por la cual los generadores a menudo se llaman iteradores, y aunque hay quienes
pueden encontrar una diferencia entre estos dos, aquí los trataremos como uno
mismo.

Puede que no te hayas dado cuenta, pero te has topado con generadores muchas,
muchas veces antes. Echa un vistazo al fragmento de código:

for i in range(5):

print(i)

La función  range()  es un generador, la cual también es un iterador.

¿Cuál es la diferencia?

Una función devuelve un valor bien definido, el cual, puede ser el resultado de una
evaluación compleja, por ejemplo, de un polinomio, y se invoca una vez, solo una vez.

Un generador devuelve una serie de valores, y en general, se invoca (implícitamente)


más de una vez.

En el ejemplo, el generador  range()  se invoca seis veces, proporcionando cinco


valores de cero a cuatro.

El proceso anterior es completamente transparente. Vamos a arrojar algo de luz sobre


el. Vamos a mostrarte el protocolo iterador.
Generadores, dónde encontrarlos: continuación
El protocolo iterador es una forma en que un objeto debe comportarse para
ajustarse a las reglas impuestas por el contexto de las sentencias  for  e  in . Un
objeto conforme al protocolo iterador se llama iterador.

Un iterador debe proporcionar dos métodos:

 __iter__()  el cual debe devolver el objeto en sí y que se invoca una vez (es
necesario para que Python inicie con éxito la iteración).
 __next__()  el cual debe devolver el siguiente valor (primero, segundo, etc.)
de la serie deseada: será invocado por las sentencias  for / in  para pasar a la
siguiente iteración; si no hay más valores a proporcionar, el método
deberá lanzar la excepción  StopIteration .

¿Suena extraño? De ningúna manera. Mira el ejemplo en el editor.

Hemos creado una clase capaz de iterar a través de los primeros  n  valores (donde  n  es
un parámetro del constructor) de los números de Fibonacci.

Permítenos recordarte: los números de Fibonacci(Fibi) se definen de la siguiente


manera:

Fib1  = 1
Fib2  = 1
Fibi  = Fibi-1  + Fibi-2

En otras palabras:

 Los primeros dos números de la serie Fibonacci son 1.


 Cualquier otro número de Fibonacci es la suma de los dos anteriores (por
ejemplo, Fib3 = 2, Fib4 = 3, Fib5 = 5, y así sucesivamente).

Vamos a ver el código:

 Líneas 2 a 6: el constructor de la clase imprime un mensaje (lo usaremos para


rastrear el comportamiento de la clase), se preparan algunas variables:
( __n  para almacenar el límite de la serie,  __i  para rastrear el número actual de
la serie Fibonacci, y  __p1  junto con  __p2  para guardar los dos números
anteriores).

 Líneas 8 a 10: el método  __iter__  está obligado a devolver el objeto iterador


en sí mismo; su propósito puede ser un poco ambiguo aquí, pero no hay
misterio; trata de imaginar un objeto que no sea un iterador (por ejemplo, es
una colección de algunas entidades), pero uno de sus componentes es un
iterador capaz de escanear la colección; el método  __iter__  debe extraer el
iterador y confiarle la ejecución del protocolo de iteración; como puedes
ver, el método comienza su acción imprimiendo un mensaje.

 Líneas 12 a 21: el método  __next__  es responsable de crear la secuencia; es


algo largo, pero esto debería hacerlo más legible; primero, imprime un
mensaje, luego actualiza el número de valores deseados y, si llega al final de la
secuencia, el método interrumpe la iteración al generar la excepción
StopIteration; el resto del código es simple y refleja con precisión la definición
que te mostramos anteriormente.

 Las líneas 23 y 24 hacen uso del iterador.

Generadores, dónde encontrarlos: continuación


El ejemplo muestra una solución donde el objeto iterador es parte de una clase
más compleja.

El código no es sofisticado, pero presenta el concepto de una manera clara.

Echa un vistazo al código en el editor.


Hemos puesto el iterador  Fib  dentro de otra clase (podemos decir que lo hemos
compuesto dentro de la clase  Class ). Se instancia junto con el objeto de  Class .

El objeto de la clase se puede usar como un iterador cuando (y solo cuando) responde
positivamente a la invocación  __iter__  - esta clase puede hacerlo, y si se invoca de
esta manera, proporciona un objeto capaz de obedecer el protocolo de iteración.

Es por eso que la salida del código es la misma que anteriormente, aunque el objeto
de la clase  Fib  no se usa explícitamente dentro del contexto del bucle  for .

La sentencia yield
El protocolo iterador no es difícil de entender y usar, pero también es
indiscutible que el protocolo es bastante inconveniente. La principal
molestia que tiene es que necesita guardar el estado de la iteración en
las invocaciones subsequentes de  __iter__ . Por ejemplo, el iterador  Fib  se
ve obligado a almacenar con precisión el lugar en el que se detuvo la última
invocación (es decir, el número evaluado y los valores de los dos elementos
anteriores). Esto hace que el código sea más grande y menos comprensible. Es
por eso que Python ofrece una forma mucho más efectiva, conveniente y
elegante de escribir iteradores.

El concepto se basa fundamentalmente en un mecanismo muy específico


proporcionado por la palabra clave reservada  yield . Se puede ver a la palabra
clave reservada  yield  como un hermano más inteligente de la
sentencia  return , con una diferencia esencial. Echa un vistazo a esta función:

def fun(n):

for i in range(n):

return i

Se ve extraño, ¿no? Está claro que el bucle  for  no tiene posibilidad de terminar
su primera ejecución, ya que el  return  lo romperá irrevocablemente. Además,
invocar la función no cambiará nada: el bucle  for  comenzará desde cero y se
romperá inmediatamente.

Podemos decir que dicha función no puede guardar y restaurar su estado en


invocaciones posteriores. Esto también significa que una función como esta no
se puede usar como generador.

Hemos reemplazado exactamente una palabra en el código, ¿puedes verla?

def fun(n):

for i in range(n):

yield i

Hemos puesto  yield  en lugar de  return . Esta pequeña enmienda convierte la
función en un generador, y el ejecutar la sentencia  yield  tiene algunos
efectos muy interesantes.

Primeramente, proporciona el valor de la expresión especificada después de la


palabra clave reservada  yield , al igual que  return , pero no pierde el estado de
la función.

Todos los valores de las variables están congelados y esperan la próxima


invocación, cuando se reanuda la ejecución (no desde cero, como ocurre
después de un  return ).

Hay una limitación importante: dicha función no debe invocarse


explícitamente ya que no es una función; es un objeto generador.

La invocación devolverá el identificador del objeto, no la serie que


esperamos del generador.

Debido a las mismas razones, la función anterior (la que tiene el  return ) solo se
puede invocar explícitamente y no se debe usar como generador.
Cómo construir un generador:
Permítenos mostrarte el nuevo generador en acción.

Así es como podemos usarlo:

def fun(n):

for i in range(n):

yield i

for v in fun(5):

print(v)

¿Puedes adivinar la salida?

Revisar

4
Cómo construir tu propio generador
¿Qué pasa si necesitas un generador para producir las primeras n potencias de  2 ?

Nada difícil. Solo mira el código en el editor.

¿Puedes adivinar la salida? Ejecuta el código para verificar tus conjeturas.

Los generadores también pueden usarse dentro de listas de comprensión, como


aqui:

La función  list()  puede transformar una serie de invocaciones de generador


subsequentes en una lista real:

Nuevamente, intenta predecir el


resultado y ejecuta el código para verificar tus predicciones.Además, el contexto
creado por el operador  in  también te permite usar un generador.

El ejemplo muestra cómo hacerlo:


Ahora veamos un Generador de números de la serie Fibonacci implementando lo
anterior.
Más sobre comprensión de listas
Debes poder recordar las reglas que rigen la creación y el uso de un fenómeno de
Python llamado comprensión de listas: una forma simple de crear listas y sus
contenidos.

En caso de que lo necesites, te proporcionamos un recordatorio en el editor.

Existen dos partes dentro del código, ambas crean una lista que contiene algunas de
las primeras potencias naturales de diez.

La primer parte utiliza una forma rutinaria del bucle  for , mientras que la segunda
hace uso de la comprensión de listas y construye la lista en el momento, sin necesidad
de un bucle o cualquier otro código.

Pareciera que la lista se crea dentro de sí misma; esto es falso, ya que Python tiene
que realizar casi las mismas operaciones que en la primera parte, pero el segundo
formalismo es simplemente más elegante y le evita al lector cualquier detalle
innecesario.

El ejemplo genera dos líneas idénticas que contienen el siguiente texto:

[1, 10, 100, 1000, 10000, 100000]

Ejecuta el código para verificar si tenemos razón.

Más sobre comprensión de listas: continuación


Hay una sintaxis muy interesante que queremos mostrarte ahora. Su usabilidad no se
limita a la comprensión de listas.

Es una expresión condicional: una forma de seleccionar uno de dos valores


diferentes en función del resultado de una expresión booleana.

Observa :

expresión_uno if condición else expresión_dos

Puede parecer un poco sorprendente a primera vista, pero hay que tener en cuenta
que no es una instrucción condicional. Además, no es una instrucción en lo
absoluto. Es un operador.

El valor que proporciona es expresión_uno cuando la condición es  True  (verdadero),


y expresión_dos cuando sea falso.

Un buen ejemplo te dirá más. Mira el código en el editor.

El código llena una lista con  unos  y  ceros , si el índice de un elemento particular es
impar, el elemento se establece en  0 , y a  1  de lo contrario.

¿Simple? Quizás no a primera vista. ¿Elegante? Indiscutiblemente.

¿Se puede usar el mismo truco dentro de una lista de comprensión? Sí, por supuesto.

Más sobre comprensión de listas: continuación


Mira el ejemplo en el editor.

Compacidad y elegancia: estas dos palabras vienen a la mente al mirar el código.


Entonces, ¿qué tienen en común, generadores y listas de comprensión? ¿Hay alguna
conexión entre ellos? Sí. Una conexión algo suelta, pero inequívoca.

Solo un cambio puede convertir cualquier comprensión en un generador.

Ahora mira el código a continuación y ve si puedes encontrar el detalle que convierte


una comprensión de la lista en un generador:

Son
los paréntesis. Los
corchetes hacen una
comprensión, los
paréntesis hacen un generador.

El código, cuando se ejecuta, produce dos líneas idénticas:

¿Cómo puedes saber que la segunda asignación crea un generador, no una lista?

Hay algunas pruebas que podemos mostrarte. Aplica la función  len()  a ambas
entidades.

len(lst)  dará como resultado  10 , claro y predecible,  len(genr)  provocará una


excepción y verás el siguiente mensaje:

TypeError: object of type 'generator' has no len()

Por supuesto, guardar la lista o el generador no es necesario; puedes crearlos


exactamente en el lugar donde los necesites, como aquí:
Nota: la misma apariencia de la salida no significa que ambos bucles funcionen de la
misma manera. En el primer bucle, la lista se crea (y se itera) como un todo; en
realidad, existe cuando se ejecuta el bucle.

En el segundo bucle, no hay ninguna lista, solo hay valores subsequentes producidos
por el generador, uno por uno.

Realiza tus propios experimentos.

La función lambda
La función  lambda  es un concepto tomado de las matemáticas, más
específicamente, de una parte llamada el cálculo Lambda, pero estos dos
fenómenos no son iguales.

Los matemáticos usan el cálculo Lambda en sistemas formales conectados con:


la lógica, la recursividad o la demostrabilidad de teoremas. Los programadores
usan la función  lambda  para simplificar el código, hacerlo más claro y fácil de
entender.

Una función  lambda  es una función sin nombre (también puedes llamarla una
función anónima). Por supuesto, tal afirmación plantea inmediatamente la
pregunta: ¿cómo se usa algo que no se puede identificar?

Afortunadamente, no es un problema, ya que se puede mandar llamar dicha


función si realmente se necesita, pero, en muchos casos la
función  lambda  puede existir y funcionar mientras permanece completamente
de incógnito.

La declaración de la función  lambda  no se parece a una declaración de función


normal; compruébalo tu mismo:

lambda parámetros: expresión

Tal cláusula devuelve el valor de la expresión al tomar en cuenta el


valor del argumento  lambda  actual.

Como de costumbre, un ejemplo será útil. Nuestro ejemplo usa tres


funciones  lambda , pero con nombres. Analizalo cuidadosamente:

dos = lambda : 2
cuadrado = lambda x : x * x
potencia = lambda x, y : x ** y

for a in range(-2, 3):


print(cuadrado(a), end=" ")
print(potencia(a, dos()))

Vamos a analizarlo:

 La primer  lambda  es una función anónima sin parametros que siempre


devuelve un  2 . Como se ha asignado a una variable llamada  dos ,
podemos decir que la función ya no es anónima, y se puede usar su
nombre para invocarla.

 La segunda es una función anónima de un parámetro que devuelve


el valor de su argumento al cuadrado. Se ha nombrado también como
tal.

 La tercer  lambda  toma dos parametros y devuelve el valor del primero


elevado al segundo. El nombre de la variable que lleva la  lambda  habla
por si mismo.
El programa produce el siguiente resultado:

4 4
1 1
0 0
1 1
4 4

Este ejemplo es lo suficientemente claro como para mostrar cómo se declaran


las funciones  lambda  y cómo se comportan, pero no dice nada acerca de por
qué son necesarias y para qué se usan, ya que se pueden reemplazar con
funciones de Python de rutina.

¿Cómo usar lambdas y para qué?


La parte más interesante de usar lambdas aparece cuando puedes usarlas en su
forma pura - como partes anónimas de código destinadas a evaluar un resultado.

Imagina que necesitamos una función (la nombraremos  imprimirfuncion ) que


imprime los valores de una (otra) función dada para un conjunto de argumentos
seleccionados. Queremos que  imprimirfuncion  sea universal - debería aceptar un
conjunto de argumentos incluidos en una lista y una función a ser evaluada, ambos
como argumentos; no queremos codificar nada. Mira el ejemplo en el editor. Así es
como hemos implementado la idea.

Analicémoslo. La función  imprimirfuncion()  toma dos parámetros:

 El primero, una lista de argumentos para los que queremos imprimir los
resultados.
 El segundo, una función que debe invocarse tantas veces como el número de
valores que se recopilan dentro del primer parámetro.

Nota: También hemos definido una función llamada  poli()  - esta es la función cuyos
valores vamos a imprimir. El cálculo que realiza la función no es muy sofisticado: es el
polinomio (de ahí su nombre) de la forma:

f(x) = 2x2 - 4x + 2

El nombre de la función se pasa a  imprimirfuncion()  junto con un conjunto de cinco


argumentos diferentes: el conjunto está construido con una cláusula de comprensión
de la lista.

El código imprime las siguientes líneas:

f(-2)=18
f(-1)=8
f(0)=2
f(1)=0
f(2)=2

¿Podemos evitar definir la función  poli() , ya que no la vamos a usar más de una vez?
Sí, podemos: este es el beneficio que puede aportar una función lambda.

Mira el ejemplo de abajo. ¿Puedes ver la diferencia?

La
función  imprimirfunc
ion() se ha mantenido

exactamente igual, pero no hay una función  poli() . Ya no la necesitamos, ya que el


polinomio ahora está directamente dentro de la invocación de la
función  imprimirfuncion()  en forma de una lambda definida de la siguiente
manera:  lambda x: 2 * x**2 - 4 * x + 2 .

El código se ha vuelto más corto, más claro y más legible.

Permítenos mostrarte otro lugar donde las lambdas pueden ser útiles. Comenzaremos
con una descripción de  map() , una función de Python incorporada. Su nombre no es
demasiado descriptivo, su idea es simple y la función en sí es muy utilizable.
Lambdas y la función map()
En el más simple de todos los casos posibles, la función  map()  toma dos argumentos:

 Una función.
 Una lista.
map(función, lista)

La descripción anterior está extremadamente simplificada, ya que:

 El segundo argumento  map()  puede ser cualquier entidad que se pueda iterar
(por ejemplo, una tupla o un generador).
 map()  puede aceptar más de dos argumentos.

La función  map()  aplica la función pasada por su primer argumento a todos los
elementos de su segundo argumento y devuelve un iterador que entrega todos
los resultados de funciones posteriores. Puedes usar el iterador resultante en un
bucle o convertirlo en una lista usando la función  list() .

¿Puedes ver un papel para una lambda aquí?

Observa el código en el editor: hemos usado dos lambdas en él.

Esta es la explicación:

 Se construye la  lista1  con valores del  0  al  4 .


 Después, se utiliza  map  junto con la primer  lambda  para crear una nueva lista
en la que todos los elementos han sido evaluados como  2  elevado a la
potencia tomada del elemento correspondiente de  lista1 .
 lista2  es entonces impresa.
 En el siguiente paso, se usa nuevamente la función  map()  para hacer uso del
generador que devuelve, e imprimir directamente todos los valores que
entrega; como puedes ver, hemos usado el segundo  lambda  aquí - solo eleva al
cuadrado cada elemento de  lista2 .

Intenta imaginar el mismo código sin lambdas. ¿Sería mejor? Es improbable.


Lambdas y la función filter()
Otra función de Python que se puede embellecer significativamente mediante la
aplicación de una lambda es  filter() .

Espera el mismo tipo de argumentos que  map() , pero hace algo diferente - filtra su
segundo argumento mientras es guiado por direcciones que fluyen desde la
función especificada en el primer argumento (la función se invoca para cada
elemento de la lista, al igual que en  map()  ).

Los elementos que regresan  True  de la función pasan el filtro - los otros son
rechazados.

El ejemplo en el editor muestra la función  filter()  en acción.

Nota: hemos hecho uso del módulo  random  para inicializar el generador de números
aleatorios (que no debe confundirse con los generadores de los que acabamos de
hablar) con la función  seed() , para producir cinco valores enteros aleatorios de  -
10  a  10  usando la función  randint() .

Luego se filtra la lista y solo se aceptan los números que son pares y mayores que
cero.

Por supuesto, no es probable que recibas los mismos resultados, pero así es como se
veían nuestros resultados:

[6, 3, 3, 2, -7]

[6, 2]
Una breve explicación de cierres
Comencemos con una definición: cierres es una técnica que permite almacenar
valores a pesar de que el contexto en el que se crearon ya no existe..
¿Complicado? Un poco.

Analicemos un ejemplo simple:

def exterior(par):
loc = par

var = 1
exterior(var)

print(var)
print(loc)

El ejemplo es obviamente erróneo.

Las dos últimas líneas provocarán una excepción NameError - ni  par  ni  loc  son
accesibles fuera de la función. Ambas variables existen cuando y solo cuando la
función  exterior()  esta siendo ejecutada.

Mira el ejemplo en el editor. Hemos modificado el código significativamente.

Hay un elemento completamente nuevo - una función (llamada  interior ) dentro de


otra función (llamada  exterior ).

¿Como funciona? Como cualquier otra función excepto por el hecho de


que  interior()  solo se puede invocar desde dentro de  exterior() . Podemos decir
que  interior()  es una herramienta privada de  exterior() , ninguna otra parte del
código la puede acceder.

Observa cuidadosamente:

 La función  interior()  devuelve el valor de la variable accesible dentro de su


alcance, ya que  interior()  puede utilizar cualquiera de las entidades a
disposición de  exterior() .
 La función  exterior()  devuelve la función  interior()  por si misma; mejor
dicho, devuelve una copia de la función  interior()  al momento de la
invocación de la función  exterior() ; la función congelada contiene su
entorno completo, incluido el estado de todas las variables locales, lo que
también significa que el valor de  loc  se retiene con éxito,
aunque  exterior()  ya ha dejado de existir.

En efecto, el código es totalmente válido y genera:

La función devuelta durante la invocación de  exterior()  es un cierre.


Una breve explicación de cierres: continuación
Un cierre se debe invocar exactamente de la misma manera en que se ha
declarado.

En el ejemplo anterior (vea el código a continuación):

def exterior(par):
loc = par
def interior():
return loc
return interior

var = 1
fun = exterior(var)
print(fun()))

La función  interior()  no tenía parámetros, por lo que tuvimos que invocarla sin
argumentos. Ahora mira el código en el editor. Es totalmente posible declarar un
cierre equipado con un número arbitrario de parámetros, por ejemplo, al igual que
la función  potencia() . Esto significa que el cierre no solo utiliza el ambiente
congelado, sino que también puede modificar su comportamiento utilizando
valores tomados del exterior. Este ejemplo muestra una circunstancia más
interesante: puedes crear tantos cierres como quieras usando el mismo código.
Esto se hace con una función llamada  crearcierre() . Nota:

 El primer cierre obtenido de  crearcierre()  define una herramienta que eleva
al cuadrado su argumento.
 El segundo está diseñado para elevar el argumento al cubo.

Es por eso que el código produce el siguiente resultado:

0 0 0
1 1 1
2 4 8
3 9 27
4 16 64

Realiza tus propias pruebas.


Accediendo a archivos desde el código en Python
Uno de los problemas más comunes en el trabajo del desarrollador es procesar
datos almacenados en archivos que generalmente se almacenan
físicamente utilizando dispositivos de almacenamiento: discos duros, ópticos,
de red o de estado sólido.

Es fácil imaginar un programa que clasifique 20 números, y es igualmente fácil


imaginar que el usuario de este programa ingrese estos veinte números
directamente desde el teclado.

Es mucho más difícil imaginar la misma tarea cuando hay 20,000 números para
ordenar, y no existe un solo usuario que pueda ingresar estos números sin
cometer un error.

Es mucho más fácil imaginar que estos números se almacenan en el archivo


que lee el programa. El programa clasifica los números y no los envía a la
pantalla, sino que crea un nuevo archivo y guarda la secuencia ordenada de
números allí.

Si queremos implementar una base de datos simple, la única forma de


almacenar la información entre ejecuciones del programa es guardarla en un
archivo (o archivos si tu base de datos es más compleja).

Es un principio que cualquier problema de programación no simple se basa en


el uso de archivos, ya sea que procese imágenes (almacenadas en archivos),
multiplique matrices (almacenadas en archivos) o calcule salarios e impuestos
(lectura de datos almacenados en archivos).

Puedes preguntarte por qué hemos esperado hasta ahora para mostrarte esto.

La respuesta es muy simple: la forma en que Python accede y procesa los


archivos se implementa utilizando un conjunto consistente de objetos. No hay
mejor momento para hablar de esto.
Nombres de archivos
Los diferentes sistemas operativos pueden tratar a los archivos de diferentes
maneras. Por ejemplo, Windows usa una convención de nomenclatura diferente
a la adoptada en los sistemas Unix/Linux.

Si utilizamos la noción de un nombre de archivo canónico (un nombre que


define de forma exclusiva la ubicación del archivo, independientemente de su
nivel en el árbol de directorios), podemos darnos cuenta de que estos nombres
se ven diferentes en Windows y en Unix/Linux:

Como puedes ver, los sistemas derivados de Unix/Linux no usan la letra de la


unidad de disco (p. Ejemplo,  C: ) y todos los directorios crecen desde un
directorio raíz llamado  / , mientras que los sistemas Windows reconocen el
directorio raíz como  \ .

Además, los nombres de archivo de sistemas Unix/Linux distinguen entre


mayúsculas y minúsculas. Los sistemas Windows almacenan mayúsculas y
minúsculas en el nombre del archivo, pero no distinguen entre ellas.

Esto significa que estas dos cadenas:

EsteEsElNombreDelArchivo
y
esteeselnombredelarchivo

describen dos archivos diferentes en sistemas Unix/Linux, pero tienen el mismo


nombre para un solo archivo en sistemas Windows.
La diferencia principal y más llamativa es que debes usar dos separadores
diferentes para los nombres de directorio:  \  en Windows y  /  en
Unix/Linux.

Esta diferencia no es muy importante para el usuario normal, pero es muy


importante al escribir programas en Python.

Para entender por qué, intenta recordar el papel muy específico que
desempeña  \  dentro de las cadenas en Python.

Nombres de Archivo: continuación


Supongamos que estás interesado en un archivo en particular ubicado en el
directorio dir, y con el nombre de archivo.

Supongamos también que deseas asignar a una cadena el nombre del archivo.

En sistemas Unix/Linux, se ve de la siguiente manera:

nombre = "/dir/archivo"

Pero si intentas codificarlo para el sistema Windows:

nombre = "\dir\archivo"

obtendrás una sorpresa desagradable: Python generará un error o la ejecución


del programa se comportará de manera extraña, como si el nombre del archivo
se hubiera distorsionado de alguna manera.

De hecho, no es extraño en lo absoluto, pero es bastante obvio y natural.


Python usa la  \  como un caracter de escape (como  \n ).

Esto significa que los nombres de archivo de Windows deben escribirse de la


siguiente manera:

nombre = "\\dir\\archivo"

Afortunadamente, también hay una solución más. Python es lo suficientemente


inteligente como para poder convertir diagonales en diagonales invertidas cada
vez que descubre que el sistema operativo lo requiere.

Esto significa que cualquiera de las siguientes asignaciones:


nombre = "/dir/archivo"

nombre = "c:/dir/archivo"

funcionará también con Windows.

Cualquier programa escrito en Python (y no solo en Python, porque esa


convención se aplica a prácticamente todos los lenguajes de programación) no
se comunica con los archivos directamente, sino a través de algunas entidades
abstractas que se nombran de manera diferente en los distintos lenguajes o
entornos, los términos más utilizados son handles (un tipo de puntero
inteligente) o streams (una especie de canal) (los usaremos como
sinónimos aquí).

El programador, que tiene un conjunto de funciones y métodos, puede realizar


ciertas operaciones en el stream, que afectan los archivos reales utilizando
mecanismos contenidos en el núcleo del sistema operativo.

De esta forma, puedes implementar el proceso de acceso a cualquier archivo,


incluso cuando el nombre del archivo es desconocido al momento de escribir el
programa.

Las operaciones realizadas con el stream abstracto reflejan las actividades


relacionadas con el archivo físico.
Para conectar (vincular) el stream con el archivo, es necesario realizar una
operación explícita.

La operación de conectar un stream con un archivo es llamada abrir el


archivo, mientras que desconectar el enlace se denomina cerrar el archivo.

Por lo tanto, la conclusión es que la primera operación realizada en el stream es


siempre  open (abrir)  y la ultima es  close (cerrar) . El programa, en efecto,
es libre de manipular el stream entre estos dos eventos y manejar el archivo
asociado. Esta libertad está limitada por las características físicas del archivo y
la forma en que se abrió el archivo. Digamos nuevamente que la apertura del
stream puede fallar, y puede ocurrir debido a varias razones: la más común es
la falta de un archivo con un nombre específico.

También puede suceder que el archivo físico exista, pero el programa no puede
abrirlo. También existe el riesgo de que el programa haya abierto demasiados
streams, y el sistema operativo específico puede no permitir la apertura
simultánea de más de n archivos (por ejemplo, 200).

Un programa bien escrito debe detectar estas aperturas fallidas y reaccionar en


consecuencia.

Streams para Archivos


La apertura del stream no solo está asociada con el archivo, sino que también
se debe declarar la manera en que se procesará el stream. Esta declaración se
llama un open mode (modo abierto).

Si la apertura es exitosa, el programa solo podrá realizar las operaciones


que sean consistentes con el modo abierto declarado.

Hay dos operaciones básicas a realizar con el stream:

 Lectura del stream: las porciones de los datos se recuperan del archivo


y se colocan en un área de memoria administrada por el programa (por
ejemplo, una variable).
 Escritura del stream: Las porciones de los datos de la memoria (por
ejemplo, una variable) se transfieren al archivo.

Hay tres modos básicos utilizados para abrir un stream:

 Modo Lectura: un stream abierto en este modo permite solo


operaciones de lectura; intentar escribir en la transmisión provocará
una excepción (la excepción se llama UnsupportedOperation, la cual
hereda el OSError y el ValueError, y proviene del módulo io).
 Modo Escritura: un stream abierto en este modo permite solo
operaciones de escritura; intentar leer el stream provocará la
excepción mencionada anteriormente.
 Modo Actualizar: un stream abierto en este modo permite tanto
lectura como escritura.
Antes de discutir cómo manipular los streams, te debemos una explicación. El
stream se comporta casi como una grabadora.

Cuando lees algo de un stream, un cabezal virtual se mueve sobre la


transmisión de acuerdo con el número de bytes transferidos desde el stream.

Cuando escribes algo en el stream el mismo cabezal se mueve a lo largo del


stream registrando los datos de la memoria.

Siempre que hablemos de leer y escribir en el stream, trata de imaginar esta


analogía. Los libros de programación se refieren a este mecanismo como
la posición actual del archivo, aquí también usaremos este término.

Ahora es necesario mostrarte el objeto responsable de representar los streams


en los programas.

Manejo de Archivos
Python supone que cada archivo está oculto detrás de un objeto de una
clase adecuada.

Por supuesto, es difícil no preguntar cómo interpretar la palabra adecuada.

Los archivos se pueden procesar de muchas maneras diferentes: algunos


dependen del contenido del archivo, otros de las intenciones del programador.

En cualquier caso, diferentes archivos pueden requerir diferentes conjuntos de


operaciones y comportarse de diferentes maneras.

Un objeto de una clase adecuada es creado cuando abres el archivo y lo


aniquilas al momento de cerrarlo.

Entre estos dos eventos, puedes usar el objeto para especificar qué operaciones
se deben realizar en un stream en particular. Las operaciones que puedes usar
están impuestas por la forma en que abriste el archivo.

En general, el objeto proviene de una de las clases que se muestran aquí:


Nota: nunca se utiliza el constructor para dar vida a estos objetos. La unica
forma de obtenerlos es invocar la función llamada  open() .

La función analiza los argumentos proporcionados y crea automáticamente el


objeto requerido.

Si deseas deshacerte del objeto, invoca el método denominado  close() .

La invocación cortará la conexión con el objeto y el archivo, y eliminará el


objeto.

Para nuestros propósitos, solo nos ocuparemos de los streams representados


por los objetos  BufferIOBase  y  TextIOBase . Entenderás por qué pronto.

Manejo de Archivos: continuación


Debido al tipo de contenido de los streams, todos se dividen en tipo texto y
binario.

Los streams de texto están estructurados en líneas; es decir, contienen


caracteres tipográficos (letras, dígitos, signos de puntuación, etc.) dispuestos
en filas (líneas), como se ve a simple vista cuando se mira el contenido del
archivo en el editor.

Este tipo de archivo es escrito (o leído) principalmente carácter por carácter, o


línea por línea.

Los streams binarios no contienen texto, sino una secuencia de bytes de


cualquier valor. Esta secuencia puede ser, por ejemplo, un programa
ejecutable, una imagen, un audio o un videoclip, un archivo de base de datos,
etc.
Debido a que estos archivos no contienen líneas, las lecturas y escrituras se
relacionan con porciones de datos de cualquier tamaño. Por lo tanto, los datos
se leen y escriben byte a byte, o bloque a bloque, donde el tamaño del bloque
generalmente varía de uno a un valor elegido arbitrariamente.

Ahora viene un problema pequeño. En los sistemas Unix/Linux, los extremos de


la línea están marcados por un solo carácter llamado  LF  (código ASCII 10)
designado en los programas de Python como  \n .

Otros sistemas operativos, especialmente los derivados del sistema prehistórico


CP/M (que también aplica a los sistemas de la familia Windows) utilizan una
convención diferente: el final de la línea está marcada por un par de
caracteres,  CR  y  LF  (códigos ASCII 13 y 10) los cuales se puede codificar
como  \r\n .

Esta ambigüedad puede causar varias consecuencias desagradables.

Si creas un programa responsable de procesar un archivo de texto y está


escrito para Windows, puedes reconocer los extremos de las líneas al encontrar
los caracteres  \r\n , pero si el mismo programa se ejecuta en un entorno
Unix/Linux será completamente inútil, y viceversa: el programa escrito para
sistemas Unix/Linux podría ser inútil en Windows.

Estas características indeseables del programa, que impiden o dificultan el uso


del programa en diferentes entornos, se denomina falta de portabilidad.

Del mismo modo, el rasgo del programa que permite la ejecución en diferentes
entornos se llama portabilidad. Un programa dotado de tal rasgo se
llama programa portable.

Manejo de archivos: continuación


Dado que los problemas de portabilidad eran (y siguen siendo) muy graves, se
tomó la decisión de resolver definitivamente el problema de una manera que no
atraiga mucho la atención del desarrollador.

Se realizó a nivel de clases, que son responsables de leer y escribir caracteres


hacia y desde el stream. Funciona de la siguiente manera:
 Cuando el stream está abierto y se recomienda que los datos en el
archivo asociado se procesen como texto (o no existe tal aviso),
se cambia al modo texto.

 Durante la lectura y escritura de líneas desde y hacia el archivo


asociado, no ocurre nada especial en el entorno Unix, pero cuando se
realizan las mismas operaciones en el entorno Windows, un proceso
llamado traducción de caracteres de nueva línea ocurre: cuando
lees una línea del archivo, cada par de caracteres  \r\n  se reemplaza con
un solo caracter  \n , y viceversa; durante las operaciones de escritura,
cada caracter  \n  se reemplaza con un par de caracteres  \r\n .

 El mecanismo es completamente transparente para el programa, el


cual puede escribirse como si estuviera destinado a procesar archivos de
texto Unix/Linux solamente; el código fuente ejecutado en un entorno
Windows también funcionará correctamente.

 Cuando el stream está abierto, su contenido se toma tal cual es, sin


ninguna conversión - no se agregan, ni se omiten bytes.

Abriendo los streams


El abrir un stream se realiza mediante una función que se puede invocar de la
siguiente manera:

stream = open(file, mode = 'r', encoding = None)


Vamos a analizarlo:

 El nombre de la función ( open ) habla por si mismo; si la apertura es


exitosa, la función devuelve un objeto stream; de lo contrario, se genera
una excepción (por ejemplo, FileNotFoundError si el archivo que vas
a leer no existe).
 El primer parámetro de la función ( file ) especifica el nombre del archivo
que se asociará al stream.
 El segundo parámetro ( mode ) especifica el modo de apertura utilizado
para el stream; es una cadena llena de una secuencia de caracteres, y
cada uno de ellos tiene su propio significado especial (más detalles
pronto).
 El tercer parámetro ( encoding ) especifica el tipo de codificación (por
ejemplo, UTF-8 cuando se trabaja con archivos de texto).
 La apertura debe ser la primera operación realizada en el stream.

Nota: el modo y los argumentos de codificación pueden omitirse; en dado caso,


se tomarán sus valores predeterminados. El modo de apertura predeterminado
es leer en modo de texto, mientras que la codificación predeterminada depende
de la plataforma utilizada.

Permítenos ahora presentarte los modos de apertura más importantes y útiles.


¿Listo?
Abriendo los streams: modos
Modo de apertura  r : lectura

 El stream será abierto en modo lectura.


 El archivo asociado con el stream debe existir y tiene que ser legible,
de lo contrario la función  open()  lanzará una excepción.

Modo de apertura  w : escritura

 El stream será abierto en modo escritura.


 El archivo asociado con el stream no necesita existir. Si no existe, se
creará; si existe, se truncará a la longitud de cero (se borrá); si la
creación no es posible (por ejemplo, debido a los permisos del sistema)
la función  open()  lanzará una excepción.

Modo de apertura  a : adjuntar

 El stream será abierto en modo adjuntar.


 El archivo asociado con el stream no necesita existir; si no existe, se
creará; si existe, el cabezal de grabación virtual se establecerá al final
del archivo (el contenido anterior del archivo permanece intacto).

Modo de apertura  r+ : leer y actualizar

 El stream será abierto en modo leer y actualizar.


 El archivo asociado con el stream debe existir y tiene que ser
escribible, de lo contrario la función  open()  lanzará una excepción.
 Se permiten operaciones de lectura y escritura en el stream.

Modo de apertura  w+ : escribir y actualizar

 El stream será abierto en modo escribir y actualizar.


 El archivo asociado con el stream no necesita existir; si no existe, se
creará; el contenido anterior del archivo permanece intacto.
 Se permiten operaciones de lectura y escritura en el stream.

Seleccionando modo de texto y modo binario


Si hay una letra  b  al final de la cadena del modo significa que el stream se debe
abrir en el modo binario.

Si la cadena del modo termina con una letra  t  el stream es abierto en modo
texto.
El modo texto es el comportamiento predeterminado que se utiliza cuando no
se especifica ya sea modo binario o texto.

Finalmente, la apertura exitosa del archivo establecerá la posición actual del


archivo (el cabezal virtual de lectura/escritura) antes del primer byte del
archivo si el modo no es  a  y después del último byte del archivo si el modo
es  a .

Modo texto Modo binario Descripción


rt rb lectura
wt wb escritura
at ab adjuntar
r+t r+b leer y actualizar
w+t w+b escribir y actualizar

EXTRA

También puedes abrir un archivo para su creación exclusiva. Puedes hacer esto
usando el modo de apertura  x . Si el archivo ya existe, la función  open()  lanzará
una excepción.
Abriendo el stream por primera vez
Imagina que queremos desarrollar un programa que lea el contenido del archivo
de texto llamado: C:\Users\User\Desktop\file.txt.

¿Cómo abrir ese archivo para leerlo? Aquí está el fragmento del código:

try:

stream = open("C:\Users\User\Desktop\file.txt", "rt")

# aqui se procesa el archivo

stream.close()

except Exception as exc:

print("No se puede abrir el archivo:", exc)

¿Que está pasando aqui?

 Hemos abierto el bloque try-except ya que queremos manejar los errores


de tiempo de ejecución suavemente.
 Se emplea la función  open()  para intentar abrir el archivo especificado
(ten en cuenta la forma en que hemos especificado el nombre del
archivo).
 El modo de apertura se define como texto para leer (como texto es la
configuración predeterminada, podemos omitir la  t  en la cadena de
modo).
 En caso de éxito obtenemos un objeto de la función  open()  y lo
asignamos a la variable del stream.
 Si  open()  falla, manejamos la excepción imprimiendo la información
completa del error (es bueno saber qué sucedió exactamente).

Streams pre-abiertos
Dijimos anteriormente que cualquier operación del stream debe estar precedida
por la invocación de la función  open() . Hay tres excepciones bien definidas a
esta regla.

Cuando comienza nuestro programa, los tres streams ya están abiertos y no


requieren ninguna preparación adicional. Además, tu programa puede usar
estos streams explícitamente si tienes cuidado de importar el módulo  sys :
import sys

Porque ahí es donde se coloca la declaración de estos streams.

Los nombres de los streams son:  sys.stdin ,  sys.stdout  y  sys.stderr .

Vamos a analizarlos:

 sys.stdin
o stdin (significa entrada estándar).
o El stream  stdin  normalmente se asocia con el teclado, se abre
previamente para la lectura y se considera como la fuente de
datos principal para los programas en ejecución.
o La función bien conocida  input()  lee datos de  stdin  por default.

 sys.stdout
o stdout (significa salida estándar).
o El stream  stdout  normalmente está asociado con la pantalla,
preabierta para escritura, considerada como el objetivo principal
para la salida de datos por el programa en ejecución.
o La función bien conocida  print()  envía los datos al
stream  stdout .

 sys.stderr
o stderr (significa salida de error estándar).
o El stream  stderr  normalmente está asociado con la pantalla,
preabierta para escribir, considerada como el lugar principal
donde el programa en ejecución debe enviar información sobre los
errores encontrados durante su trabajo.
o No hemos presentado ningún método para enviar datos a este
stream (lo haremos pronto, lo prometemos).
o La separación de  stdout  (resultados útiles producidos por el
programa) de  stderr  (mensajes de error, indudablemente útiles
pero no proporcionan resultados) ofrece la posibilidad de redirigir
estos dos tipos de información a los diferentes objetivos. Una
discusión más extensa sobre este tema está más allá del alcance
de nuestro curso. El manual del sistema operativo proporcionará
más información sobre estos temas.
Cerrando streams
La última operación realizada en un stream (esto no incluye a los
streams  stdin ,  stdout , y  stderr  pues no lo requieren) debe ser cerrarlo.

Esa acción se realiza mediante un método invocado desde dentro del objeto del
stream:  stream.close() .

 El nombre de la función es fácil de entender  close() , es decir cerrar.


 La función no espera argumentos; el stream no necesita estar abierto.
 La función no devuelve nada pero lanza una excepción IOError en caso
de un error.
 La mayoría de los desarrolladores creen que la función  close()  siempre
tiene éxito y, por lo tanto, no hay necesidad de verificar si ha realizado
su tarea correctamente.

Esta creencia está solo parcialmente justificada. Si el stream se abrió


para escribir y luego se realizó una serie de operaciones de escritura,
puede ocurrir que los datos enviados al stream aún no se hayan
transferido al dispositivo físico (debido a los mecanismos
de cache o buffer). Dado que el cierre del stream obliga a los búferes a
descargarse, es posible que dichas descargas fallen y, por lo
tanto,  close()  falle también.

Ya hemos mencionado fallas causadas por funciones que operan con los
streams, pero no mencionamos nada sobre cómo podemos identificar
exactamente la causa de la falla.

La posibilidad de hacer un diagnóstico existe y es proporcionada por uno de los


componentes de excepción de los streams.

Diagnosticando problemas con los streams


El objeto  IOError  está equipado con una propiedad llamada  errno  (el nombre
viene de la frase error number, número de error) y puedes accederla de la
siguiente manera:

try:
# operaciones con streams

except IOError as exc:

print(exc.errno)

El valor del atributo  errno  se puede comparar con una de las constantes
simbólicas predefinidas en módulo  errno .

Echemos un vistazo a algunas constantes seleccionadas útiles para


detectar errores de flujo:

errno.EACCES  → Permiso denegado

El error se produce cuando intentas, por ejemplo, abrir un archivo con atributos
de solo lectura para abrirlo.

errno.EBADF  → Número de archivo incorrecto

El error se produce cuando intentas, por ejemplo, operar un stream sin abrirlo.

errno.EEXIST  → Archivo existente

El error se produce cuando intentas, por ejemplo, cambiar el nombre de un


archivo con su nombre anterior.

errno.EFBIG  → Archivo demasiado grande

El error ocurre cuando intentas crear un archivo que es más grande que el
máximo permitido por el sistema operativo.

errno.EISDIR  → Es un directorio

El error se produce cuando intentas tratar un nombre de directorio como el


nombre de un archivo ordinario.

errno.EMFILE  → Demasiados archivos abiertos

El error se produce cuando intentas abrir simultáneamente más streams de los


aceptables para el sistema operativo.

errno.ENOENT  → El archivo o directorio no existe


El error se produce cuando intentas acceder a un archivo o directorio
inexistente.

errno.ENOSPC  → no queda espacio en el dispositivo

El error ocurre cuando no hay espacio libre en el dispositivo.

La lista completa es mucho más larga (incluye también algunos códigos de


error no relacionados con el procesamiento del stream).

Diagnosticando problemas con los streams:


continuación
Si eres un programador muy cuidadoso, puedes sentir la necesidad de usar una
secuencia de sentencias similar a la que se presenta a continuación:

import errno
try:
s = open("c:/users/user/Desktop/file.txt", "rt")
# el procesamiento va aquí
s.close()
except Exception as exc:
if exc.errno == errno.ENOENT:
print("El archivo no existe.")
elif exc.errno == errno.EMFILE:
print("Has abierto demasiados archivos.")
else:
printf("El número de error es:", exc.errno)

Afortunadamente, existe una función que puede simplificar el código de


manejo de errores. Su nombre es  strerror() , y proviene del
módulo  os  y espera solo un argumento: un número de error.

Su función es simple: proporciona un número de error y una cadena que


describe el significado del error.

Nota: Si pasas un código de error inexistente (un número que no está vinculado
a ningún error real), la función lanzará una excepción ValueError.

Ahora podemos simplificar nuestro código de la siguiente manera:

from os import strerror


try:

s = open("c:/users/user/Desktop/file.txt", "rt")

# el procesamiento va aquí

s.close()

except Exception as exc:

print("El archivo no se pudo abrir:", strerror(exc.errno));

Bueno. Ahora es el momento de tratar con archivos de texto y familiarizarse


con algunas técnicas básicas que puedes utilizar para procesarlos.

Procesamiento de archivos de texto


En esta lección vamos a preparar un archivo de texto simple con contenido breve y
simple.

Te mostraremos algunas técnicas básicas que puedes utilizar para leer el contenido


del archivo para poder procesarlo.

El procesamiento será muy simple: vas a copiar el contenido del archivo a la consola y
contarás todos los caracteres que el programa ha leído.

Pero recuerda: nuestra comprensión de un archivo de texto es muy estricta. Es un


archivo de texto sin formato: puede contener solo texto, sin decoraciones adicionales
(formato, diferentes fuentes, etc.).

Es por eso que debes evitar crear el archivo utilizando un procesador de texto
avanzado como MS Word, LibreOffice Writer o algo así. Utiliza los conceptos básicos
que ofrece tu sistema operativo: Bloc de notas, vim, gedit, etc.

Si tus archivos de texto contienen algunos caracteres nacionales no cubiertos por el


juego de caracteres ASCII estándar, es posible que necesites un paso adicional. La
invocación de tu función  open()  puede requerir un argumento que denote una
codificación específica del texto.

Por ejemplo, si estás utilizando un sistema operativo Unix/Linux configurado para usar
UTF-8 como una configuración de todo el sistema, la función  open()  puede verse de la
siguiente manera:

stream = open('file.txt', 'rt', encoding='utf-8')


Donde el argumento de codificación debe establecerse en un valor dentro de una
cadena que representa la codificación de texto adecuada (UTF-8, en este caso).

Consulta la documentación de tu sistema operativo para encontrar el nombre de


codificación adecuado para tu entorno.

INFORMACIÓN

A los fines de nuestros experimentos con el procesamiento de archivos que se llevan a


cabo en esta sección, vamos a utilizar un conjunto de archivos precargados (p. Ej., los
archivos tzop.txt, o text.txt) con los cuales podrás trabajar. Si deseas trabajar con
tus propios archivos localmente en tu máquina, te recomendamos que lo hagas y que
utilices un Entorno de Desarrollo para llevar a cabo tus propias pruebas.

Procesamiento de archivos de texto: continuación


La lectura del contenido de un archivo de texto se puede realizar utilizando diferentes
métodos; ninguno de ellos es mejor o peor que otro. Depende de ti cuál de ellos
prefieres y te gusta. Algunos de ellos serán a veces más prácticos y otros más
problemáticos. Se flexible. No tengas miedo de cambiar tus preferencias. El más
básico de estos métodos es el que ofrece la función  read() , la cual pudiste ver en
acción en la lección anterior. Si se aplica a un archivo de texto, la función es capaz de:

 Leer un número determinado de caracteres (incluso solo uno) del archivo y


devolverlos como una cadena.
 Leer todo el contenido del archivo y devolverlo como una cadena.
 Si no hay nada más que leer (el cabezal de lectura virtual llega al final del
archivo), la función devuelve una cadena vacía.

Comenzaremos con la variante más simple y usaremos un archivo llamado  text.txt .


El archivo contiene lo siguiente:

Lo hermoso es mejor que lo feo.


Explícito es mejor que implícito.
Simple es mejor que complejo.
Complejo es mejor que complicado.

Ahora observa el código en el editor y analicémoslo.

La rutina es bastante simple:

 Se usa el mecanismo try-except y se abre el archivo con el nombre


(text.txt en este caso).
 Intenta leer el primer caracter del archivo ( ch = s.read(1) ).
 Si tienes éxito (esto se demuestra por el resultado positivo de la
condición  while ), se muestra el caracter (nota el argumento  end= ,¡es
importante! ¡No querrás saltar a una nueva línea después de cada caracter!).
 Se actualiza el contador ( cnt ).
 Intenta leer el siguiente carácter y el proceso se repite.

Procesamiento de archivos de texto: continuación


Si estás absolutamente seguro de que la longitud del archivo es segura y puedes leer
todo el archivo en la memoria de una vez, puedes hacerlo: la función  read() , invocada
sin ningún argumento o con un argumento que se evalúa a  None , hará el trabajo por
ti.

Recuerda - el leer un archivo muy grande (en terabytes) usando este método
puede dañar tu sistema operativo.

No esperes milagros: la memoria de la computadora no se puede extender.

Observa el código en el editor. ¿Que piensas de el?

Vamos a analizarlo:

 Abre el archivo, como anteriormente se hizo.


 Lee el contenido mediante una invocación de la función  read() .
 Despues, se procesa el texto, iterando con un bucle  for  su contenido, y se
actualiza el valor del contador en cada vuelta del bucle.

El resultado será exactamente el mismo que en el ejemplo anterior.


Procesando archivos de texto: readline()
Si deseas manejar el contenido del archivo como un conjunto de líneas, no como un
montón de caracteres, el método  readline()  te ayudará con eso.

El método intenta leer una línea completa de texto del archivo, y la devuelve como
una cadena en caso de éxito. De lo contrario, devuelve una cadena vacía.

Esto abre nuevas oportunidades: ahora también puedes contar líneas fácilmente, no
solo caracteres.

Hagámos uso de ello. Observa el código en el editor.

Como puedes ver, la idea general es exactamente la misma que en los dos ejemplos
anteriores.
Procesando archivos de texto: readlines()
Otro método, que maneja el archivo de texto como un conjunto de líneas, no como
caracteres, es  readlines() .

Cuando el método  readlines() , se invoca sin argumentos, intenta leer todo el


contenido del archivo y devuelve una lista de cadenas, un elemento por línea del
archivo.

Si no estás seguro de si el tamaño del archivo es lo suficientemente pequeño y no


deseas probar el sistema operativo, puedes convencer al método  readlines()  de
leer no más de un número especificado de bytes a la vez (el valor de retorno sigue
siendo el mismo, es una lista de una cadena).

Siéntete libre de experimentar con este código de ejemplo para entender cómo


funciona el método  readlines() .

El tamaño máximo del búfer de entrada aceptado se pasa al método como


argumento.

Puedes esperar que  readlines()  procese el contenido del archivo de manera más
efectiva que  readline() , ya que puede ser invocado menos veces.

Nota: cuando no hay nada que leer del archivo, el método devuelve una lista vacía.
Úsalo para detectar el final del archivo.

Puedes esperar que al aumentar el tamaño del búfer mejore el rendimiento de


entrada, pero no hay una regla de oro para ello: intenta encontrar los valores óptimos
por ti mismo.

Observa el código en el editor. Lo hemos modificado para mostrarte cómo


usar  readlines() .

Hemos decidido usar un búfer de 15 bytes de longitud. No pienses que es una


recomendación.

Hemos utilizado ese valor para evitar la situación en la que la primera invocación
de  readlines()  consuma todo el archivo.

Queremos que el método se vea obligado a trabajar más duro y que demuestre sus
capacidades.

Existen dos bucles anidados en el código: el exterior emplea el resultado


de  readlines()  para iterar a través de él, mientras que el interno imprime las líneas
carácter por carácter.
Procesando archivos de texto: continuación
El último ejemplo que queremos presentar muestra un rasgo muy interesante del
objeto devuelto por la función  open()  en modo de texto.

Creemos que puede sorprenderte - el objeto es una instancia de la clase iterable.

¿Extraño? De ningúna manera. ¿Usable? Si, por supuesto.

El protocolo de iteración definido para el objeto del archivo es muy simple: su


método  __next__  solo devuelve la siguiente línea leída del archivo.

Además, puedes esperar que el objeto invoque automáticamente a  close()  cuando


cualquiera de las lecturas del archivo lleguen al final del archivo.

Mira el editor y ve cuán simple y claro se ha vuelto el código.

Manejando archivos de texto: write()


Escribir archivos de texto parece ser más simple, ya que hay un método que puede
usarse para realizar dicha tarea. El método se llama  write()  y espera solo un
argumento: una cadena que se transferirá a un archivo abierto (no lo olvides), el modo
de apertura debe reflejar la forma en que se transfieren los datos - escribir en un
archivo abierto en modo de lectura no tendrá éxito).
No se agrega carácter de nueva línea al argumento de  write() , por lo que debes
agregarlo tu mismo si deseas que el archivo se complete con varias líneas.

El ejemplo en el editor muestra un código muy simple que crea un archivo


llamado newtext.txt (nota: el modo de apertura  w  asegura que el archivo se creará
desde cero, incluso si existe y contiene datos) y luego pone diez líneas en él.

La cadena que se grabará consta de la palabra línea, seguida del número de línea.
Hemos decidido escribir el contenido de la cadena carácter por carácter (esto lo hace
el bucle interno  for ) pero no estás obligado a hacerlo de esta manera.

Solo queríamos mostrarte que  write()  puede operar con caracteres individuales.

El código crea un archivo con el siguiente texto:

línea #1
línea #2
línea #3
línea #4
línea #5
línea #6
línea #7
línea #8
línea #9
línea #10

¿Puedes imprimir el contenido del archivo en la consola?

Te alentamos a que pruebes el comportamiento del método  write()  localmente en


tu máquina.

Manejando archivos de texto: continuación


Mira el ejemplo en el editor. Hemos modificado el código anterior para escribir líneas
enteras en el archivo de texto.

El contenido del archivo recién creado es el mismo.


Nota: puedes usar el mismo método para escribir en el stream  stderr , pero no
intentes abrirlo, ya que siempre está abierto implícitamente.

Por ejemplo, si deseas enviar un mensaje de tipo cadena a  stderr  para distinguirlo de
la salida normal del programa, puede verse así:

import sys

sys.stderr.write("Mensaje de Error")

¿Qué es un bytearray?
Antes de comenzar a hablar sobre archivos binarios, tenemos que informarte
sobre una de las clases especializadas que Python usa para almacenar
datos amorfos.
Los datos amorfos son datos que no tienen forma específica - son solo
una serie de bytes.

Esto no significa que estos bytes no puedan tener su propio significado o que no
puedan representar ningún objeto útil, por ejemplo, gráficos de mapa de bits.

Los datos amorfos no pueden almacenarse utilizando ninguno de los medios


presentados anteriormente: no son cadenas ni listas.

Debe haber un contenedor especial capaz de manejar dichos datos.

Python tiene más de un contenedor, uno de ellos es una clase especializada


llamada bytearray - como su nombre indica, es un arreglo que contiene
bytes (amorfos).

Si deseas tener dicho contenedor, por ejemplo, para leer una imagen de mapa
de bits y procesarla de alguna manera, debes crearlo explícitamente, utilizando
uno de los constructores disponibles.

Observa:

data = bytearray(100)

Tal invocación crea un objeto bytearray capaz de almacenar diez bytes.

Nota: dicho constructor llena todo el arreglo con ceros.

Bytearrays: continuación
Bytearrays se asemejan a listas en muchos aspectos. Por ejemplo, son mutables, son
suceptibles a la función  len() , y puedes acceder a cualquiera de sus elementos
usando indexación convencional.

Existe una limitación importante - no debes establecer ningún elemento del arreglo
de bytes con un valor que no sea un entero (violar esta regla causará una
excepción TypeError) y tampoco está permitido asignar un valor fuera del rango
de 0 a 255 (a menos que quieras provocar una excepción ValueError).

Puedes tratar cualquier elemento del arreglo de bytes como un valor entero - al


igual que en el ejemplo en el editor.

Nota: hemos utilizado dos métodos para iterar el arreglo de bytes, y hemos utilizado la
función  hex()  para ver los elementos impresos como valores hexadecimales.

Ahora te vamos a mostrar cómo escribir un arreglo de bytes en un archivo binario,


como no queremos guardar su representación legible, queremos escribir una copia
uno a uno del contenido de la memoria física, byte a byte.

Bytearrays: continuación
Entonces, ¿cómo escribimos un arreglo de bytes en un archivo binario?

Observa el código en el editor. Analicémoslo:

 Primero, inicializamos  bytearray  con valores a partir de  10 ; si deseas que el


contenido del archivo sea claramente legible, reemplaza el  10 con algo
como  ord('a') , esto producirá bytes que contienen valores correspondientes
a la parte alfabética del código ASCII (no pienses que harás que el archivo sea
un archivo de texto; sigue siendo binario, ya que se creó con un indicador -
bandera  wb ).
 Después, creamos el archivo usando la función  open() , la única diferencia en
comparación con las variantes anteriores es que el modo de apertura contiene
el indicador  b .
 El método  write()  toma su argumento ( bytearray ) y lo envía (como un todo)
al archivo.
 El stream se cierra de forma rutinaria.

El método  write()  devuelve la cantidad de bytes escritos correctamente.

Si los valores difieren de la longitud de los argumentos del método, puede significar
que hay algunos errores de escritura.

En este caso, no hemos utilizado el resultado; esto puede no ser apropiado en todos
los casos.

Intenta ejecutar el código y analiza el contenido del archivo recién creado.

Lo vas a usar en el siguiente paso.

Cómo leer bytes de un stream


La lectura de un archivo binario requiere el uso de un método especializado
llamado  readinto() , ya que el método no crea un nuevo objeto del arreglo de bytes,
sino que llena uno creado previamente con los valores tomados del archivo binario.

Nota:

 El método devuelve el número de bytes leídos con éxito.


 El método intenta llenar todo el espacio disponible dentro de su argumento; si
existen más datos en el archivo que espacio en el argumento, la operación de
lectura se detendrá antes del final del archivo; el resultado del método puede
indicar que el arreglo de bytes solo se ha llenado de manera fragmentaria (el
resultado también lo mostrará y la parte del arreglo que no está siendo
utilizada por los contenidos recién leídos permanece intacta).
Mira el código completo a continuación:

Analicémoslo:

 Primero, abrimos el archivo (el que se creó usando el código anterior) con el
modo descrito como  rb .
 Luego, leemos su contenido en el arreglo de bytes llamado  data , con un
tamaño de diez bytes.
 Finalmente, imprimimos el contenido del arreglo de bytes: ¿Son los mismos
que esperabas?

Ejecuta el código y verifica si funciona.

Cómo leer bytes de un stream


Se ofrece una forma alternativa de leer el contenido de un archivo binario mediante el
método denominado  read() .
Invocado sin argumentos, intenta leer todo el contenido del archivo en la memoria,
haciéndolo parte de un objeto recién creado de la clase bytes.

Esta clase tiene algunas similitudes con  bytearray , con la excepción de una diferencia
significativa: es immutable.

Afortunadamente, no hay obstáculos para crear un arreglo de bytes tomando su valor


inicial directamente del objeto de bytes, como aquí:

from os import strerror

try:
bf = open('file.bin', 'rb')
data = bytearray(bf.read())
bf.close()

for b in data:
print(hex(b), end=' ')

except IOError as e:
print("Se produjo un error de E/S: ", strerr(e.errno))

Ten cuidado - no utilices este tipo de lectura si no estás seguro de que el
contenido del archivo se ajuste a la memoria disponible.

Cómo leer bytes de un stream: continuación


Si el método  read()  se invoca con un argumento, se especifica el número máximo
de bytes a leer.

El método intenta leer la cantidad deseada de bytes del archivo, y la longitud del
objeto devuelto puede usarse para determinar la cantidad de bytes realmente leídos.

Puedes usar el método como aquí:

try:

bf = open('file.bin', 'rb')

data = bytearray(bf.read(5))

bf.close()

for b in data:

print(hex(b), end=' ')

except IOError as e:

print("Se produjo un error de E/S:", strerr(e.errno))

Nota: los primeros cinco bytes del archivo han sido leídos por el código; los siguientes
cinco todavía están esperando ser procesados.

Copiando archivos: una herramienta simple y


funcional
Ahora vas a juntar todo este nuevo conocimiento, agregarle algunos elementos
nuevos y usarlo para escribir un código real que pueda copiar el contenido de un
archivo.

Por supuesto, el propósito no es crear un reemplazo para los comandos como copy de


(MS Windows) o cp de (Unix/Linux) pero para ver una forma posible de crear una
herramienta de trabajo, incluso si nadie quiere usarla.

Observa el código en el editor. Analicémoslo:

 Las líneas 3 a la 8: solicitan al usuario el nombre del archivo a copiar e intentan


abrirlo para leerlo; se termina la ejecución del programa si falla la apertura;
nota: emplea la función  exit()  para detener la ejecución del programa y
pasar el código de finalización al sistema operativo; cualquier código de
finalización que no sea  0  significa que el programa ha encontrado algunos
problemas; se debe utilizar el valor  errno  para especificar la naturaleza del
problema.
 Las líneas 9 a la 15: repiten casi la misma acción, pero esta vez para el archivo
de salida.
 La línea 17: prepara una parte de memoria para transferir datos del archivo
fuente al destino; Tal área de transferencia a menudo se llama un búfer, de ahí
el nombre de la variable; el tamaño del búfer es arbitrario; en este caso,
decidimos usar 64 kilobytes; técnicamente, un búfer más grande es más rápido
al copiar elementos, ya que un búfer más grande significa menos operaciones
de E/S; en realidad, siempre hay un límite, cuyo cruce no genera más ventajas;
pruébalo tú mismo si quieres.
 Línea 18: cuenta los bytes copiados: este es el contador y su valor inicial.
 Línea 20: intenta llenar el búfer por primera vez.
 Línea 21: mientras se obtenga un número de bytes distinto de cero, repite las
mismas acciones.
 Línea 22: escribe el contenido del búfer en el archivo de salida (nota: hemos
usado un segmento para limitar la cantidad de bytes que se escriben, ya
que  write()  siempre prefiero escribir todo el búfer).
 Línea 23: actualiza el contador.
 Línea 24: lee el siguiente fragmento de archivo.
 Las líneas 29 a la 31: limpieza final: el trabajo está hecho.

from os import strerror

srcname = input("¿Nombre del archivo fuente?: ")


try:
src = open(srcname, 'rb')
except IOError as e:
print("No se puede abrir archivo fuente: ", strerror(e.errno))
exit(e.errno)
dstname = input("¿Nombre del archivo de destino?: ")
try:
dst = open(dstname, 'wb')
except Exception as e:
print("No se puede crear el archivo de destino: ", strerr(e.errno))
src.close()
exit(e.errno)

buffer = bytearray(65536)
total = 0
try:
readin = src.readinto(buffer)
while readin > 0:
written = dst.write(buffer[:readin])
total += written
readin = src.readinto(buffer)
except IOError as e:
print("No se puede crear el archivo de destino: ", strerr(e.errno))
exit(e.errno)

print(total,'byte(s) escritos con éxito')


src.close()
dst.close()

También podría gustarte