IA desde 0 absoluto (Python)

Disclaimer. Soy abogado. Mis conocimientos respecto al ámbito de la Inteligencia Artificial son escasos y mi experiencia en el ámbito de la programación es nula. Tengo 4 meses para aprender lo suficiente sobre Python para tratar de llegar a aplicarlo en el ámbito de la inteligencia artificial.

En esta bloque trataré unicamente sobre las bases del lenguaje de programación Python.

Semana 0

He empezado un curso en línea para iniciarme en el lenguaje de programación Python. En internet hay muchos cursos y no es mi intención hacer publicidad de ninguno, simplemente para que sirva de referencia, diré que dicho curso es “PCAP Programming Essentials in Python” de la Networking Academy de Cisco.

Aunque en el curso siempre hay una pestaña llamada “sandbox” donde poder escribir el código y ejecutarlo, resulta conveniente descargarse una versión de Python (yo trabajo con la versión 3.9.5, pero a fecha de este escrito ha salido la beta de la versión 3.10).

Respecto al curso decir que tiene una primera parte dividida en 4 módulos y dirigida a los fundamentos básicos de Python. Después hay una segunda parte de un nivel intermedio que consta de 2 módulos, en el que se tratan aspectos como módulos, paquetes, métodos de listas y programación con un enfoque orientado a objetos. A lo largo de las diferentes lecciones tendremos la oportunidad de ir evaluando nuestros conocimientos a través de unos laboratorios. Los ejercicios irán aumentando su dificultad a medida que vayamos avanzando en el curso, algunos llevarán menos de 5 minutos, otros nos harán devanarnos los sesos e incluso puede que nos veamos obligados a buscar ayuda (no os fieis de lo que el curso estima que deben durar).

Pues bien, en mi primera semana he estado aprendiendo:

Al principio me ha resultado un tanto difícil abstraerme y enfocar los distintos problemas desde una lógica computacional. Algunas partes considero intuitivas como print, if, else o elif, otras como las instrucciones break, continue y return dentro de los bucles for y while me han provocado verdaderos dolores de cabeza. Eso ha derivado en una diversa cantidad de errores que he cometido y sigo cometiendo. Sin embargo, intento disminuir el número de errores prestando atención a:

  • los mensajes de error/excepción
  • la diferencia entre asignar (=) y equivaler (==)
  • la manera de escribir el nombre de las funciones (por ejemplo, ver si he escrito Print en vez de print)
  • la indentación de las funciones
  • si he escrito las comillas y los dos puntos (:) donde corresponde
  • las variables (si están creadas antes de definir la función o no)
  • si he optado por la instrucción adecuada entre break, continue y return
  • la prioridad de los distintos operadores

Además de todo ello, he ido probando el código poco a poco. Ejecutando pequeñas partes de manera separada para ver los fallos aisladamente. Pero aun y todo me he atascado varias veces. Ante esta situación, mi solución ha sido crear una variable ocupando ese punto del código sin resolver para más adelante volver a ello.

Como en todos los casos de aprendizaje viene bien contar con varios materiales de apoyo. Particularmente me ha sido de gran ayuda el canal de Youtube de Travis Bonfigli, experto en IT que trabaja en la Universidad de Maryland. En él Bonfigli alberga una lista de videos donde muestra cómo resuelve los distintos laboratorios del curso. Los vídeos están en inglés, en el caso de no saber el idioma, se puede configurar su reproducción para que se muestren subtítulos automáticamente traducidos al español.

Asimismo, para dudas rápidas respecto al lenguaje Python he recurrido principalmente a los ya referidos documentos de la propia web de Python, así como al foro de Stackoverflow.

P.D.: He acabado la semana atascado en el último laboratorio de la primera parte del curso: escribir un programa que simule el juego de tres en raya («Tic-tac-toe»).

Semana 1

Empecé la semana acabando el laboratorio del “tictactoe”. La verdad es que me llevó más tiempo de lo esperado, sin embargo, gracias al material de apoyo conseguí que el programa funcionase tal y como lo pedían. He de añadir que en cada laboratorio existe la opción de que nos den pistas, hasta que nos dan su solución. Recalco el su ya que, por lo que voy viendo, hay más de una manera de diseñar un programa.

Tras el “tictactoe”, superé el quiz del módulo 4 y test final de la parte 1. De este modo empecé la parte 2 que consta de 2 módulos. En el primero, el módulo 5, nos enseñan a importar módulos.

En síntesis, los módulos son como códigos ya escritos que podemos importar dentro de nuestro código para que resuelva alguna tarea específica. Dentro de cada módulo puede haber funciones, variables y/o instrucciones.

En el curso he aprendido a utilizar los siguientes:

  • Math. Sirve para obtener funciones relativas a operaciones y variables típicas matemáticas. Entre otros: sin(), cos(), tan(), e, log(), pi, exp(), ceil(), floor(), trunc()… (He de decir que he tenido problemas con este módulo porque tenía las matemáticas muy oxidadas y he tenido que repasar algunos términos)
  • Random. Para obtener elementos aleatorios, valores no repetidos (choice, sample, randint, randrange, seed).
  • Platform. Para obtener información del hardware (machine), procesador (processor), sistema operativo (system), versión del programa (versión).

Además, he podido construir mi primer paquete con mi primer módulo. Un paquete es como si fuera una carpeta que alberga módulos. Según he entendido, al buscar los módulos, Python sigue el orden de las carpetas y lee el primer módulo que se encuentra, podría ser que tuviéramos un problema al tener dos o más módulos con el mismo nombre. Para solventarlo se utiliza la variable path a través del módulo sys y de este modo le indicamos qué queremos en concreto.

Seguidamente he aprendido a trabajar con las excepciones. Las excepciones son los mensajes de error que emergen cuando el programa no puede ejecutarse. Así como las causas son diversas, las excepciones lo son. Incluso existe un árbol de excepciones, según la cual una excepción general absorbe a una específica, por ejemplo, ZeroDivisionError (cuando algo se divide entre 0) es absorbido por ArithmeticError.

Resulta crucial entender las excepciones para conocer en qué fallaban mis códigos. Para no encontrarse con sorpresas existen las palabras clave try y except. Con ellas podemos ir probando el código e indicándole una excepción para así ver la solución correcta. De todas formas se asemejan un tanto a la función if y de hecho es posible introducir un else al trabajar con ellas. De tal manera que si, tras el try, el except no tiene lugar, el programa devolverá la instrucción incluida en else. Asimismo, cabe incluir un finally que devuelve lo que queramos independientemente de si se ha levantado una excepción. Ahí va un ejemplo:

def Ejemplo(x):
    try:
        x = 1 / x

    except ZeroDivisionError:
        print('No se puede dividir entre cero')


        x = None

    else:
        print('Calculando...')

    finally:
        print('El resultado es')

        return x

print(Ejemplo(2))
print(Ejemplo(0))

Resultado:


Calculando...
El resultado es
0.5
No se puede dividir entre cero
El resultado es
None

Seguidamente el curso trata los caracteres y las cadenas. Se mencionan los diferentes estándares, de este modo nos introduce los puntos de código y cómo cada uno representa a un caracter. Aquí entran en juego las funciones ord() y chr(), para obtener el ordinal o punto de código de un carácter y viceversa.

Ello nos sirve para adentrarnos en las operaciones con cadenas, donde aprendí:

  • Indexar cadenas.
  • Obtener porciones de las mismas.
  • Las funciones min() y max(). Devuelven el menor y mayor caracter.
  • Cómo obtener listas de una cadena: list().
  • La función sorted(). A partir de una lista de cadenas devuelve una nueva lista con las cadenas ordenadas.

Así como diferentes métodos de cadenas de caracteres:

  • El método find() y rfind(). Devuelve el índice del primer caracter igual al indicado. Para que devuelva el último rfind()
  • El método center(). Para que centre una cadena.
  • El método capitalize(). Sirve para convertir minúsculas en mayúsculas.
  • Para identificar el tipo de caracteres que componen una cadena: solo dígitos o letras isalnum(), solo letras isalpha(), solo dígitos isdigit(), solo minúsculas islower(), solo espacios isspace(), solo mayúsculas isupper().
  • El método join(). Sirve para unir las cadenas de una lista, utilizando otra cadena de separador entre ellas y así devolver una sola cadena.
  • El método lower(). Copia una cadena y la devuelve en minúsculas.
  • El método upper(). Cambia todas las letras a mayúsculas.
  • El método title(). Cambia la primera letra de cada palabra a mayúscula.
  • El método strip(), lstrip() y rstrip(). Devuelve una cadena sin espacios o sin los caracteres indicados. lstrip() eliminaría los primeros espacios o caracteres y rstrip() los últimos.
  • El método replace(). Reemplaza un carácter por otro.
  • El método split() y rsplit(). Divide una cadena y crea una lista con todas las subcadenas detectadas.
  • El método startswith(). Comprueba si una cadena empieza por la cadena indicada.
  • El método endswith(). Comprueba si una cadena termina por la cadena indicada.
  • El método swapcase(). Intercambia las minúsculas y mayúsculas.
  • El método sort(). Ordena una lista de cadenas. También es aplicable si la lista contiene números.

Muchos de estos métodos los podemos aplicar en el preprocesamiento de texto para las tareas de pln.

Semana 2

Al acabar de aprender las diferentes operaciones con cadenas, me puse con un laboratorio en el que nos piden programar un display led. Básicamente consiste en que el programa dibuje con 13 leds cada dígito del número que ingrese el usuario. He de reconocer que me atasqué bastante y tuve que avanzar. Lo máximo que conseguí es reproducir uno a uno cada dígito verticalmente.

Tras ello aprendí, por un lado, el denominado cifrado césar consistente en que cada letra de un mensaje se reemplaza por la siguiente más cercana en el alfabeto, y por otro lado, el programa validador IBAN (sí, el que permite comprobar que número de cuenta bancaria existe).

El modulo 5 acaba con los siguientes laboratorios.

  • “Mejorando el cifrado césar”: En este laboratorio tenemos que aplicar el cifrado césar pero configurándolo de tal manera que el valor de cambio lo determine la entrada del usuario. No me resultó muy difícil, aunque me confundí un par de veces con los valores de los puntos de código. Viene bien tener a mano la tabla ASCII de los 128 primeros caracteres. Cómo lo resolví yo:
def cifradoCesar():  #  Hace falta definir el código
    texto = input('Ingresa tu mensaje: ') 
    codigo = int(input('Ingresa un número entero del rango 1 al 25: '))  
                              #  Lo paso a entero para poder trabajar con él
    cifrado = ''  #  La cadena que se formará aplicándo el código al mensaje 
    for char in texto:  #  Para cada caracter en el texto
        if char == ' ':  #  Si el caracter es un espacio
            cifrado += char  #  Lo añade a la nueva cadena tal y como está
            continue
        elif ord(char) > 47 and ord(char) < 58:  #  Los puntos de código entre 
            cifrado += char                      #  el 47 y el 58 (ambos  
                                                 #  excluidos) se reservan
                                                 #  para los digitos
            continue
        else:
            letraCodificada = ord(char) + codigo  #  Con los caracteres
                                                  #  restantes se crea una 
                                                  #  variable con un valor 
                                                  #  igual a la suma del punto 
                                                  #  de código del caracter + 
                                                  #  el codigo
            
        if ord(char) >= ord('a') and letraCodificada < ord('a'):  
            letraCodificada = (ord('z')+1)+(codigo + (ord(char)-ord('a')))  
        elif ord(char) >= ord('A') and letraCodificada < ord('A'):  
            letraCodificada = (ord('Z')+1)+(codigo + (ord(char)-ord('A')))  
        elif ord(char) <= ord('z') and letraCodificada > ord('z'):  
            letraCodificada = (ord('a')-1)+(codigo - (ord('z')-ord(char)))  
        elif ord(char) <= ord('Z') and letraCodificada > ord('Z'):  
            letraCodificada = (ord('A')-1)+(codigo - (ord('Z')-ord(char))) 
                      #  Estas líneas de código sirven para reasignar
                      #  un valor a todos aquellos caracteres que, 
                      #  tras aplicarles el código, su valor ya no se
                      #  encuentra entre 'A' y 'A' o 'a' y 'z'.
                      #  Las dos primeras están dedicadas para cuando el 
                      #  código sea negativo y las últimas positivo.

        cifrado += chr(code)  #  Lo añade a la nueva cadena según se hayan 
                              #  aplicado los casos anteriores
                              #  Indentado a la linea "for char in texto:"
                              
    print(cifrado)  #  Devuelve el nuevo mensaje

cifradoCesar()
  • “Palíndromo”: Consiste en averiguar si un texto introducido es un palíndromo (que se lee igual de izquierda a derecha que de derecha a izquierda). Aquí me atasqué un poco hasta que di con [::-1].  Sirve para obtener una lista o cadena del revés (Valdría también la función texto.reverse()). De esta manera comparaba la entrada de texto invertida con la original y si era igual el texto era un palíndromo.
texto = input('Ingresa un texto: ')


texto = texto.replace(' ','')  #  Como los espacios no los necesito, 
                               #  los sustituyo por nada


if texto.upper() == texto[::-1].upper():  #  Pasar a mayúsculas
    print('Es un palíndromo')             #  para que los caracteres  
else:                                     #  sean los mismos
	print('No es un palíndromo')
  • “Anagramas”: Aquellas palabras que se forman con los mismos caracteres que otras pero en otro orden. No me llevó demasiado tiempo, me basé en el método sorted() para resolverlo.
texto1 = input('Introduce un texto:')
texto2 = input('Introduce otro texto:')

texto1 = texto1.upper()  #  Pasarlo a mayúsculas para trabajar
texto2 = texto2.upper()  #  más facilmente

texto1 = texto1.replace(' ','')  #  Los espacios no son relevantes
texto2 = texto2.replace(' ','')

listatexto1 = list(texto1)  #  Convertir en listas para aplicar sorted()
listatexto2 = list(texto2)

listatexto1 = sorted(listatexto1)  #  Para ordenar los caracteres y comparar
listatexto2 = sorted(listatexto2)  #  más facilmente

if listatexto1 == listatexto2:
    print('Es un anagrama')
else:
    print('No es un anagrama')
  • “Dígito de la vida”: Básicamente sumar cada digito. Atención porque si la suma de los digitos de la fecha llega a suponer un número de dos digitos hay que sumar estos también.
cumple = input('Introduce tu fecha de cumpleaños (AAAAMMDD, AAAADDMM o MMDDAAAA): ')
if len(cumple) != 8 or not cumple.isdigit():      # Poner primero los errores 
	print('Error. Introduce solo 8 digitos')  # facilita el código
else:
	while len(cumple) > 1:                 #  Este bucle es necesario para
		digitoVital = 0                #  que el dígito de la vida sea
		for digito in cumple:          #  solo uno.
			digitoVital += int(digito)  #  Los digitos del input
                                                    #  son cadenas
		cumple = str(digitoVital)       #  Hay que renombrar cumple
                                                #  para cerrar el bucle y
                                                #  quedarse con un solo digito
		
print('Tu Dígito de la Vida es: ' + cumple)
  • “Busca una palabra”: 5.1.9.4 Aquí el curso confunde diciéndo que dentro del código debemos «usar las variantes de dos argumentos de las funciones pos()«. Tuve que volver a la lección 5.1.9.4 para entenderlo. Por lo demás, basicamente consiste en utilizar la función find() (ya hablé de él la semana pasada). Reconozco que es de las funciones que más útiles me parece para procesar textos.
palabra = input('Introduce la palabra que quieras buscar: ').upper()
cadena = input('Introduce el texto donde quieras buscar la palabra: ').upper()

    #  Lo convierto todo a mayúsculas para facilitar la búsqueda.

contador = 0  #  Para ir sumando por cada caracter que encuentre.
indice = 0  #  Lo utilizo para indicarle donde empezar a buscar.

for char in palabra:
    resultado = cadena.find(char, indice)  #  El char es el caracter 
                                           #  a buscar, indice la posición 
                                           #  donde empieza a buscar
    if resultado >= 0:
        contador += 1
        indice += 1
    else:
        continue


if resultado >= 0:      #  Como find() da la posición de la palabra en la 
    print('Sí')         #  cadena y tal posición tiene que ser >= 0
else:
    print('No')
  • “Sudoku”: Es el último laboratorio y el que más me ha costado. Consiste en escribir un código para verificar que un sudoku, completado con las entradas que nos da el usuario, cumple con las reglas del juego. Sin exagerar, habré realizado más de 100 ensayos y errores en total. He empezado creando una variable igual a los números del 1 al 9 y una lista del tablero. He tenido problemas con el método sorted() ya que cada entrada es una cadena dentro de una lista. A lo más que llegaba era a que me devolviera los dígitos en orden sumándole las corcheas: 123456789[]. He tenido que convertir las cadenas en listas antes de aplicar el método sorted(). Además he tenido problemas delimitando la comprobación por filas, columnas y subcuadros. He acabado por resolverlo a base de colocar unos cuantos print(), revisar lecciones e ir creando listas.
Numeros19 = '123456789'  #  Esta variable me sirve para más adelante poder
                         #  comprobar que están los dígitos del 1-9 sin que
                         #  se repita ninguno
Tablero = []    #  Esta lista es para comprobar las filas 

Fila = ''       #  Las siguientes cadenas las utilizo para el mensaje final,
Columna = ''    #  si están marcadas con una 'x' significa que el sudoku no 
                #  está resuelto porque las reglas no se cumplen en alguna
Subcuadro = ''  #  fila, columna o subcuadro

for i in range(9):  #  Este bloque es para hacer una lista con las filas
    filasUsuario = input('Mete los números de la fila ' + str(i + 1) + ':')
        #  Aquí simplemente recordar que range parte de 0 de ahí ese +1
    if len(filasUsuario) == 9:
        Tablero.append(filasUsuario)  #  Añado cada entrada en una lista
    else:
        print('Error. Introduzca solo 9 números')
            
for f in range(9):  #  Este bloque es para comprobar que en cada lista
                    #  se compone de los 9 digitos
    if sorted(list(Tablero[f])) == list(Numeros19):
            #  Enlisto el Tablero[f] porque es una cadena y necesito 
            #  ordenarla a través de sorted()
        print('La fila ' + str(f + 1) + ' cumple la norma')
    else:
        print('La fila ' + str(f + 1) + ' no cumple la norma')
        Fila += 'x'  #  Si no se cumple la norma mete una x en "Fila"

for c in range(9):  #  Para comprobar que en cada columna hay 9 digitos

    columna = []    #  Una lista donde meter cada digito de la columna
    for i in range(9):
        columna.append(Tablero[i][c])  #  En la lista creada inserta
                                       #  de la lista Tablero las
                                       #  coordenadas fila/columna

        if len(columna) == 9:  #  Para que solo opere cuando columna
                               #  se componga de 9 dígitos
            if sorted(columna) == list(Numeros19):  #  Aquí no enlisto
                                                    #  "columna" porque
                                                    #  ya es una lista
                print('La columna ' + str(c + 1) + ' cumple la norma')
            else:
                print('La columna ' + str(c + 1) + ' no cumple la norma')
                Columna += "x"
        else:
            None
            
numS = 0

for sf in range(0, 9, 3):  #  Este bloque es para comprobar los subcuadros.
                           #  Parte de 0 hasta 9, con un paso de 3.(0,3,6)
                           #  sf responde a SubFila
    for sc in range(0, 9, 3):  #  sc responde a SubColumna
        subcuadro = []  
        numS += 1  #  Para darle un numero a cada subcuadro
        for i in range(3):
            subcuadro.append(Tablero[sf+i][sc:sc+3])
                            #  En subcuadro insertamos las coordenadas del
                            #  Tablero (sf, sc). [sf+i] para que dentro de
                            #  cada subcuadro vaya de fila en fila
                            #  [sc:sc+3] para que dentro de cada fila, meta
                            #  los dígitos desde sc hasta sc+3(excluido)
                            #  De este modo el primer subcuadro se 
                            #  compondrá así: [((0,0)(0,1)(0,2)),
                            #                  ((1,0)(1,1)(1,2)),
                            #                  ((2,0)(2,1)(2,2))]
            print(subcuadro)
            subcuadroCadena = ''.join(subcuadro) 
                            #  Para unir los elementos de subcuadro
                            #  y convertirlos en una cadena
            if len(subcuadroCadena) == 9:  #  Igual que con "columna"
                if sorted(list(subcuadroCadena)) == list(Numeros19):
                    print('El subcuadro ' + str(numS) + ' cumple la norma')
                else:
                    print('La subcuadro ' + str(numS) + ' no cumple la norma')
                    Subcuadro += "x"
            else:
                None

if Fila != '' or Columna != '' or Subcuadro != '':  #  Si alguna tiene una
    print('No')                                     #  "x": No.
else:                                               
    print('Sí')

Semana 3

Esta semana he estado estudiando el módulo 6 el cual se dedica a explicar los fundamentos de la programación orientada a objetos.

En la programación procedimental (o funcional dependiendo del nivel) en el que existen, por un lado, datos y variables y, por otro lado, código (funciones y módulos). En cambio, en la programación orientada a objetos los datos y el código están dentro del mismo mundo y están divididos en clases. Ese mundo se refiere a las unidades lógicas llamadas objetos en las que se agrupan datos y operaciones que pueden realizarse con estos datos.

¿Cómo se crea una clase?

Class ClaseCero:
    pass

Esta es una clase que no hace nada. La palabra clave pass no produce efecto, pero cierra un bloque.

¿Cómo se hace para que los objetos de esa clase tengan una propiedad?

Lo primero de todo es inicializar el objeto. Darle ciertos atributos. Para ello está el denominado constructor o inicializador. Se escribe así:

Def __init__(self):
    self.propiedad

Siguiendo con el mismo ejemplo, creamos una clase y atribuimos una propiedad a los objetos que se vayan a crear.

class ClaseCero:  #  la ClaseCero
    Def __init__(self):  #  atribuye al objeto/self que se vaya a crear
        self.lista = []  #  la propiedad de ser una lista

(Para aclarar: si pusiera “self.cadena = []” seguiría teniendo la propiedad de ser una lista.)

De este modo, el constructor ini(t)cializa al objeto que se vaya a crear con ciertas propiedades, en este caso, la propiedad del objeto de ser una lista. Hago un pequeño inciso para recalcar que self hace referencia al objeto. Self es el parámetro por el cual podemos manipular el objeto y acceder a su propiedad. Se podría nombrar de otra manera, pero el estándar para facilitar la lectura es llamarlo self. Por ello para acceder a la propiedad tenemos que escribir NombreObjeto.Propiedad. Esto se verá mejor más adelante cuando lleguemos a los métodos.

Pues bien, volviendo a las clases, estas tienen jerarquías, una clase puede tener subclases y estas, a su vez, otras subclases según las diseñemos. Cada subclase hereda las propiedades de su clase superior a menos que las subclase las reconfigure.

¿Y cómo se crea una subclase?

class SubclaseCero(ClaseCero):  #  SubclaseCero descendiente de ClaseCero
    def __init__(self):  #  atribuyo al objeto/self que se vaya a crear,
        ClaseCero.__init__(self)  #  así como ClaseCero me atribuyó a mí,
        self.sum = 0  #  la propiedad del objeto/self que se vaya a crear, 
                      #  de tener una variable llamada “sum” con valor 0.

La línea «ClaseCero.__init__(self)» es importantísima, sin ella no funcionaría el constructor de la subclase que hemos creado. Es como si la subclase tuviera que mostrarle las credenciales a Python para poder construir.

Llegado a este punto… ¿Cómo se crea un objeto?

Objeto1 = ClaseCero()

Tal cual. Aquí se ha creado un objeto y una variable («Objeto1«) que hará referencia a ese mismo objeto. Este es el denominado proceso de instanciación, el objeto se convierte en una instancia de clase. Objeto1 es una instancia de la clase ClaseCero. Ello nos sirve para entender qué son las variables de instancia, que no son más que las propiedades estrechamente conectadas con cada objeto.

Además de las variables de instancia están las variables de clase. Son aquellas variables creadas dentro de la clase y antes del constructor. Las variables permanecerán constantes dentro de los objetos, esto es, los objetos podrán trabajar con ellas pero siempre tendrán el mismo valor. Ejemplo:

class ClaseCero: 
    EjemploDeVariable = 0  #  variable de clase
    def __init__(self): 
        self.lista = []  #  variable de instancia

Es posible comprobar las propiedades/atributos de un objeto o clase. Para ello sirva el siguiente ejemplo:

class ClaseCero: 
    EjemploDeVariable = 0  #  Variable de clase
    def __init__(self): 
        self.lista = []    #  Variable de instancia

Objeto1 = ClaseCero()
Print(Objeto1.lista)       #  Imprime la variable de instancia
Print(Objeto1.EjemploDeVariable)  #  Imprime la variable de clase

El resultado será:

[]
0

Pero si en lugar de «print(Objeto1.lista)» pusiéramos «print(Objeto1.cosa)» nos daría un error, concretamente el AttributeError, porque ese atributo no existe en ese objeto.

Por suerte para saber si un objeto o clase tiene atributo existe una función llamada hasattr (has attribute). Funciona del siguiente modo:

class ClaseCero: 
    EjemploDeVariabl = 0 
    def __init__(self): 
        self.lista = []

Objeto1 = ClaseCero()
if hasattr(Objeto1, 'lista'):  #  Pregunta si el objeto tiene  
                               #  el atributo 'lista' 
                               #  (se busca como cadena)
    print('El atributo \"lista\" del Objeto1 existe')

El resultado será:

El atributo "lista" del ObjetoClaseCero existe

O también funciona del siguiente modo:

class ClaseCero: 
    EjemploDeVariableDeClase = 0 
    def __init__(self): 
        self.lista = []

ObjetoClaseCero = ClaseCero()
print(hasattr(ObjetoClaseCero, 'lista')  #  Imprime el resultado de la
                                         #  pregunta de si el objeto
                                         #  tiene el "atributo"

Siendo el resultado: True. En caso contrario, obviamente: False.

Por último, debo recordar que hasattr sirve también para clases, por lo que si cambiásemos el parámetro del objeto por el de la clase también funcionaría.

Semana 4

A continuación, vamos a trabajar con los métodos. Los métodos son las funciones dentro de las clases. Hay un método que ya hemos aprendido, que es el constructor. Dentro de la clase había que definir el constructor ligado a un parámetro llamado self. Esta es una característica importante de los métodos: al declararlos tienen como mínimo un parámetro al que, por lo general, se le llama self.

Si quisiéramos crear un método que acepte más parámetros, debemos escribir primero el self/objeto y tras él los parámetros que queramos. Veamos un ejemplo

Se crean de esta forma:

class Clase0:
    def metodo0(self)
        print('Método')
    def metodoX(self, var)
        print('Método ', var)

objeto = Clase0()
objeto.metodo0()
objeto.metodoX(2)

Resultado:

Método
Método 2

También se puede utilizar self para invocar otros métodos dentro de la clase:

class Clase0:
    def metodo0(self)
        print('Método')
    def otrometodo(self, var)

        print('Número: ', var)

objeto0 = Clase0()
objeto0.otrometodo(3)

Resultado:

Método 
Número 3

Resulta interesante saber lo que es un encapsulamiento. Ello consiste en ocultar ciertos atributos para proteger a los mismos de un acceso o una modificación no autorizada, de tal manera que esos pertenezcan solo a la clase o al objeto sin que puedan ser accesibles más que por ellos mismos. Al invocarlos Python devolverá un error ya que «no los encontrará» (en verdad sabe que existen, pero como ha recibido la orden de mantenerlos en privado se hace el sueco). Volviendo al ejemplo anterior:

class Clase0:
    variable0 = 'Esta es una variable accesible'
    __variable0 = 'Esta variable no es accesible'

    def metodo0(self):
        print('Método visible')

    def __metodo1(self):
        print('Método no visible')

obj = Clase0()

obj.variable0
obj._variable0
obj.metodo0()
obj.__metodo1()

Resultado:

Esta es una variable accesible
Traceback (most recent call last):
    File "main.py", line 14, in <module>
        obj._variable0
AttributeError: 'Clase0' object has no attribute '_variable0'
           #  El programa se detendrá con la primera variable
           #  que hemos ocultado al no encontrar la variable.
           #  Lo mismo pasará con el método oculto.

¿Cómo se pueden conocer las características de las clases y objetos?

En Python se pueden llevar a cabo dos tipos de actuaciones para ello:

  • Introspección: la capacidad de examinar las propiedades de un objeto mientras se ejecuta.
  • Reflexión: la capacidad de manipular las características de un objeto mientras se ejecuta.

Resulta importante conocer que las clases tienen un conjunto de propiedades y métodos per se que pueden utilizarse con ese fin. Entre ellos:

  • __dict__ Un diccionario que contiene los nombres y variables de todas las propiedades o atributos de la clase u objeto.
  •  __name__ Una cadena con el nombre de la clase. Si no supiéramos el nombre de la clase al que pertenece un objeto deberemos utilizar la función type().
  • __module__ Una cadena con el nombre del módulo que contiene la definición de la clase.
  • __bases__  Una tupla que consta de las superclases de una clase. Este atributo es propio de las clases, con los objetos no funcionaría.
  • __subclasses__()  Una lista con las subclases de la superclase. Es útil cuando desconocemos los nombres de estas.
class Clase0:

    def __init__(self):
        self.variable = 1

    def otravariable(self, val):
        self.otravariable = val

class Clase1:
    def __init__(self):
        pass

class Subclase01(Clase0, Clase1):
    pass

obj = Clase0()
obj.otravariable(2)

print(obj.__dict__)
print(type(obj).__name__)
print(Clase0.__module__)
print(Subclase01.__bases__)
print(Clase0.__subclasses__())

Resultado:

{'variable': 1, 'otravariable': 2}  #  El objeto tiene la primera 
                                    #  variable otorgada por el 
                                    #  constructor que vale 1 y 
                                    #  la segunda variable, a la que 
                                    #  hemos llamado "otravariable",
                                    #  que coge el valor que hemos
                                    #  dado, en este caso, 2.

Clase0                              #  Nombre de la clase a la que
                                    #  pertenece el objeto.

__main__                            #  El nombre del módulo.

(<class '__main__.Clase0'>, <class '__main__.Clase1'>)
                                    #  La Subclase01 pertenece a 
                                    #  dos clases.

[<class '__main__.Subclase01'>]     #  Subclase01 es subclase de 
                                    #  Clase0

Como curiosidad, basándonos en el ejemplo anterior, si imprimiésemos el objeto:

print(obj)

Python nos devolverá lo siguiente:

<__main__.Clase0 object at 0x7febcca0b410>

Nos dice que es objeto de Clase0 y nos da su «matrícula». Esta última variará de ordenador en ordenador.

Para evitar eso y darle nombre al objeto podríamos hacer uso del método __str__().

class Clase0:

    def __init__(self):
        self.variable = 1

    del __str___(self):
        return 'Objeto número ' + variable

print(obj)

Ahora el resultado será:

Objeto número 1

La Herencia

Todo ello nos sirve bien para abordar uno de los fundamentos de Python que es la herencia. En la semana 3 dijimos que las subclases heredaban las características de la clase superior. Un objeto también hereda los atributos de la clase a la que pertenece. Para investigar atributos de una clase u objeto existen ciertas funciones. Hay muchas pero entre otras podemos encontrar:

  • getattr(objeto, ‘nombredeatributo’): Devuelve el valor del atributo.
  • setattr(objeto, ‘nombredeatributo’, valor): Da un valor a un atributo.
  • issubclass(subclase, clase) / ininstance(objeto,clase): Sirven para lo mismo lo único que la primera va dirigida a clases y la otra a objetos. Devuelven un resultado True o False.
  • is/ is not. Con el podemos comprobar si dos variables hacen referencia al mismo objeto.
  • super(): Para acceder a la clase superior aun sin saber su nombre:
Super().__init__(parametro) 

Nota: No hace falta el self. Debemos tener en cuenta que para que un objeto de una subclase acceda a una variable de clase superior no hace falta nada. Sin embargo, para acceder a una variable de instancia de clase superior es necesario que esté invocado el constructor de la clase superior.

Resulta importante saber que, a la hora de buscar el atributo de un objeto, es necesario saber que Python buscará primero dentro del mismo objeto y después lo hará en toda la jerarquía de clases, yendo de abajo hacia arriba.

¿Puede una clase tener más de una clase superior?

Dado que cabe que haya más de una clase superior puede darse el supuesto en que tengan atributos contradictorios o con el mismo nombre. En estos casos opera la regla del overriding (anulación) según la cual, la última superclase que se haya definido anula los atributos de la anterior. Esto es, Python escanea las superclases de abajo a arriba.

¿Y puede haber más de una clase superior del mismo nivel?

Sí, lo hemos visto en este ejemplo. Si es que tuvieran variables contradictorias, imperan las de la superclase que esté a la izquierda. Es decir, que si están al mismo nivel Python las escanea de izquierda a derecha.

class Clase0:
    variable = 1

class Clase1:
    variable = 2

class Subclase(Clase1, Clase0):
    pass

objeto = Subclase()

print(objeto.variable)

Resultado:

2

Llegados a este punto podemos hablar de la jerarquía de clases. Hemos visto como se crean clases y subclases y un poco de la relación entre estas. Sin embargo es necesario conocer lo que es el polimorfismo, que no es más que la capacidad de una clase para tener formas distintas según la redefina una subclase. Esto flexibiliza la jerarquía de clases, cada clase podría tener atributos distintos según el momento. Cuando vemos un método de una superclase que es redefinido posteriormente estamos ante lo que se denomina un método virtual. Por otro lado, cuando vemos un método que ha sido declarado pero no implementado (por ejemplo con un pass), lo llamamos método abstracto.

Junto a la herencia tenemos lo que se conoce como composición, proceso por el cual componemos un objeto (otorgamos atributos) a través de otros objetos pertenecientes a otras clases.

Después de ver todo esto, nos damos cuenta de que las excepciones son clases y funcionan con las mismas reglas de jerarquías (ya vimos que incluso existía un árbol de excepciones). Es por ello que es posible crear nuevas excepciones creando subclases dentro de las excepciones predefinidas.

Generadores

Un generador de Python es un fragmento de código que sirve para devolver una serie de valores. También se les suele llamar iteradores porque cumplen con el protocolo iterador, según el cual se invoca una vez explicitamente y va devolviendo valores a medida que implicitamente se invoca de manera reiterada.

Un iterador cuenta con dos métodos:

  • __iter__() Al invocarse inicia la iteración y devuelve un valor.
  • __next__() En base a las sentencias for/in devuelve el siguiente valor. Cuando no hay más valores de la serie deseadase lanzará la excepción StopIteration.

Conviene hablar de la palabra clave reservada yield, que tiene una función parecida a return, pero a diferencia de return yield no provoca que se interrumpa la función. Todos los valores de las variables quedan en suspensión hasta que se reanuda la ejecución. En return en cambio la función se interumpe para volver a empezar desde el principio.

Compresión de listas

Tras ver mil formas de crear una lista, es hora de ver una manera simple y elegante de crear una lista al momento. En el siguiente ejemplo crearemos dos listas con las potencias de los números del 0 al 10 (incluido):

listaTipica = []

for i in range(11):
    listaTipica.append(i ** 2)

listaElegante = [i ** 2 for i in range(11)]

print(listaTipica)
print(listaElegante)

Resultado:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Hemos comprimido el código escribiéndolo en una línea, esto también se puede hacer incluyendo sentencias if y else. Ahora haremos una lista cambiando cada uno de los números impares del 0-10 por una ‘x’:

listaTipica = []

for i in range(11):

    if i % 2 == 0:
        listaTipica.append(i)
    else:
        listaTipica.append('x')


listaElegante = [i if i % 2 == 0 else 'x' for i in range(11)]

print(listaTipica)
print(listaElegante)

Resultado:

[0, 'x', 2, 'x', 4, 'x', 6, 'x', 8, 'x', 10]
[0, 'x', 2, 'x', 4, 'x', 6, 'x', 8, 'x', 10]

Existe una conexión entre la comprensión y los generadores y es que es posible convertir una comprensión en un generador cambiando los corchetes por unos paréntesis. Ejemplo:

compresor = [i for i in range(10)]
generador = (i for i in range(10))

for i in compresor:
    print(i, end=' ')
print()                     #  Introduzco este print() vacio
                            #  para que el siguiente bloque se
                            #  imprima en la siguiente linea.

for i in generador:
    print(i, end=' ')
print()

print(compresor)            #  Una forma de averiguar diferencias.
print(generador)

print(len(compresor)        #  Otra forma de ver las diferencias.
print(len(generador)

El resultado será:

0 1 2 3 4 5 6 7 8 9             #  Ambas devuelven los mismo valores.
0 1 2 3 4 5 6 7 8 9
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  #  Compresor es una lista.
<generator object <genexpr> at 0x7f25d91b2450>   
                                #  Nos indica que generador es un
                                #  objeto del tipo "generator"
                     
10                              #  Mientras compresor esta compuesto
                                #  de 10 elementos...

Traceback (most recent call last):
  File "main.py", line 16, in <module>
    print(len(generador))
TypeError: object of type 'generator' has no len()
                                #  Generador como tal no tiene un 
                                #  numero de elementos. Pero gracias
                                #  a él se van creando.   

Ahora veremos una serie de diferenes funciones que nos servirán con la programación orientada a objetos:

  • Lambda: Estas funciones sirven para crear funciones anónimas. Por lo general se usa para realizar funciones sencillas que pueden tener más de un parametro.
  • map(función, lista): Coge dos argumentos: función y lista. Ello entregará un iterador que devolverá cada elemento que surja de aplicar la función del primer argumento a cada elemento de la listas.
  • filter(función, lista): Coge el mismo tipo de parametros que map(). La diferencia estriba en que filtrará los elementos de la lista en base a que cumplan con lo que establezca la función. Si no pasan el filtro las rechazará.
ista = [0, 1, 2, 3, 4, 5]

potencias = list(map(lambda x: x ** 2, lista))

potenciasPares = list(filter(lambda x: x % 2 == 0 and x > 0, potencias))
     #  Tanto en esta linea como en la anterior he incluido 
     #  el list, ello se debe a que, como en el caso del 
     #  generador no nos devolvería una serie de números
     #  sino el tipo de objeto que es.

print(potencias)
print(potenciasPares)

Resultado:

[0, 1, 4, 9, 16, 25]
[4, 16]

En relación con los generadores nos encontramos los cierres. Estos son funciones que están dentro de otras funciones, por eso se les denomina funciones internas y funciones externas. Tienen las siguientes características:

  • Las funciones externas devuelven las funciones internas.
  • Las funciones internas solo se pueden invocar desde dentro de la función externa. Si las invocásemos desde fuera no daría un error.
  • Las funciones externas pueden tener parámetros o variables.
  • Las funciones internas hacen uso de parámetros o variables propios así como variables de las funciones externas.
  • Las funciones internas dan sentido o «cierran» las funciones externas. Sin las funciones internas las externas apenas tienen uso. Sin embargo, las funciones internas pueden funcionar una vez que las funciones ya no existen, incluso recordaran variables de las funciones externas.

Veamos un ejemplo:

def funcExterna(x):
    multi = x
    def funcInterna(i):         #  El parámetro i
        return i * multi
    return funcInterna

duplicador = funcExterna(2)

triplicador = funcExterna(3)

del funcExterna      #  Eliminamos la función externa

for i in range(5):           
     print(duplicador(i), triplicador(i))
                     #  Creamos un generador para que,
                     #  dándonos parámetros para usar
                     #  dentro de la función interna,
                     #  devuelva una serie de valores

 Resultado:

0 0
2 3
4 6
6 9
8 12

Procesar archivos

Ha llegado el momento de ver cómo se procesan los archivos en Python. A la hora de trabajar con archivos, Python no actúa en ellos directamente sino que lo hace a través de handles o streams, una especie de variables u «objetos archivo«. Estos pertenecerán a diferentes subclases dependientes de la clase I0Base (IO (o en español, ES) se refiere a In y Out (Entrada y Salida)) :

  • IOBase
    • RawIOBase: objetos archivo sin formato.
    • BufferedIOBase: objetos archivo binarios.
    • TextIOBase: objetos archivo de tipo texto.

Por lo tanto cada stream pertencerá a una clase y será creado al conectarlo con el archivo. De tal manera que al realizar operaciones en el stream estas acaben aplicándose en el archivo.

¿Cómo se conecta el stream con el archivo?

Abriéndolo. Para trabajar con el stream/archivo es necesario que lo abramos. Esto se hace con la siguiente función:

open(file, mode, encode)

Como vemos, open() tiene 3 parametros:

  • file: el nombre del archivo.
  • mode: el modo de apertura.
  • encode: el tipo de codificación del archivo.

El modo de apertura determina el modo en que se va a procesar el stream, estos son:

  • ‘r’: lectura. El archivo debe existir, si no Python devolverá un error.
  • ‘w’: escritura. Si el archivo no existe, se creará. Si existe y tiene contenido, este se borrará y se escribirá desde cero.
  • ‘a’: adjuntar. Si el archivo no existe, se creará. Si existe y tiene contenido, se escribirá desde el final.
  • ‘+’: actualizar. Por sí solo no funciona. Debe añadirse a un modo lectura o escritura (‘r+’ o ‘w+’). Sin embargo en ambos se podrá leer y escribir. La diferencia estriba en que en el modo ‘r+’ el archivo debe existir o Python devolverá un error, en cambio, en el modo ‘w+’ no hace falta que exista, pero si existe y tiene contenido, se escribirá desde el final.
  • ‘x’: este modo sirve exclusivamente para la creación del archivo. Si ya existe, Python devolverá un error.

Al definir el modo apertura es necesario indicar con qué tipo de stream queremos trabajar. Como hemos visto hay tres tipos de streams u objetos archivo, los sin formato, binario y texto. Nos centraremos en los dos últimos:

  • ‘b’: Modo binario. El archivo se guarda como bytes. Los streams se procesarán byte a byte o según una secuencia de bytes o bloques. Este modo suele reservarse para archivos de tipo imagen, videos, sonido o programas.
  • ‘t’: Modo texto. El archivo se guardará como cadenas (str). De este modo, los streams constarán de caracteres tipográficos dispuestos en líneas y serán leidos o escritos por caracteres o líneas. Luego entraremos más a fondo.

Los caracteres de referencia de estos dos modos se incluirán seguidamente a los modos de lectura, escritura, etc.

Open('archivo.txt', 'wb')   #  En este ejemplo el archivo
                            #  se abrirá en modo escritura
                            #  y binario.

Si no indicamos nada respecto al modo de apertura, Python, por defecto, abrirá el archivo en modo lectura de texto.

Respecto al tipo de codificación no vamos a profundizar, simplemente comentar que automáticamente se utiliza el UTF-8, podría usarse otro, como por ejemplo, el ASCII occidental ‘cp1252’. Si, como en el ejemplo, no indicásemos nada, funcionaría igualmente.

Streams pre-abiertos

Al iniciar Python hay 3 streams que ya están abiertos y que pertenecen al módulo sys. Estos son: sys.stdin, sys.stdout y sys.stderr:

  • sys.stdin
    • STanDard IN (entrada estándar).
    • Está vinculado al teclado.
  • sys.stdout
    • STanDard OUT (salida estándar).
    • Está vinculado con la pantalla.
  • sys.stderr
    • STanDard ERRor (salida de error estándar).
    • Se vincula con la pantalla donde aparecerá la información de los errores.

¿Cómo se cierra un stream?

Hemos visto que es necesario abrir un archivo para crear el stream y así trabajar con el archivo. Para acabar con la conexión es necesario invocar el método close() (cerrar).

archivo.close()

A la hora de operar con streams es posible que nos aparezcan errores, también denominadas errores de flujo. Hay una clase de excepciones dedicada a ellos: la IOerror, también denominada OSerror. Esta consta de una propiedad denominada errno la cual podemos utilizar para conocer el tipo de error. Los tipos de errores son abundantes, para no alargarnos diremos que pueden estar relacionadas con el nombre del archivo, el tamaño, la existencia o no del archivo…

Otra función que podemos utilizar para averiguar el error es strerror(), a la cual tendremos que dar como argumento la excepción más la propiedad errorno.

try:
    #  Cualquier operación

except Exception as e:
    print('El error es: ', strerror(e.errorno))

Nos devolverá un número de error y un mensaje describiendo el error.

Volviendo a los archivos de texto, dijimos que tenían dos modos basicamente: lectura y escritura.

En el modo que permita la lectura, hay diferentes métodos para leer el contenido del archivo:

  • read(). Podremos leer un número de caracteres que devolverá como una cadena. Si no hay nada que leer, la cadena estará vacía.
  • readline(). Lee el archivo por líneas, devolverá una cadena por línea. Si la línea está vacía, devolverá una cadena vacía.
  • readlines(x). Leerá todo el archivo y devuelve una lista de cadenas por línea. Si no hay nada que leer, devolverá una lista vacía. El parámetro identificado como ‘x’ sirve para introducir el número de bytes, esto viene bien para controlar el tamaño máximo de búfer de entrada. Si no se indica nada, leerá todo el archivo.

Como curiosidad decir que es posible convertir un stream en una instancia de la clase iterable. Esto se puede hacer a través de la función open() en el modo texto.

lineas = 0

for i in open('archivo.txt', 'rt'):  #  modo lectura texto
    lineas += 1

print(lineas)

Por otro lado, en los modos que permitan la escritura podemos utilizar el método write(). Este tomará como argumento una cadena que será la que se incluirá en el stream para ser escrita en el archivo.

archivo = open('archivo.txt', 'wt')

archivo.write('texto')

Semana 5

Una vez finalizado el curso de Python, me descargué la plataforma de Pycharm para realizar un primer laboratorio gracias al cual pude programar un asistente virtual. A continuación desgranaremos el código:

import speech_recognition as sr
import pyttsx3
import pywhatkit
import datetime
import wikipedia
import pyjokes

En este primer bloque importamos una serie de librerías que nos servirán para que funcione nuestro programa.

  • speech_recognition: Se utiliiza para convertir voz a texto; incluye varios métodos ligados al proceso de conversión. Para que funcione es necesario instalar previamente la librería pyaudio. No es extraordinario que al instalarlo devuelva un error. Siguiendo el consejo de otros internautas lo que hice fue descargar de esta web el archivo wheel compatible con mi ordenador y versión de python(un wheel es un paquete integrado comprimido). Luego desde Símbolo de sistema instale el wheel introduciendo el comando «pip install PyAudio‑0.2.11‑cp39‑cp39‑win_amd64.whl». Le damos el nombre «sr» para trabajar con él más adelante.
  • pyttsx3: Hace el camino inverso de speech_recognition. Convierte texto a voz.
  • pywhatkit: Esta librería sirve para enviar mensajes por whatsapp, así como reproducir videos en youtube y realizar búsquedas en google.
  • datetime: Sirve para manipular horas y fechas.
  • wikipedia: Para acceder a wikipedia y obtener datos de la misma.
  • pyjokes: Incluye chistes de programadores.
listener = sr.Recognizer()
engine = pyttsx3.init()
voices = engine.getProperty('voices')
engine.setProperty('voice', voices[0].id)
  • Creamos un «escuchador» (listener), ligado al método Recognizer() del speech_recognition.
  • Engine va a ser un objeto de pyttsx3 que va a ser quién nos va a contestar. Será nuestro asistente virtual.
  • Creamos una variable llamada «voices» (voces) para darle la propiedad a nuestro asistente de tener voces.
  • Luego fijamos la propiedad de nuestro engine de tener una voz, en nuestro caso una voz en castellano. Para que tuviera una voz en inglés habría que escribir «voices[1].id».
def hablar(text):
    engine.say(text)
    engine.runAndWait()

Definimos una función para que nuestro asistente responda.

  • Primero consiste en una función para responder con un mensaje.
  • Otra función para que hable o de en alto esa respuesta.
def tomar_orden():
    try:
        with sr.Microphone() as micro:
            print('Escuchando...')
            voz = listener.listen(micro)
            orden = listener.recognize_google(voz)
            orden = orden.lower()
            f = open("historial.txt", "a+")
            f.write(orden + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
            f.close()
            if 'alexa' in orden:
                orden = orden.replace('alexa', '')
                print(orden)
    except:
        pass
    return orden

Definimos la función «tomar_orden» que consista en tomar las órdenes que le damos al asistente virtual. Lo basaremos en un try/except para probar el código y evitar posibles errores.

  • Cogemos el método sr.Microphone() para que sea nuestra fuente de sonido que llamaremos «micro», de tal modo que funcione como input.
  • El mensaje «Escuchando…» nos sirve como señal de que el micrófono está funcionando.
  • Lo que el escuchador escuche del micro lo llamaremos «voz», porque será nuestra voz.
  • Seguidamente creamos una variable llamada «orden». Esta consistirá en el texto que el escuchador, a través del módulo de reconocimiento de voz de google, obtenga al convertir la voz.
  • Despues pasamos ese texto a mnúsculas para trabajar mejor con él.
  • Luego, como personalmente queremos tener un documento que recoja un historial de órdenes y respuestas, creamos/abrimos un archivo de texto en el modo adjuntar, para que en él se escriba la orden incluyendo la fecha y hora en la que se ha dado la orden. Esto se lleva a cabo haciendo uso del método now, de la clase datetime del módulo datetime. Como detalle, hemos incluido ‘%I:%M %p‘ esto nos da la hora en el formato a.m./p.m., para obtener el formato de 24 horas bastaría con un ‘%H:%M‘. Tras cada orden cerramos el documento.
  • Incluimos una sentecia if, para excluir el término «alexa» de nuestra «orden».
  • En el except le decimos que ante un error no haga nada.
  • Finalmente devolverá la «orden».
def mi_alexa():
    orden = tomar_orden()
    f = open("historial.txt", "a+")
    if 'reproduce' in orden:
        cancion = orden.replace('reproduce', '')
        hablar('Reproduciendo ' + cancion)
        f.write('Reproduciendo ' + cancion + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
        pywhatkit.playonyt(song)
    elif 'hora' in orden:
        time = datetime.datetime.now().strftime('%I:%M %p')
        hablar('Actualmente son las ' + time)
        f.write('Actualmente son las ' + time + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
    elif 'Quien narices es' in orden:
        personaje = orden.replace('Quien narices es', '')
        info = wikipedia.summary(personaje, 1)
        hablar(info)
        f.write(info + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
    datetime.datetime.now().strftime('%I:%M %p') + '\n')
    elif 'chiste' in orden:
        hablar(pyjokes.get_joke())
        f.write('Me sé uno en inglés: ' + pyjokes.get_joke() + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
    elif 'hakuna matata' in orden:
        hablar('Vive y deja vivir')
        f.write('Vive y deja vivir' + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
    else:
        hablar('No he entendido. Por favor, repite.')
        f.write('No he entendido. Por favor, repite.' + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
    f.close()

Definimos una función que determine qué tipo de respuestas va a dar nuestro asistente virtual.

  • Creamos una variable a la función tomar_orden.
  • Abrimos otra vez nuestro archivo «historial«.
  • El primer tipo de respuesta será para una orden donde le pidamos que reproduzca una canción. Si encuentra la palabra «reproduce», la borrará de la orden para tomar solo la parte del mensaje que identifica la canción y así crear una variable con ella. De este modo, nuestro asistente responderá que la va a reproducir y, tras incluir la respuesta en nuestro historial, abrirá el navegador para reproducir en YouTube la canción que le hemos ordenado. Playonyt significa reproducir en YouTube.
  • Luego otro supuesto en el que si escucha en la orden que hemos dicho «hora», nos devuelva la hora actual (primero determinamos la hora actual y luego devuelve el mensaje.
  • Otra condición para que nos devuelva información de wikipedia sobre un personaje. Esta funcionará igual que la orden de reproducir una canción, pero en su caso hará uso de la librería wikipedia para que a través del método resume nos devuelva un resumen de la entrada en wikipedia (como argumentos tomará la variable que le hemos dado más una variable correspondiente al número de líneas del texto que queremos que nos devuelva).
  • Otro supuesto en el que nos cuente un chiste. Cuando capte la palabra «chiste», hará uso de la función get_joke() para contarnos un chiste.
  • Otra situación en la que simplemente si oye «hakuna matata» responda «vive y deja vivir».
  • Para acabar, si no capta ningún mensaje de los anteriores, le hemos indicado que responda que no entiende y que repitamos. Para finalmente cerrar nuestro historial.
while True:
    mi_alexa()

Finalmente, creamos un bucle para que nuestro asistente virtual funcione ad infinitum.

#  Bloque 1
import speech_recognition as sr
import pyttsx3
import pywhatkit
import datetime
import wikipedia
import pyjokes

#  Bloque 2
listener = sr.Recognizer()
engine = pyttsx3.init()
voices = engine.getProperty('voices')
engine.setProperty('voice', voices[0].id)

#  Bloque 3
def hablar(text):
    engine.say(text)
    engine.runAndWait()

#  Bloque 4
def tomar_orden():
    try:
        with sr.Microphone() as micro:
            print('Escuchando...')
            voz = listener.listen(micro)
            orden = listener.recognize_google(voz)
            orden = orden.lower()
            f = open("historial.txt", "a+")
            f.write(orden + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
            f.close()
            if 'alexa' in orden:
                orden = orden.replace('alexa', '')
                print(orden)
    except:
        pass
    return orden

#  Bloque 5
def mi_alexa():
    orden = tomar_orden()
    f = open("historial.txt", "a+")
    if 'reproduce' in orden:
        cancion = orden.replace('reproduce', '')
        respuesta = hablar('Playing ' + cancion)
        f.write('Reproduciendo ' + cancion + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
        pywhatkit.playonyt(song)
    elif 'hora' in orden:
        time = datetime.datetime.now().strftime('%I:%M %p')
        hablar('Actualmente son las ' + time)
        f.write('Actualmente son las ' + time + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
    elif 'Quien narices es' in orden:
        personaje = orden.replace('Quien narices es', '')
        info = wikipedia.summary(personaje, 1)
        hablar(info)
        f.write(info + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
    datetime.datetime.now().strftime('%I:%M %p') + '\n')
    elif 'chiste' in orden:
        hablar(pyjokes.get_joke())
        f.write('Me sé uno en inglés: ' + pyjokes.get_joke() + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
    elif 'hakuna matata' in orden:
        hablar('Vive y deja vivir')
        f.write('Vive y deja vivir' + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
    else:
        hablar('No he entendido. Por favor, repite.')
        f.write('No he entendido. Por favor, repite.' + ' ' + datetime.datetime.now().strftime('%I:%M %p') + '\n')
    f.close()

#  Bloque 6
while True:
    mi_alexa()