Cómo ejecutar una función periódicamente en python

Tengo un metrónomo simple en ejecución y, por alguna razón, cuando está en un bpm más bajo está bien, pero a un bpms más alto es inconsistente y no es estable. No sé que está pasando. Quiero intentar usar algo para ejecutarlo periódicamente. ¿Hay una manera de hacerlo?

Aquí está mi código:

class thalam(): def __init__(self,root,e): self.lag=0.2 self.root=root self.count=0 self.thread=threading.Thread(target=self.play) self.thread.daemon=True self.tempo=60.0/120 self.e=e self.pause=False self.tick=open("tick.wav","rb").read() self.count=0 self.next_call = time.time() def play(self): if self.pause: return winsound.PlaySound(self.tick,winsound.SND_MEMORY) self.count+=1 if self.count==990: self.thread=threading.Thread(target=self.play) self.thread.daemon=True self.thread.start() return self.next_call+=self.tempo new=threading.Timer(self.next_call-time.time(),self.play) new.daemon=True new.start() def stop(self): self.pause=True winsound.PlaySound(None,winsound.SND_ASYNC) def start(self): self.pause=False def settempo(self,a): self.tempo=a class Metronome(Frame): def __init__(self,root): Frame.__init__(self,root) self.first=True self.root=root self.e=Entry(self) self.e.grid(row=0,column=1) self.e.insert(0,"120") self.play=Button(self,text="Play",command=self.tick) self.play.grid(row=1,column=1) self.l=Button(self,text="",command=lambda:self.inc("r")) self.r.grid(row=0,column=2) def tick(self): self.beat=thalam(root,self.e) self.beat.thread.start() self.play.configure(text="Stop",command=self.notick) def notick(self): self.play.configure(text="Start",command=self.tick) self.beat.stop() def inc(self,a): if a=="l": try: new=str(int(self.e.get())-5) self.e.delete(0, END) self.e.insert(0,new) self.beat.settempo(60.0/(int(self.e.get()))) except: print "Invalid BPM" return elif a=="r": try: new=str(int(self.e.get())+5) self.e.delete(0, END) self.e.insert(0,new) self.beat.settempo((60.0/(int(self.e.get())))) except: print "Invalid BPM" return 

Hacer algo que requiera precisión de tiempo es muy difícil debido a la necesidad de que el procesador se comparta con otros progtwigs. Desafortunadamente, para los progtwigs críticos, el sistema operativo es libre de cambiar a otro proceso cuando lo desee. Esto podría significar que es posible que no vuelva a su progtwig hasta después de un retraso notable. El uso de time.sleep después del tiempo de importación es una forma más consistente de tratar de equilibrar el tiempo entre los pitidos porque el procesador tiene menos “razones” para desconectarse. Aunque el modo de espera en Windows tiene una granularidad predeterminada de 15.6ms, pero asumo que no necesitará jugar un ritmo en exceso de 64Hz. También parece que estás usando multihilo para tratar de resolver tu problema, sin embargo, la implementación de los hilos en Python a veces obliga a los subprocesos a ejecutarse de forma secuencial. Esto hace que las cosas sean aún peores para cambiar de proceso.

Creo que la mejor solución sería generar datos de sonido que contengan el pitido del metrónomo a la frecuencia deseada. Luego, puede reproducir los datos de sonido de una manera que el sistema operativo entienda bien. Como el sistema sabe cómo manejar el sonido de manera confiable, su metrónomo funcionará.

Lamento decepcionar, pero las aplicaciones críticas son MUY difíciles a menos que quiera ensuciarse las manos con el sistema con el que está trabajando.

La reproducción de sonido para emular un metrónomo ordinario no requiere capacidades de “tiempo real”.

Parece que usas el framework Tkinter para crear la GUI. root.after() permite llamar a una función con un retraso . Podrías usarlo para implementar ticks:

 def tick(interval, function, *args): root.after(interval - timer() % interval, tick, interval, function, *args) function(*args) # assume it doesn't block 

tick() ejecuta la function con args dados cada interval milisegundos. La duración de las root.after() individuales se ve afectada por la root.after() de root.after() pero a la larga, la estabilidad depende solo de la función del timer() .

Aquí hay un script que imprime algunas estadísticas, 240 latidos por minuto:

 #!/usr/bin/env python from __future__ import division, print_function import sys from timeit import default_timer try: from Tkinter import Tk except ImportError: # Python 3 from tkinter import Tk def timer(): return int(default_timer() * 1000 + .5) def tick(interval, function, *args): root.after(interval - timer() % interval, tick, interval, function, *args) function(*args) # assume it doesn't block def bpm(milliseconds): """Beats per minute.""" return 60000 / milliseconds def print_tempo(last=[timer()], total=[0], count=[0]): now = timer() elapsed = now - last[0] total[0] += elapsed count[0] += 1 average = total[0] / count[0] print("{:.1f} BPM, average: {:.0f} BPM, now {}" .format(bpm(elapsed), bpm(average), now), end='\r', file=sys.stderr) last[0] = now interval = 250 # milliseconds root = Tk() root.withdraw() # don't show GUI root.after(interval - timer() % interval, tick, interval, print_tempo) root.mainloop() 

El tempo oscila solo por un tiempo: 240 ± 1 en mi máquina.