Python Cisco Parte2
Python Cisco Parte2
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.
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.
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.
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.
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).
Esto significa que puede tener sus propias entidades llamadas sin o pi y no
serán afectadas en alguna manera por el import.
math.pi
math.sin
Es sencillo, se pone:
Este primer ejemplo no será muy avanzado: solo se desea imprimir el valor
de sin(1/2π).
import math
0.99999999
1.0
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.
print(math.e)
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.
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
Importando un Módulo: *
En el tercer método, la sintaxis del import es una forma más agresiva que la
presentada anteriormente:
¿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.
import math as m
print(m.sin(m.pi/2))
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.
La frase nombre as alias puede repetirse: emplea comas para separar las frases,
como a continuación:
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.
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
print(name, end="\t")
El código de ejemplo debería producir el siguiente resultado:
¿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.
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.
import math
dir(math)
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:
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() :
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.
seed(0)
for i in range(5):
print(random())
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)
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.
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".
choice(secuencia)
sample(secuencia, elementos_a_elegir=1)
print(choice(lst))
print(sample(lst, 5))
print(sample(lst, 10))
4
[3, 1, 8, 9, 10]
[10, 8, 5, 1, 6, 4, 3, 9, 7, 2]
Esto significa que algunas de las acciones del programa tienen que recorrer un
largo camino para ejecutarse con éxito, imagina que:
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.
Ahora:
Windows-Vista-6.0.6002-SP2
Windows-Vista-6.0.6002-SP2
Windows-Vista
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
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.
x86
x86_64
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).
x86
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.
Windows
Linux
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() .
6.0.6002
CPython
3
6
4
¿Qué es un paquete?
Escribir tus propios módulos no difiere mucho de escribir scripts comunes.
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.
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.
Volvamos al archivo main.py:
Ejecuta el archivo. ¿Que ves? Con suerte, verás algo como esto:
Python puede hacer mucho más. También crea una variable llamada __name__ .
__main__
module
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.
Ver código en
Sandbox
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.
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
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.
Nota:
Revisar
Debido a que una diagonal invertida se usa para escapar de otros caracteres, si
deseas obtener solo una diagonal invertida, debe escapar.
path.append('C:\\Users\\user\\py\\modules')
Tu primer paquete
Imagina que en un futuro no muy lejano, tu y tus socios escriben una gran
cantidad de funciones en Python.
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:
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.
Nota:
Observa el código en el editor. Hay al menos dos formas posibles de que "salga mal" la
ejecución. ¿Puedes verlas?
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'
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.
Excepciones: continuación
Observa el código en el editor. Ejecuta el (obviamente incorrecto) programa.
Excepciones:
continuación
Observa el código en el editor. ¿Qué pasará cuando lo ejecutes?
Pero, ¿no sería mejor verificar primero todas las circunstancias y luego hacer algo solo
si es seguro?
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.
Nota:
Resumamos esto:
try:
:
:
except:
:
:
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:
Se parece a esto:
try:
:
except exc1:
:
except exc2:
:
except:
:
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í.
0.2
FIN.
Si se ingresa 0 , dirá:
Observa el programa en el editor. Esta vez, hemos eliminado el bloque sin nombre.
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:
Nota:
BaseException
↑
Exception
↑
ArithmeticError
↑
ZeroDivisionError
Excepciones: continuación
Observa el código en el editor. Es un ejemplo simple para comenzar. Ejecutalo.
Uuuppsss...
FIN.
try:
y = 1 / 0
except ArithmeticError:
print("Uuuppsss...")
print("FIN.")
Ya se sabe que ArithmeticError es una clase general que incluye (entre otras) la
excepción ZeroDivisionError .
Vamos a resumir:
¿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.")
¡Problema aritmético!
FIN.
Recuerda:
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.
Dentro de la función.
Fuera de la función.
¡Problema aritmético!
FIN.
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.).
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.
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
La instrucción permite:
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.
Gracias a esto, puedes distribuir el manejo de excepciones entre diferentes partes del
código.
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 .
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).
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.
Las excepciones son tan rutinarias y normales como cualquier otro aspecto de
la vida de un programador.
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.
ArithmeticError
Ubicación:
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:
Descripción:
Código :
print(tan(radians(angle)))
BaseException
Ubicación:
BaseException
Descripción:
IndexError
Ubicación:
Descripción:
Código:
# 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:
LookupError
Ubicación:
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:
Código:
string = 'x'
try:
while True:
string = string + string
print(len(string))
except MemoryError:
print('¡Esto no es gracioso!')
OverflowError
Ubicación:
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:
Descripción:
Código:
KeyError
Ubicación: BaseException ← Exception ← LookupError ← KeyError
Descripción:
Código:
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.
¿Como es posible?
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.
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.
¿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.
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 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.
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).
UTF-8
Uno de los más utilizados es UTF-8.
Por ejemplo:
Es muy importante tener en cuenta esto, porque significa que debes esperar un
comportamiento familiar.
Cualquier cadena puede estar vacía. Si es el caso, su longitud es 0 como en el Ejemplo
2.
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.
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.
multiLinea = """Linea #1
Linea #2"""
print(len(multiLinea))
Concatenadas (unidas).
Replicadas.
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.
ab
ba
aaaaa
bbbb
Nota: Los atajos de los operadores anteriores también son aplicables para las cadenas
( += y *= ).
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() .
a
α
Nota:
chr(ord(x)) == x
ord(chr(x)) == x
De nuevo, realiza tus propios experimentos.
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.
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.
for ch in exampleString:
print()
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.
bd
efg
abd
e
adf
beg
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).
True
False
False
True
False
alfabeto = "abcdefghijklmnopqrstuvwxyz"
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?
alfabeto = "abcdefghijklmnopqrstuvwxyz"
del alfabeto[0]
Lo único que puedes hacer con del y una cadena es eliminar la cadena como un
todo. Intenta hacerlo.
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")
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.
Nota: Es una A mayúscula. ¿Por qué? Recuerda la tabla ASCII, ¿qué letras ocupan las
primeras posiciones, mayúsculas o minúsculas?
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.
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.
[¡]
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).
La salida es:
Es:
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:
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.
Abcd
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.
[ alfa ]
La variante de dos parámetros de center() hace uso del caracter del segundo
argumento, en lugar de un espacio. Analiza el siguiente ejemplo:
[*******gamma********]
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.
t = "zeta"
print(t.endswith("a"))
print(t.endswith("A"))
print(t.endswith("et"))
print(t.endswith("eta"))
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:
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.
t = 'teta'
print(t.find('eta'))
print(t.find('et'))
print(t.find('te'))
print(t.find('ha'))
print('kappa'.find('a', 2))
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
print('kappa'.find('a', 1, 4))
print('kappa'.find('a', 2, 4))
1
-1
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.
True
True
True
False
False
False
t = 'Six lambdas'
print(t.isalnum())
t = 'ΑβΓδ'
print(t.isalnum())
t = '20E1'
print(t.isalnum())
El método isalpha()
El método isalpha() es más especializado, se interesa en letras solamente.
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.
True
False
El método islower()
El método islower() es una variante de isalpha() - solo acepta letras minúsculas.
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 ).
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.
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.
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.
sigma=60
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.
Los corchetes no son parte del resultado, solo muestran los límites del resultado.
[tau ]
print("www.cisco.com".lstrip("w."))
cisco.com
print("pythoninstitute.org".lstrip(".org"))
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.
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.
www.pyhoninstitute.org
Apple
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).
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.
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:
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.
[aleph]
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.
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.
¡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.
==
!=
>
>=
<
<=
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.
'alfa' == 'alfa'
'alfa' != 'Alfa'
'10' == '010'
'10' > '010'
'10' > '8'
'20' < '8'
'20' < '80'
False
True
False
True
True
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 .
Vamos a verlo:
'10' == 10
'10' != 10
'10' == 1
'10' != 1
'10' > 10
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.
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.
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:
itg = 13
flt = 1.3
si = str(itg)
sf = str(flt)
si = '13'
sf = '1.3'
itg = int(si)
flt = float(sf)
print(itg + flt)
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.
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.
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.
Vehículos de ruedas.
Vehículos oruga.
Aerodeslizadores.
Mamíferos.
Reptiles.
Pájaros.
Peces.
Anfibios.
Mamíferos salvajes.
Mamíferos domesticados.
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.
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:
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
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:
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.
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.
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.
Estamos listos para definir una función que pone un valor en la pila. Aquí están las
presuposiciones para ello:
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:
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.
1
2
3
Pero cuanto más la uses, más desventajas encontrarás. Éstas son algunas de
ellas:
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.
class Pila:
¿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:
class Pila:
def __init__(self):
print("¡Hola!")
objetoPila = Pila()
¡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 .
class Pila:
def __init__(self):
self.listaPila = []
objetoPila = Pila()
print(len(objetoPila.listaPila))
Nota:
class Pila:
def __init__(self):
self.__listaPila = []
objetoPila = Pila()
print(len(objetoPila.__listaPila))
¿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.
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).
class Pila:
def __init__(self):
self.__listaPila = []
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.
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.
Hay una cosa más que requiere explicación: la forma en que se invocan los métodos
desde la variable __listaPila .
El primer paso es fácil: solo define una nueva subclase que apunte a la clase que se
usará como superclase.
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.
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.
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.
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
Aquí está:
def getSuma(self):
return self.__sum
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.
class ClaseEjemplo:
def __init__(self, val = 1):
self.primera = val
objetoEjemplo1 = ClaseEjemplo()
objetoEjemplo2 = ClaseEjemplo(2)
objetoEjemplo2.setSegunda(3)
objetoEjemplo3 = ClaseEjemplo(4)
objetoEjemplo3.tercera = 5
print(objetoEjemplo1.__dict__)
print(objetoEjemplo2.__dict__)
print(objetoEjemplo3.__dict__)
{'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:
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.
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:
{'_ClaseEjemplo__primera': 1} 3
{'_ClaseEjemplo__primera': 2} 3
{'_ClaseEjemplo__primera': 4} 3
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.
El objeto creado por el constructor solo puede tener uno de los dos atributos
posibles: a o b .
print(objetoEjemplo.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.
La función retorna True o False.
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.
¿Puedes adivinar la salida del código? Ejecútalo para verificar tus conjeturas.
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'))
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.
método
class conClase:
def metodo(self, par):
print("método:", par)
obj = conClase()
obj.metodo(1)
obj.metodo(2)
obj.metodo(3)
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 constructor:
objeto
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.
Observemos cómo esta propiedad trata con los métodos: mira el código en el editor.
Nota: el atributo __name__ está ausente del objeto - existe solo dentro de las clases.
conClase
conClase
__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.
Además, te mostraremos cómo usar este atributo cuando discutamos los aspectos
orientados a objetos de las excepciones.
( object )
( object )
( SuperUno SuperDos )
¡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.
El programa imprime solo una línea de texto, que en nuestro caso es:
Como puedes ver, la impresión aquí no es realmente útil, y algo más específico, es
preferible.
El método por default __str__() devuelve la cadena anterior: fea y poco informativa.
Puedes cambiarlo definiendo tu propio método del nombre.
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?
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.
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.
issubclass(ClaseUno, ClaseDos)
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.
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)
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.
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.
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.
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.
Mi nombre es Andy.
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.
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 .
12
11
Cuando intentes acceder a una entidad de cualquier objeto, Python intentará (en este
orden):
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.
El código imprime:
¿Qué crees que sucederá si más de una de las superclases define una entidad con un
nombre en particular?
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.
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 .
¿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.
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:
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.
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?
hazlo de Uno
hazlo de Dos
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.
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.
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.
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.
Solo hay un "pero". El hecho de que puedas hacerlo no significa que tengas que
hacerlo.
No olvides que:
pass
class B(A):
pass
class C(A):
pass
pass
d = D()
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: estas dos variantes ( else y finally ) no son dependientes entre si, y pueden
coexistir u ocurrir de manera independiente.
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.
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:
Para cada una de las clases encontradas, se realiza el mismo conjunto de operaciones:
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__() .
: :
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.
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 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
class DemasiadoQuesoError(PizzaError):
def __init__(self, pizza, queso, mensaje):
PizzaError._init__(self, pizza, mensaje)
self.queso = queso
Nota:
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)
¿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.
__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 .
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.
Fib1 = 1
Fib2 = 1
Fibi = Fibi-1 + Fibi-2
En otras palabras:
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.
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.
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.
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.
def fun(n):
for i in range(n):
yield i
for v in fun(5):
print(v)
Revisar
4
Cómo construir tu propio generador
¿Qué pasa si necesitas un generador para producir las primeras n potencias de 2 ?
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.
Observa :
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 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.
¿Se puede usar el mismo truco dentro de una lista de comprensión? Sí, por supuesto.
Son
los paréntesis. Los
corchetes hacen una
comprensión, los
paréntesis hacen un generador.
¿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.
En el segundo bucle, no hay ninguna lista, solo hay valores subsequentes producidos
por el generador, uno por uno.
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.
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?
dos = lambda : 2
cuadrado = lambda x : x * x
potencia = lambda x, y : x ** y
Vamos a analizarlo:
4 4
1 1
0 0
1 1
4 4
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
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.
La
función imprimirfunc
ion() se ha mantenido
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)
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() .
Esta es la explicación:
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.
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.
def exterior(par):
loc = par
var = 1
exterior(var)
print(var)
print(loc)
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.
Observa cuidadosamente:
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.
0 0 0
1 1 1
2 4 8
3 9 27
4 16 64
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.
Puedes preguntarte por qué hemos esperado hasta ahora para mostrarte esto.
EsteEsElNombreDelArchivo
y
esteeselnombredelarchivo
Para entender por qué, intenta recordar el papel muy específico que
desempeña \ dentro de las cadenas en Python.
Supongamos también que deseas asignar a una cadena el nombre del archivo.
nombre = "/dir/archivo"
nombre = "\dir\archivo"
nombre = "\\dir\\archivo"
nombre = "c:/dir/archivo"
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).
Manejo de Archivos
Python supone que cada archivo está oculto detrás de un objeto de una
clase adecuada.
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.
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.
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.
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.close()
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.
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() .
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.
try:
# operaciones con streams
print(exc.errno)
El valor del atributo errno se puede comparar con una de las constantes
simbólicas predefinidas en módulo errno .
El error se produce cuando intentas, por ejemplo, abrir un archivo con atributos
de solo lectura para abrirlo.
El error se produce cuando intentas, por ejemplo, operar un stream sin abrirlo.
El error ocurre cuando intentas crear un archivo que es más grande que el
máximo permitido por el sistema operativo.
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)
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.
s = open("c:/users/user/Desktop/file.txt", "rt")
# el procesamiento va aquí
s.close()
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.
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.
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:
INFORMACIÓN
Recuerda - el leer un archivo muy grande (en terabytes) usando este método
puede dañar tu sistema operativo.
Vamos a analizarlo:
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.
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() .
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.
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.
Creemos que puede sorprenderte - el objeto es una instancia de la clase iterable.
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.
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
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.
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)
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).
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.
Bytearrays: continuación
Entonces, ¿cómo escribimos un arreglo de bytes en un archivo binario?
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.
Nota:
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?
Esta clase tiene algunas similitudes con bytearray , con la excepción de una diferencia
significativa: es immutable.
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.
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.
try:
bf = open('file.bin', 'rb')
data = bytearray(bf.read(5))
bf.close()
for b in data:
except IOError as e:
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.
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)