I'm noob at programming in python, and tried to make a script to help my kids to improve their reading and math skills.
I have this code:
import tkinter as tk
from tkinter import messagebox
import random
from num2words import num2words
import inflect
import inspect
import time
print(inspect)
# Inicializamos la biblioteca inflect
p = inflect.engine()
# Velocidad predeterminada para mostrar palabras
velocidad_predeterminada = 1 # en segundos
palabras = [ "Abrir", "Amor", "Aprender", "Amigo", "Año", "Azul"]
# Lista de palabras en español
generando_palabras = False # Variable global para controlar la generación de palabras
# Código de la clase MatQuizApp aquí (no modificado)
class MathQuizApp:
def __init__(self, root):
self.root = root
self.root.title("Math Quiz")
self.root.geometry("500x400")
self.score = 0
# Widgets for setting the number of digits
self.digit_label = tk.Label(root, text="Número de dígitos:")
self.digit_label.pack()
self.digit_entry = tk.Entry(root)
self.digit_entry.pack()
self.start_button = tk.Button(root, text="Iniciar", command=self.start_quiz)
self.start_button.pack()
# Frame for displaying the operation
self.operation_frame = tk.Frame(root)
self.operation_frame.pack(pady=20)
self.symbol_label = tk.Label(self.operation_frame, font=("Helvetica", 24))
self.symbol_label.grid(row=0, column=0, rowspan=2)
self.num1_labels = []
self.num2_labels = []
self.answer_entries = []
self.num1_carry = []
self.num2_carry = []
for i in range(5):
carry_num1_entry = tk.Entry(self.operation_frame, font=("Helvetica", 10), width=2)
carry_num1_entry.grid(row=0, column=i + 1, sticky='W')
carry_num1_entry.bind('<KeyRelease>', self.on_carry_num1_change)
self.num1_carry.append(carry_num1_entry)
num1_label = tk.Label(self.operation_frame, font=("Helvetica", 24))
num1_label.grid(row=0, column=i + 1, sticky='E')
self.num1_labels.append(num1_label)
carry_num2_entry = tk.Entry(self.operation_frame, font=("Helvetica", 10), width=2)
carry_num2_entry.grid(row=1, column=i + 1, sticky='W')
carry_num2_entry.bind('<KeyRelease>', self.on_carry_num2_change)
self.num2_carry.append(carry_num2_entry)
num2_label = tk.Label(self.operation_frame, font=("Helvetica", 24))
num2_label.grid(row=1, column=i + 1, sticky='E')
self.num2_labels.append(num2_label)
answer_entry = tk.Entry(self.operation_frame, font=("Helvetica", 24), width=2)
answer_entry.grid(row=2, column=i + 1)
self.answer_entries.append(answer_entry)
# Buttons for quiz actions
self.validate_button = tk.Button(root, text="Validar", command=self.validate_answer)
self.retry_button = tk.Button(root, text="Reintentar", command=self.retry)
self.continue_button = tk.Button(root, text="Continuar", command=self.continue_quiz)
self.exit_button = tk.Button(root, text="Salir", command=root.quit)
# Score label
self.score_label = tk.Label(root, text=f"Score: {self.score}", font=("Helvetica", 14))
self.score_label.pack(anchor='ne', padx=10, pady=5)
# Initially hide quiz widgets
self.hide_quiz_widgets()
def hide_quiz_widgets(self):
self.operation_frame.pack_forget()
self.validate_button.pack_forget()
self.retry_button.pack_forget()
self.continue_button.pack_forget()
self.exit_button.pack_forget()
def show_quiz_widgets(self):
self.operation_frame.pack(pady=20)
self.validate_button.pack()
self.retry_button.pack()
self.continue_button.pack()
self.exit_button.pack()
def start_quiz(self):
try:
self.num_digits = int(self.digit_entry.get())
if self.num_digits < 1 or self.num_digits > 5:
raise ValueError("Número de dígitos debe ser entre 1 y 5.")
except ValueError as e:
messagebox.showerror("Error", str(e))
return
self.digit_entry.pack_forget()
self.digit_label.pack_forget()
self.start_button.pack_forget()
self.show_quiz_widgets()
self.generate_operation()
def generate_operation(self):
max_value = 10 ** self.num_digits
self.num1 = random.randint(1, max_value - 1)
self.num2 = random.randint(1, max_value - 1)
self.operation = random.choice(['+', '-'])
if self.operation == '-':
# Ensure the result is non-negative
if self.num1 < self.num2:
self.num1, self.num2 = self.num2, self.num1
self.show_carry_entries()
else:
self.hide_carry_entries()
self.symbol_label.config(text=f"{self.operation}")
self.update_labels(self.num1, self.num1_labels)
self.update_labels(self.num2, self.num2_labels)
self.clear_entries()
def update_labels(self, number, labels):
num_str = str(number).zfill(5)
for i, digit in enumerate(num_str):
labels[i].config(text=digit)
def clear_entries(self):
for entry in self.answer_entries:
entry.delete(0, tk.END)
for entry in self.num1_carry + self.num2_carry:
entry.delete(0, tk.END)
def hide_carry_entries(self):
for entry in self.num2_carry:
entry.grid_remove()
def show_carry_entries(self):
for entry in self.num2_carry:
entry.grid()
def get_user_answer(self):
answer_str = ''.join(entry.get().zfill(1) for entry in self.answer_entries)
try:
return int(answer_str)
except ValueError:
return None
def validate_answer(self):
user_answer = self.get_user_answer()
if user_answer is None:
messagebox.showerror("Error", "Por favor, ingresa un número válido.")
return
correct_answer = self.num1 + self.num2 if self.operation == '+' else self.num1 - self.num2
if user_answer == correct_answer:
messagebox.showinfo("Correcto", "¡FELICIDADES la respuesta es CORRECTA!")
self.score += 1
self.score_label.config(text=f"Score: {self.score}")
self.generate_operation()
else:
response = messagebox.askquestion("Incorrecto", "La respuesta es incorrecta. ¿Quieres reintentar?",
icon='warning')
if response == 'yes':
self.retry()
else:
self.continue_quiz()
def retry(self):
self.clear_entries()
def continue_quiz(self):
self.generate_operation()
def on_carry_num1_change(self, event):
entry = event.widget
if self.operation == '+':
self.add_plus_symbol(entry)
def on_carry_num2_change(self, event):
entry = event.widget
if self.operation == '-':
self.add_plus_symbol(entry)
def add_plus_symbol(self, entry):
content = entry.get()
if content and not content.endswith('+'):
entry.delete(0, tk.END)
entry.insert(0, f"{content}+")
# Código de la clase MathGame aquí (no modificado)
class MathGame:
def __init__(self, root):
self.root = root
self.root.title("Juego de Tablas Matemáticas")
self.root.geometry("800x400") # Tamaño de la ventana aumentado
self.score = 0
self.errors = 0
self.label = tk.Label(root, text="Elige una o más tablas para practicar:", font=("Arial", 14))
self.label.pack(pady=10)
# Checkbox variables for selecting multiple tables
self.table_vars = {}
self.checkboxes_frame = tk.Frame(root)
self.checkboxes_frame.pack(pady=10)
for i in range(1, 11):
var = tk.IntVar()
checkbox = tk.Checkbutton(self.checkboxes_frame, text=f"Tabla del {i}", variable=var, font=("Arial", 14))
checkbox.grid(row=(i - 1) // 5, column=(i - 1) % 5, padx=5, pady=5)
self.table_vars[i] = var
self.start_button = tk.Button(root, text="Empezar", font=("Arial", 14), command=self.start_game)
self.start_button.pack(pady=10)
self.question_frame = tk.Frame(root)
self.score_label = tk.Label(root, text=f"Puntaje: {self.score} | Errores: {self.errors}", font=("Arial", 14))
def start_game(self):
self.selected_tables = [table for table, var in self.table_vars.items() if var.get() == 1]
if not self.selected_tables:
self.label.config(text="Por favor, elige al menos una tabla.")
return
self.label.pack_forget()
self.checkboxes_frame.pack_forget()
self.start_button.pack_forget()
self.question_frame.pack(pady=10)
self.score_label.pack(pady=10)
self.generate_question()
def generate_question(self):
self.table = random.choice(self.selected_tables)
self.x = random.randint(1, 10)
correct_answer = self.table * self.x
options = [correct_answer, random.randint(1, 100), random.randint(1, 100)]
random.shuffle(options)
self.question_label = tk.Label(self.question_frame, text=f"{self.table} x {self.x} =", font=("Arial", 14))
self.question_label.grid(row=0, column=0, padx=10, pady=10)
self.answer_var = tk.StringVar()
self.answer_var.set(None) # Opciones sin seleccionar al inicio
self.option_buttons = []
for i, option in enumerate(options):
button = tk.Radiobutton(self.question_frame, text=option, variable=self.answer_var, value=option,
font=("Arial", 14))
button.grid(row=i + 1, column=0, padx=10, pady=10)
self.option_buttons.append(button)
self.submit_button = tk.Button(self.question_frame, text="Enviar", font=("Arial", 14),
command=self.check_answer)
self.submit_button.grid(row=4, column=0, padx=10, pady=10)
def check_answer(self):
selected = self.answer_var.get()
if not selected:
self.question_label.config(text=f"Por favor selecciona una respuesta.")
return
selected = int(selected)
correct_answer = self.table * self.x
if selected == correct_answer:
self.score += 1
messagebox.showinfo("Correcto", "¡Respuesta correcta!")
else:
self.errors += 1
messagebox.showerror("Incorrecto", f"La respuesta correcta era {correct_answer}.")
self.score_label.config(text=f"Puntaje: {self.score} | Errores: {self.errors}")
self.generate_question()
# Función para practicar Números en Español
def practicar_numeros_espanol():
def generar_numero():
try:
digitos = int(entry_digitos.get())
if digitos <= 0:
raise ValueError("El número de dígitos debe ser mayor que 0.")
numero = random.randint(10**(digitos-1), (10**digitos)-1)
numero_palabras = num2words(numero, lang='es') # Convertir a palabras en español
numero_mostrado.set(numero_palabras.capitalize())
numero_generado.set(numero)
except ValueError:
messagebox.showerror("Error", "Por favor, ingresa una cantidad válida de dígitos.")
def verificar_respuesta():
try:
respuesta = int(entry_respuesta.get())
if respuesta == numero_generado.get():
correcto.set(correcto.get() + 1)
messagebox.showinfo("Resultado", "¡Correcto!")
else:
incorrecto.set(incorrecto.get() + 1)
messagebox.showinfo("Resultado", "Incorrecto")
generar_numero() # Generar otro número después de verificar
entry_respuesta.delete(0, tk.END) # Limpiar el campo de texto
except ValueError:
messagebox.showerror("Error", "Por favor, ingresa un número válido.")
# Configuración de la ventana
ventana_numeros = tk.Toplevel(ventana)
ventana_numeros.title("Juego de Números en Palabras en Español")
# Variables
numero_mostrado = tk.StringVar()
numero_generado = tk.IntVar()
correcto = tk.IntVar(value=0)
incorrecto = tk.IntVar(value=0)
# Interfaz gráfica
tk.Label(ventana_numeros, text="Ingresa la cantidad de dígitos:").pack(pady=5)
entry_digitos = tk.Entry(ventana_numeros)
entry_digitos.pack(pady=5)
tk.Button(ventana_numeros, text="Generar número", command=generar_numero).pack(pady=5)
tk.Label(ventana_numeros, text="Número mostrado (en palabras):").pack(pady=5)
tk.Label(ventana_numeros, textvariable=numero_mostrado, font=("Helvetica", 16)).pack(pady=5)
tk.Label(ventana_numeros, text="Escribe el número:").pack(pady=5)
entry_respuesta = tk.Entry(ventana_numeros)
entry_respuesta.pack(pady=5)
tk.Button(ventana_numeros, text="Verificar respuesta", command=verificar_respuesta).pack(pady=5)
tk.Label(ventana_numeros, text="Correcto:").pack(pady=5)
tk.Label(ventana_numeros, textvariable=correcto).pack()
tk.Label(ventana_numeros, text="Incorrecto:").pack(pady=5)
tk.Label(ventana_numeros, textvariable=incorrecto).pack()
# Función para practicar Números en Inglés
def practicar_numeros_ingles():
def generar_numero():
try:
digitos = int(entry_digitos.get())
if digitos <= 0:
raise ValueError # Evitar que se ingresen valores no válidos
numero = random.randint(10**(digitos-1), (10**digitos)-1)
numero_palabras = p.number_to_words(numero)
numero_mostrado.set(numero_palabras.capitalize())
numero_generado.set(numero)
except ValueError:
messagebox.showerror("Error", "Por favor, ingresa una cantidad válida de dígitos")
def verificar_respuesta():
try:
respuesta = int(entry_respuesta.get())
if respuesta == numero_generado.get():
correcto.set(correcto.get() + 1)
messagebox.showinfo("Resultado", "¡Correcto!")
else:
incorrecto.set(incorrecto.get() + 1)
messagebox.showinfo("Resultado", "Incorrecto")
generar_numero() # Generar otro número después de verificar
entry_respuesta.delete(0, tk.END) # Limpiar el campo de texto
except ValueError:
messagebox.showerror("Error", "Por favor, ingresa un número válido")
# Configuración de la ventana
ventana_numeros = tk.Toplevel(ventana)
ventana_numeros.title("Juego de Números en Palabras en Inglés")
# Variables
numero_mostrado = tk.StringVar()
numero_generado = tk.IntVar()
correcto = tk.IntVar(value=0)
incorrecto = tk.IntVar(value=0)
# Interfaz gráfica
tk.Label(ventana_numeros, text="Ingresa la cantidad de dígitos:").pack(pady=5)
entry_digitos = tk.Entry(ventana_numeros)
entry_digitos.pack(pady=5)
tk.Button(ventana_numeros, text="Generar número", command=generar_numero).pack(pady=5)
tk.Label(ventana_numeros, text="Número mostrado (en palabras):").pack(pady=5)
tk.Label(ventana_numeros, textvariable=numero_mostrado, font=("Helvetica", 16)).pack(pady=5)
tk.Label(ventana_numeros, text="Escribe el número:").pack(pady=5)
entry_respuesta = tk.Entry(ventana_numeros)
entry_respuesta.pack(pady=5)
tk.Button(ventana_numeros, text="Verificar respuesta", command=verificar_respuesta).pack(pady=5)
tk.Label(ventana_numeros, text="Correcto:").pack(pady=5)
tk.Label(ventana_numeros, textvariable=correcto).pack()
tk.Label(ventana_numeros, text="Incorrecto:").pack(pady=5)
tk.Label(ventana_numeros, textvariable=incorrecto).pack()
# Función para practicar lectura
def practicar_lectura():
def mostrar_palabra_aleatoria():
global generando_palabras
if generando_palabras:
cantidad_palabras = int(entrada_cantidad_palabras.get())
palabras_aleatorias = random.choices(palabras, k=cantidad_palabras)
etiqueta.config(text=" ".join(palabras_aleatorias))
try:
velocidad = float(entrada_velocidad.get())
if velocidad > 0:
pausa = max(0.01, velocidad) # Establecer una pausa mínima de 0.01 segundos
ventana.after(int(pausa * 1000), mostrar_palabra_aleatoria)
else:
ventana.after(velocidad_predeterminada * 1000, mostrar_palabra_aleatoria)
except ValueError:
ventana.after(velocidad_predeterminada * 1000, mostrar_palabra_aleatoria)
def iniciar_generacion():
global generando_palabras
generando_palabras = True
cantidad_palabras = int(entrada_cantidad_palabras.get())
if cantidad_palabras > 0:
mostrar_palabra_aleatoria()
def detener_generacion():
global generando_palabras
generando_palabras = False
# Configuración de la ventana
ventana_lectura = tk.Toplevel(ventana)
ventana_lectura.title("Palabras al Azar")
ventana_lectura.geometry("1200x400")
etiqueta = tk.Label(ventana_lectura, font=("Arial", 50))
etiqueta.pack(pady=50)
etiqueta_velocidad = tk.Label(ventana_lectura, text="¿Segundos entre Palabras?")
etiqueta_velocidad.pack()
entrada_velocidad = tk.Entry(ventana_lectura, width=10)
entrada_velocidad.pack(pady=10)
entrada_velocidad.insert(0, velocidad_predeterminada)
etiqueta_cantidad_palabras = tk.Label(ventana_lectura, text="Cantidad de palabras para mostrar")
etiqueta_cantidad_palabras.pack()
entrada_cantidad_palabras = tk.Entry(ventana_lectura, width=10)
entrada_cantidad_palabras.pack(pady=10)
entrada_cantidad_palabras.insert(0, "1")
boton_iniciar = tk.Button(ventana_lectura, text="Iniciar", command=iniciar_generacion)
boton_iniciar.pack(pady=10)
boton_detener = tk.Button(ventana_lectura, text="Detener", command=detener_generacion)
boton_detener.pack(pady=10)
# Función para abrir las diferentes prácticas
def abrir_practica(practica):
if practica == "numeros_espanol":
practicar_numeros_espanol()
elif practica == "numeros_ingles":
practicar_numeros_ingles()
elif practica == "lectura":
practicar_lectura()
elif practica == "tablas":
MathGame(tk.Toplevel(ventana)) # Abre el juego de tablas en una nueva ventana
elif practica == "SumasyRestas":
MathQuizApp(tk.Toplevel(ventana)) # Abre el juego de tablas en una nueva ventana
# Configuración de la ventana principal
ventana = tk.Tk()
ventana.title("¿Que vamos a practicar hoy?")
ventana.geometry("400x400")
# Botones para seleccionar la práctica
tk.Button(ventana, text="Practicar Tablas", command=lambda: abrir_practica("tablas")).pack(pady=10) # Nuevo botón para practicar tablas
tk.Button(ventana, text="Practicar Sumas Y Restas", command=lambda: abrir_practica("SumasyRestas")).pack(pady=10) # Nuevo botón para practicar tablas
tk.Button(ventana, text="Practicar Lectura", command=lambda: abrir_practica("lectura")).pack(pady=10)
tk.Button(ventana, text="Practicar Números en Español", command=lambda: abrir_practica("numeros_espanol")).pack(pady=10)
tk.Button(ventana, text="Practicar Números en Inglés", command=lambda: abrir_practica("numeros_ingles")).pack(pady=10)
# Iniciar el bucle principal
ventana.mainloop()
and i created the .exe file using: pyinstaller --onefile -w main.py. but when i try to run the main.exe file in the dist folder, i get this error:
"Failed to execute script 'main' due to unhandled exception: could not get source code"
Traceback (most recent call last):
File "main.py", line 5, in <module>
File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
File "<frozen importlib._bootstrap>", line 1149, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
File "PyInstaller\loader\pyimod02_importers.py", line 378, in exec_module
File "inflect\__init__.py", line 2071, in <module>
File "inflect\__init__.py", line 2092, in engine
File "typeguard\_decorators.py", line 223, in typechecked
File "typeguard\_decorators.py", line 71, in instrument
File "inspect.py", line 1262, in getsource
File "inspect.py", line 1244, in getsourcelines
File "inspect.py", line 1081, in findsource
OSError: could not get source code
The Script is running with no issues within pycharm when i click the play button, but i just cant have the .exe file working.
I already look for answer withing google and chatgpt and none of the solutions suggested worked.
thanks in advance for your support.
Doing some digging on this, I ended up here: https://github.com/pyinstaller/pyinstaller/issues/8013
The module inflect
uses a dataset and the traceback points to line 5 which is the import for inflect
.
This means that you'll need to apply the solution from the github issue to your project.
If you run pyinstaller --onefile -w main.py
it creates a main.spec
file which you'll need to edit. You need to add this:
module_collection_mode={
'inflect': 'pyz+py',
}
to the section a = Analysis
.
For me I ended up with the following main.spec
file:
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
module_collection_mode={
'inflect': 'pyz+py',
}
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
Finally, you need to build the .EXE with this command:
pyinstaller main.spec