Mostrando las entradas con la etiqueta python. Mostrar todas las entradas
Mostrando las entradas con la etiqueta python. Mostrar todas las entradas

lunes, 10 de agosto de 2020

BUSCAR Y BORRAR MULTIPLES ARCHIVOS

Este es un programa similar al listador de archivos (Enlace), Este programa busca  todos los archivos con algún nombre o extension en específico que esten en una direccion dada, guarda el resultado de la búsqueda en un archivo de texto tambien da la opcion de borrarlos. Muy util cuando hay muchos archivos indeseables con diferentes nombres o extensiones.

Lo modifiqué para que se pudiera buscar mas de un tipo de archivo o nomnre. Se deben poner en una lista serarada por una coma como se muestra. Al terminar la búsqueda, en la barra de estado muestra cuantos archivos encontró.

Requiere wxpython y python 3 para correr este programa.

import wx
from os      import getcwd
from os      import walk
from os      import stat
from os.path import isfile
from os      import remove


class MyFrame(wx.Frame):
    def __init__(self, parent, title ="Borrador multiple"):
        super().__init__(parent, title = title , size = (600,240))
        self.SetMinSize(size = (600,240)) #Set minimum size to current
        self.locale   = wx.Locale(wx.LANGUAGE_ENGLISH) # Fix locale unknown error
        self.Dir    = getcwd() #directorio por defecto
        self.DirSve = getcwd()+"\DefaultName.txt" #directorio por defecto
        #--------------------------------------------------
        #                   Widgets
        #---------------------------------------------------
        self.CreateStatusBar()
        self.SetStatusText("")

        panel = wx.Panel(self, wx.ID_ANY)

        BtnDirSrch = wx.Button(panel, label="Dir. Busqueda")
        self.TxtDirSrch = wx.TextCtrl( panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtDirSrch.SetEditable(False)
        self.TxtDirSrch.SetValue(self.Dir)


        BtnDirRes = wx.Button(panel, label="Dir. Guardar")
        self.TxtDirRes = wx.TextCtrl( panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtDirRes.SetEditable(False)
        self.TxtDirRes.SetValue(self.DirSve)
        
        self.TxtFleToSrch = wx.TextCtrl( panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtFleToSrch.SetEditable(True)
        LblNombre = wx.StaticText(panel)
        LblNombre.SetLabel("extencion o nombre") 
        
        BtnProcesar = wx.Button(panel, label="BUSCAR Y GENERAR LISTA")
        BtnBorrar   = wx.Button(panel, label="BORRAR ARCHIVOS!!!!!")

        #--------------------------------------------------
        #                   BINDS
        #--------------------------------------------------
        BtnProcesar.Bind(wx.EVT_BUTTON, self.onSearch )
        BtnDirSrch .Bind(wx.EVT_BUTTON, self.onDirSrch)
        BtnDirRes  .Bind(wx.EVT_BUTTON, self.onDirSve )
        BtnBorrar  .Bind(wx.EVT_BUTTON, self.onErseFles)
        #--------------------------------------------------
        #                   LAYOUT
        #--------------------------------------------------
        MainSizer = wx.BoxSizer(wx.VERTICAL)
        
        sizrGrid = wx.FlexGridSizer(rows=3, cols=2, hgap=5, vgap=5)
        sizrGrid.AddGrowableCol(1, 1)

        sizrGrid.Add(BtnDirSrch,        0, wx.CENTER)
        sizrGrid.Add(self.TxtDirSrch,   1, wx.CENTER|wx.EXPAND)
        sizrGrid.Add(BtnDirRes,         0, wx.CENTER)
        sizrGrid.Add(self.TxtDirRes,    1, wx.CENTER|wx.EXPAND)
        sizrGrid.Add(LblNombre,         0, wx.CENTER)
        sizrGrid.Add(self.TxtFleToSrch, 0, wx.CENTER|wx.EXPAND)
        
        MainSizer.Add(sizrGrid,   0,wx.ALL|wx.EXPAND,5)
        MainSizer.Add(BtnProcesar,1,wx.ALL|wx.EXPAND,5)
        MainSizer.Add(BtnBorrar,  1,wx.ALL|wx.EXPAND,5) 
        panel.SetSizer(MainSizer)

    def cleanText(self):
        #-------------------------------------------
        #     Palabras no necesarias
        #-------------------------------------------
        quitar = ['',  #en blanco 
                  'de',
                  'del',
                  'el',
                  'la',
                  'los',
                  'las',
                  'y',
                  'en',
                  'con',
                  'para',
                  'por',
                  'a',
                  'desde',
                  'hacia',
                  'excepto',
                  'entre',
                  'sin',
                  'sobre',
                  'tras',
                  'contra',
                  'hasta',
                  'durante',
                  'mediante',
                  'también',
                  'tambien']
        
        FindValue = self.TxtFleToSrch.GetValue()  # obtener texto
        #----------------------------------------------
        #               Tokenizar
        #----------------------------------------------
        FindValue = FindValue.replace(" ",",")
        words = FindValue.split(',')
        for x in quitar:
                while x in words: # quitar de la lista palaras inesesarias
                    words.remove(x)
        return words
        
    def onSearch (self, event):
        DirValuesFound = []
        List = self.cleanText()              
        
        GetBusy = wx.BusyInfo("Procesandio, por favor espere", self) #Mostrar Leyenda (ocupado)
        for root, dirs, files in walk(self.Dir):
            for file in files:
                for value in List:
                    if str(file).find(value) != -1:
                        DirValuesFound.append(root+'/'+str(file)) # Direccion completa
            
        file = open(self.DirSve,'w')
        for x in DirValuesFound:
            file.write(x+"\n")
        file.close()
        self.SetStatusText("Se encontraron " + str(len(DirValuesFound))+ " archivos.")
        del GetBusy # quitar leyenda (ocupado)

    def onErseFles(self, evt):
        RemovedCount = 0
        if isfile(self.DirSve): #archivo existe
            if not stat(self.DirSve).st_size == 0: # lista no vacia
                file = open(self.DirSve,'r')
                filesList = file.read().splitlines()
                file.close()
                for file in filesList:
                    try:
                        remove(file)
                        RemovedCount += 1
                    except:
                        pass
                self.SetStatusText("Se borraron " + str(RemovedCount)+ " archivos.")             
        else:
            self.SetStatusText('No has realizado ninguna busqueda \no el archivo generado fue borrado')

    def onDirSrch(self, event):
        dlg = wx.DirDialog(self, "Elegir directorio de busqueda:",
                           style=wx.DD_DEFAULT_STYLE
                           #| wx.DD_DIR_MUST_EXIST
                           #| wx.DD_CHANGE_DIR
                           )
        if dlg.ShowModal() == wx.ID_OK:
            self.Dir = dlg.GetPath()
            self.TxtDirSrch.SetValue(self.Dir)
        dlg.Destroy()

    def onDirSve(self, event):
        wildcard = "Text Files (*.txt)|*.txt"
        dlg = wx.FileDialog(
            self, message="Elegir dereccion y numbre", 
            defaultDir=self.DirSve, 
            defaultFile="", wildcard=wildcard, style=wx.FD_SAVE
            )
        if dlg.ShowModal() == wx.ID_OK:
            self.DirSve = dlg.GetPath()
            self.TxtDirRes.SetValue(self.DirSve)
        dlg.Destroy()
               
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()

martes, 21 de julio de 2020

Tablas HTML a Tablas de Excel


Este programa extrae todas las tabulaciones tradicionales en en un archivo HTML local y las guarda en un archivo de Excel, donde cada tabla tendrá su hoja. En este ejemplo se extrae el texto o los links de las imagenes dependiendo de lo que contenga la tabla..



Para correr este programa se requiere Python 3 y las librerías conocidas como wxpython, Beautifulsoup y oprnpyxl. Se instalan fácilmente usando pip.

El código fuente esta comentado para que sepas lo que sucede.

import wx
import wx.lib.agw.hyperlink as hl
from   os              import getcwd
from   openpyxl        import Workbook
from   openpyxl.styles import Border,Side, Alignment, Font, Color, PatternFill
from   bs4             import BeautifulSoup

class MyFrame(wx.Frame):
    def __init__(self, parent, title ="Extractor de Tablas HTML"):
        super().__init__(parent, title = title , size = (600,240))
        self.SetMinSize(size = (600,240)) #Set minimum size to current
        self.locale   = wx.Locale(wx.LANGUAGE_ENGLISH) # Fix locale unknown error
        self.Dir    = ""                       #directorio por defecto
        self.DirSve = getcwd()+"\DefaultName.xlsx" #directorio por defecto guardar
        self.wb = Workbook()
        #--------------------------------------------------
        #                   Widgets
        #---------------------------------------------------
        self.CreateStatusBar()
        self.SetStatusText("")

        panel = wx.Panel(self, wx.ID_ANY)

        BtnDirSrch = wx.Button(panel, label="HTML")
        self.TxtDirSrch = wx.TextCtrl( panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtDirSrch.SetEditable(False)
        self.TxtDirSrch.SetValue("")

        BtnDirRes = wx.Button(panel, label="Dir. Guardar")
        self.TxtDirRes = wx.TextCtrl( panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.TxtDirRes.SetEditable(False)
        self.TxtDirRes.SetValue(self.DirSve)
        
        BtnProcesar = wx.Button(panel, label="Crear Tabla de Excel")

        LblDeveloper = wx.StaticText(panel,-1, style = wx.ALIGN_CENTER | wx.ST_ELLIPSIZE_MIDDLE) 
        LblDeveloper.SetLabel("http://mecatroncharly.blogspot.com") 
        LblDeveloper.SetForegroundColour((255,255,255)) 
        LblDeveloper.SetBackgroundColour((0,0,0))

        hyper1 = hl.HyperLinkCtrl(panel, -1, "http://mecatroncharly.blogspot.com",
                                  URL="http://mecatroncharly.blogspot.com")
        hyper1.SetColours("BLUE", "BLUE", "BLUE")
        #--------------------------------------------------
        #                   BINDS
        #--------------------------------------------------
        BtnProcesar.Bind(wx.EVT_BUTTON, self.onProcesar )
        BtnDirSrch .Bind(wx.EVT_BUTTON, self.onSetDirHtml)
        BtnDirRes  .Bind(wx.EVT_BUTTON, self.onSetDirSve )
        #--------------------------------------------------
        #                   LAYOUT
        #--------------------------------------------------
        MainSizer = wx.BoxSizer(wx.VERTICAL)
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        sizerBtns = wx.BoxSizer(wx.VERTICAL)
        sizerDir  = wx.BoxSizer(wx.VERTICAL)
        sizerBtns.Add(BtnDirSrch, 0, wx.ALL|wx.CENTER, 5)
        sizerBtns.Add(BtnDirRes,  0, wx.ALL|wx.CENTER, 5)

        sizerDir.Add(self.TxtDirSrch,   1, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        sizerDir.Add(self.TxtDirRes,    1, wx.ALL|wx.CENTER|wx.EXPAND, 5)
        
        sizer.Add(sizerBtns, 0,wx.EXPAND)
        sizer.Add(sizerDir,  1,wx.EXPAND)
        
        MainSizer.Add(sizer,      0,wx.EXPAND)
        MainSizer.Add(BtnProcesar,1,wx.EXPAND,5)
        MainSizer.Add(hyper1,0,wx.EXPAND,5)
        panel.SetSizer(MainSizer)
        
    def onProcesar (self, event):
        if self.Dir == '': #prevenir errores causados por descuido del usiario.
            wx.MessageBox('No has elegido el archivo HTML', 'Info', wx.OK | wx.ICON_INFORMATION)
            return
        
        GetBusy = wx.BusyInfo("Procesandio, por favor espere", self) #Mostrar Leyenda (ocupado)
        #-----------------------------------------------------------------------------------------
        file = open(self.Dir, 'r')              # abrir archivo
        theString = file.read()                 # Obtener texto
        soup = BeautifulSoup(theString, 'lxml') # frasear el archivo HTML
        tables = soup.find_all('table')         # Encontrar tablas HTML
        
        #-----------------------------------------------------
        #         Estilos a usar en la hoja de Excel
        #-----------------------------------------------------
        Borders = Border(left  = Side(style='thin'),
                        right  = Side(style='thin'),
                        top    = Side(style='thin'),
                        bottom = Side(style='thin'))
        ALCentered = Alignment(horizontal = 'center',vertical = 'center')
        FuenteHdr = Font(bold = True) 
        #-----------------------------------------------------
        #    Iterar sobre las tablas que fueron encontradas
        #-----------------------------------------------------
        for x in range(len(tables)): #iterar sobre las tablas
            ws = self.wb.create_sheet('Tabla '+str(x+1))
            self.wb.active = x
            HdrCols = 1
            #-----------------------------------------------------
            #   Encontrar encabezados y ponerlos en el documento
            #-----------------------------------------------------
            for Hdr in tables[x].find_all('th'): #iterar sobre encabezados de esta tabla
                ws.cell(row    = 1,
                        column = HdrCols,
                        value  = Hdr.get_text()).border = Borders
                ws.cell(row    = 1, column = HdrCols).alignment = ALCentered
                ws.cell(row    = 1, column = HdrCols).font = FuenteHdr
                HdrCols += 1
                #-----------------------------------------------------
                #          Encontrar Datos de la tabla
                #-----------------------------------------------------
                row_marker = 0
            for row in tables[x].find_all('tr'):
                columns = row.find_all('td')
                column_marker = 1
                row_marker += 1
                for column in columns:
                    img = column.find('img') #encontrar si tiene una imagen
                    if img:                  #Si tiene una imagen, extraer el link
                        txtImg = img.get('src')
                        ws.cell(row    = row_marker,
                                column = column_marker,
                                value  = txtImg).border = Borders
                    else:                    #Si no tiene una imagen, extraer el texto
                        ws.cell(row    = row_marker,
                                column = column_marker,
                                value  = column.get_text()).border = Borders

                    ws.cell(row    = row_marker, column = column_marker).alignment = ALCentered
                    column_marker += 1
                    
        del self.wb["Sheet"]       # hoja se crea por defecto,la borramos porque no la queremos
        self.wb.save(self.DirSve)  # Guardar
        #------------------------------------------------------------------------------------
        del GetBusy # quitar leyenda (ocupado)
        wx.MessageBox('Se ha creado la tabla de Excel', 'Info', wx.OK | wx.ICON_INFORMATION)

    def onSetDirHtml(self, event):
        wildcard = "HTML (*.html)|*.html"
        dlg = wx.FileDialog(self,
                            message     = "Choose a file",
                            defaultDir  = self.Dir, 
                            defaultFile = "",
                            wildcard    = wildcard,
                            style       = wx.FD_OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.Dir = dlg.GetPath()
            self.TxtDirSrch.SetValue(self.Dir)
        dlg.Destroy()
        print(self.Dir)

    def onSetDirSve(self, event):
        wildcard = "Archivos de Excel(*.xlsx)|*.xlsx"
        dlg = wx.FileDialog(
            self, message="Elegir dereccion y numbre", 
            defaultDir=self.DirSve, 
            defaultFile="", wildcard=wildcard, style=wx.FD_SAVE
            )
        if dlg.ShowModal() == wx.ID_OK:
            self.DirSve = dlg.GetPath()
            self.TxtDirRes.SetValue(self.DirSve)
          
        dlg.Destroy()
        print(self.DirSve)
               
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()


puedes usar este ejemplo de una tabla HTML: 


<!DOCTYPE html>
<html>
<head>
<style>
table, th, td {
  border: 1px solid black;
  border-collapse: collapse;
}
th, td {
  padding: 5px;
  text-align: left;
}
</style>
</head>
<body>

<h2>Table Caption</h2>
<p>To add a caption to a table, use the caption tag.</p>

<table style="width:100%">
  <caption>Monthly savings</caption>
  <tr>
    <th>Month</th>
    <th>Savings</th>
  </tr>
  <tr>
    <td>January</td>
    <td>$100</td>
  </tr>
  <tr>
    <td>February</td>
    <td>$50</td>
  </tr>
</table>

</body>
</html>

jueves, 16 de abril de 2020

PYTHON +GOOGLE NOTICIAS MEXICO + GOOGLE TTS

Una voz sintetica en español que lee los encabezados de google noticias Mexico.



Google noticias es un excelente lugar para obtener información de noticias relevantes y recientes, debido a que dichas noticias son de diversas fuentes como: Diario noticias, Excelsior, El universal, entre otras. Este programa obtiene los encabezados de las noticias de google por medio de web scarping y hace el fraseo (parsing) del archivo xml. Cuando obtiene los encabezados, los pone en una lista y los manda al sintetizador de voz de Google para obtener los archivos de sonido de la conversión de texto a voz. En seguida pregunta al usuario si quiere escuchar los audios.

Cuando se realiza web scarping se debe evitar saturar el servidor al que se esté accsesando (enviando a cada instante la requisición de información de este) de lo contrario podrían bloquearnos. Este programa esta diseñado para correrse una sola vez, el programa intentará obtener la información del servidor (Google noticias de México), pero si está saturado o caído, esperará 10 minutos antes de volver a intentarlo. Si falla, esta vez esperara el doble (20 minutos), si falla de nuevo, espera el doble (40 minutos), de esta forma no se satura el servidor y se elimina la posibilidad de que nos bloqueen.

Una vez teniendo los titulares. estos  se dividen en grupos de 4 para mandar menos texto al sintetizador de Google, debido a que solo permite como máximo 100 caracteres por sintetización. 

Este programa fue probado en python 3.8. Para correr este programa necesitas las librerías: bs4, lxml ,gtts , pyautogui y playsound. Se instala fácilmente con pip.
 
SUGERENCIA: Puedes poner este programa en el folder "startup" (en windows). En la PC de mi trabajo hise esto para enterarme de las noticias más relevantes al prender la computadora.

En el programa están comentadas casi todas las lineas de código para que se entienda lo que sucede.
 

Recientemente gtts fallo debido a una actualizacion. se arregla corriendo en una terminal/Linea de comandos los siguiente:

python -m pip install --upgrade gtts
python -m pip install --upgrade gtts-token

#!/usr/bin/env python3
#-*- coding: utf-8 -*-
#------------------------------------------------------
#  Desarrollado por Carlos Alberto rodriguez Martinez
#  http://mecatroncharly.blogspot.com/p/blog-page.html
#
#  Este es un programa que obtiene las noticias
#  desde google noticias Mexico y sintetiza el texto
#  a voz.
#------------------------------------------------------
import gtts
from os             import getcwd, listdir, path, remove
from pyautogui      import alert, confirm
from playsound      import playsound
from time           import sleep
from bs4            import BeautifulSoup as soup
from urllib.request import urlopen
from urllib.error   import HTTPError
from urllib.error   import URLError
#-------------------------------------------------
#                  Variables
#-------------------------------------------------
news_url="https://news.google.com.mx/news/rss" #pagina de noticias
T_waitWeb   = 600     # 10 minutos (600) segundos para pedir acceso al servidor
T_waitTTS   = 15      # 15 segundos en requerir siguiente TTS de google
Intentos    = 0       # variable para registro de intentos de acceso al servidor
WorkinDir   = getcwd()
#-------------------------------------------------
#                  Funciones
#-------------------------------------------------
def deleteFiles(Dir, extension):  #borrar los que hayan quedado 
    files = listdir(Dir)
    for file in files:
        if file.endswith(extension):
            remove(path.join(Dir,file))

def getFiles(Dir, extension):# obtener lista de archivos de audio para reproducir
    arr = []
    files = listdir(Dir)
    for file in files:
        if file.endswith(extension):
            arr.append(path.join(Dir,file))
    return arr

def playFiles():
    NombreArch = getFiles(WorkinDir,".mp3")
    for x in NombreArch:
        playsound(x) 
#-------------------------------------------------
#            PROGRAMA PRINCIPAL
#-------------------------------------------------
deleteFiles(WorkinDir, ".mp3") # Borrar archivos mp3 si hay.
while True:  # ciclo "infinito" (se rompe cuanto termina satisfactoriamente)
    try:
        Client = urlopen(news_url) #intentar abrir url

    except HTTPError as e:  # si ocurre un error por alguna causa desconocida
        Intentos += 1       # registro de intentos incrementa
        alert(text   = e,   # Mostrar mensaje
              title  = 'Error',
              button = 'OK')         
        T_waitWeb = T_waitWeb * 2 #tiepo de espera es el doble
        sleep(T_waitWeb)          #Esperar

    except URLError:   # si ocurre un error por direccion invalida o servidor caido
        Intentos += 1  # registro de intentos incrementa
                       # Mostrar mensaje
        alert(text   = "Servidor caido o dominio incorrecto. Intentos = "+str(Intentos),
              title  = "Error",
              button = 'OK')
        T_waitWeb = T_waitWeb * 2 #tiepo de espera es el doble
        sleep(T_waitWeb)          #Esperar
    
    else: # Si pudo acceder al servidor
        xml_page = Client.read() # Leer y guardar informacion en una variable
        Client.close()           # cerrar cliente (no hace falta que siga aierto)

        soup_page = soup(xml_page,"xml")      # Frasear archivo xml
        news_list = soup_page.findAll("item") # poner titulares en una lista
        #-----------------------------------------------------------
        #  Dividir titulares de Noticias en grupos de 4 para mandar a Google TTS.
        #-----------------------------------------------------------
        N_Noticias  = 4          # 4 encabezados de noticias
        inicio      = 0          # inicio de cada grupo de 4 (inicia en 0 e incrementa de 4 en 4)
        Acumulador  = N_Noticias # fin    de cada grupo de 4 (inicia en 4 e incrementa de 4 en 4)
        NumArch     = 1          # Variable para nombrae archivos con cuenta numerica Ej: 1.mp3, 2.mp3, etc

        print(len(news_list))
        while Acumulador < len(news_list):
            Texto = ''
            for x in range(inicio,Acumulador):
                Texto = Texto + 'Noticia número ' + str(x+1)+'. '+news_list[x].title.text+". "
                #--------------------------------------------------------------------
                #        Obtener otros datos de cada item si se quiere
                #--------------------------------------------------------------------
                #print(news_list[x].link.text)    # obtener link de la noticia
                #print(news_list[x].pubDate.text) # obtener la fecha de la noticia

            #print(Texto)      #Debug
            #print(inicio)     #Debug
            #print(Acumulador) #Debug
            
            inicio     += N_Noticias # incrementar para el inicio del siguiente grupo
            Acumulador += N_Noticias # incrementar para el fin del siguiente grupo

            #----------------------------------------------------
            #   Mandar texto a GTTS y guardar archivo de sonido
            #----------------------------------------------------
            tts = gtts.tts.gTTS(Texto, lang='es-us')
            
            try:
                NombreArch = str(NumArch)+".mp3"
                with open(NombreArch, "wb") as archivo:
                    tts.write_to_fp(archivo)
                    
                NumArch += 1
                sleep(T_waitTTS)
            except:
                pass
        #----------------------------------------------------
        # Preguntar al usuario si quiere escuchar los audios
        #----------------------------------------------------
        respuesta = confirm(text='Se generaron audios de noticias. Quieres escucharlas',
                            title='Escuchar?',
                            buttons=['Si', 'No'])
        if respuesta == 'Si':
            playFiles()
        break # romper ciclo while principal

viernes, 27 de marzo de 2020

MONITOREO DE MULTIPLES HILOS EN PYTHON 3


En este programa se ejemplifica cómo monitorear varios hilos y mostrar el progreso de cada uno en la interfase gráfica. Para saber mas sobre hilos, procesos y demás. puedes leer el post del link.


El programa utiliza unas barras de progreso (gauge) para representar cada hilo en la interfase gráfica. Cada hilo tiene un proceso, el cual consiste en contar de 0 a 100, con un retraso aleatorio de tiempo de entre 5 y 10 mili segundos en cada iteración para simular la variación de tiempos de proceso. Puedes  cambiar el numero de procesos en la variable num_procesos.  Corre este programa una vez y observa como las barras de progreso se llenan casi al mismo tiempo.

semáforo desactivado

En el código mostrado mas abajo hay 2 funciones comentadas. Estas funciones son threadLock.acquire() y threadLock.release(). Estas funciones funcionan (valga la redunancia) como un semáforo que pausa un hilo determinado mientras otro continua. Esto es útil para evitar corromper datos cuando 2 o mas hilos intentan acceder a la misma información para lectura o escritura al mismo tiempo. Des-comenta estas 2 funciones como experimento para observar como las barras de progreso se pausan entre si de modo que ya no se llenan de de forma uniforme.

Semáforo activado

En el código fuente te dejo comentarios comprender lo que sucede. Nesecitas la librería wxpython para correr este programa. Este programa fué probado en Python 3.7.


import wx
from time            import sleep
from random          import randint
from threading       import Thread
from threading       import Lock
from threading       import current_thread
from wx.lib.newevent import NewEvent
 
threadLock = Lock() # descomenta threadLock.acquire() y threadLock.release()
                    # (mas abajo) como experimento para ver que sucede.
MandaGaugeDatos, EVT_ACTUAL_GAUG = NewEvent() # evento que se usara para monitorear hilos y actualizar gauges
num_procesos = 5   # numero de hlos (y por ende, de gauges) pon aqui los que quieras

class TestThread(Thread): # Esto es un hilo.
    def __init__(self,parent,threadID):
        Thread.__init__(self)     # llamar al constructor de la clase
        self.threadID = threadID  # variable para identificar el hilo
        self.parent   = parent    # padre de el hilo (se usa para matar hilo cunando se sierre el programa)

    def run(self): # EL PROCESO
        #--------------------------------------------------
        #   El roceso consiste en contar de 1 a 100.
        #   se generan retardos aleatorios para simular tiempos variantes
        #--------------------------------------------------
        for i in range(1,100): # Cuenta de 1 a 100%
            
            #threadLock.acquire() # DESCOMENTA ESTO A VER QUE PASA.

            slp = randint(5, 10)/1000  # generar tiempo aleatorio entre 5 y 10ms
            sleep(slp)                 # realizar el retraso aleatorio
            #-------------------------------------------------------------
            # Enviar datos resultantes de cada iteracion del hilo a la GUI
            #  wx.postevent(parent, function) #generar evento
            #  MandaGaugeDatos(datos nombrados que quieras regresar)
            #--------------------------------------------------
            wx.PostEvent(self.parent,
                         MandaGaugeDatos(dato   = i,
                                         gauge  = self.threadID,
                                         thread = current_thread()))
            
            #threadLock.release() # DESCOMENTA ESTO A VER QUE PASA.
     
class MainFrame(wx.Frame):
    def __init__(self, parent, title =""):
        super().__init__(parent, title = title , size = (500,250))
        self.panel  = wx.Panel(self)
        
        self.gauges = []  # Arreglo de Gauges
        threads     = []  # arreglo de hilos
        #---------------------------------------------
        #                WIDGETS
        #---------------------------------------------
        #       llenar el arreglo de gauges
        #--------------------------------------------
        for gauge in range(num_procesos):
            self.gauges.append(wx.Gauge(self.panel, size=(300, 20)))
        #---------------------------------------------
        #                 BINDS
        #---------------------------------------------
        # Conectar señal del evento emitida con la funcion correspondiente
        #--------------------------------------------
        self.Bind(EVT_ACTUAL_GAUG, self.on_actualizar)
        #---------------------------------------------
        #                 LAYOUT
        #---------------------------------------------
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add(self.panel,1, wx.CENTER|wx.ALL|wx.EXPAND)
        panel_sizer = wx.BoxSizer(wx.VERTICAL)
        self.panel.SetSizer(panel_sizer)
        for gauge in self.gauges:
            panel_sizer.Add(gauge , 0, wx.CENTER|wx.ALL|wx.EXPAND)
        self.SetSizer(main_sizer)
        #---------------------------------------------
        #  llenar arreglo de hilos e  Iniciarlos todos
        #---------------------------------------------
        for x in range(num_procesos):
            threads.append(TestThread(self, x))
            threads[x].daemon = True
            threads[x].start()
            
    def on_actualizar(self, evt):
        #Actualizar el gauge correspondiente en la GUI
        self.gauges[evt.gauge].SetValue(evt.dato)
 
if __name__ == '__main__':
    app = wx.App(0)
    frame = MainFrame(None)
    app.SetTopWindow(frame)
    frame.Show()
    app.MainLoop()