1
votes

Python - exécutant un programme toutes les 10 secondes, datetime.now () change de comportement

Je testais un programme pour faire quelque chose toutes les N secondes, mais je suis tombé sur un problème étrange.

Si j'utilise quelque chose de simple comme celui-ci:

import time
from datetime import datetime


def main():
    start_t = time.time()
    path_screenshots = "screenshots"

    while(True):
        path_screenshots_today = f"{path_screenshots}/{datetime.now().strftime('%Y_%m_%d')}/"
        if (time.time()-start_t)%10 == 0:
            print(f"Checking folder {path_screenshots_today}...")

if __name__ == "__main__":
    main()

le le programme fonctionne comme prévu, c'est-à-dire qu'il imprime "Test" toutes les 10 secondes.

Cependant, j'ai fait une petite modification, car je dois vérifier à chaque itération la date actuelle ... si je change le programme en ceci:

import time

def main():
    start_t = time.time()

    while(True):
        if (time.time()-start_t)%10 == 0:
            print("Test")

if __name__ == "__main__":
    main()

Je m'attendrais à ce que le programme imprime à nouveau "Vérification du dossier {path_screenshots_today}" toutes les 10 secondes, mais à la place, il continue de fonctionner, sans rien imprimer. Je comprends que le résultat de l'opération (time.time () - start_t)% 10 n'est jamais précisément égal à 0, ce qui pourrait créer le problème ... mais alors, pourquoi cela fonctionne-t-il même dans le premier cas? p>


3 commentaires

Mis à part les détails sur la question de savoir si elle est jamais exactement égale, vous ne voulez pas utiliser 100% de CPU en attendant que cela se produise. Au lieu de cela, calculez simplement la durée du prochain multiple de 10 secondes et dormez pendant cette durée.


La résolution temporelle réelle de time.time () semble être de 2 ^ -20 secondes, soit environ une microseconde. Ceci est basé sur la plus petite valeur non nulle de abs (time.time () - time.time ()) que l'on peut observer (la plupart du temps, cette expression vaut 0, mais quelques pourcentage du temps, il est différent de zéro). Étant donné que time.time () est de l'ordre 2 ^ 31 et que les flottants ont une mantisse de 53 bits (voir sys.float_info.mant_dig ) cela semble globalement cohérent avec la limitation de résolution étant le fait qu'il est retourné dans un float. Je ne connais pas la résolution temporelle réelle de l'horloge matérielle.


Commentaire précédent, la résolution temporelle réelle des flotteurs de cette ampleur est de 2 ^ -22 secondes, donc peut-être que certains calculs internes perdent quelques bits de précision - ou peut-être que nous examinons en fait la résolution de l'horloge matérielle elle-même. Pas certain.


3 Réponses :


2
votes

Je soupçonne que cela fonctionne dans le premier cas parce que la boucle tourne suffisamment vite pour s'aligner. Le décalage créé par la création de path_screenshots_today (en particulier l'appel de datetime.now () ) fait qu'il ne s'aligne pas aussi souvent. Pour vraiment faire ce que vous voulez, essayez:

import time
from datetime import datetime


def main():
    last = time.time()
    path_screenshots = "screenshots"

    while True:
        path_screenshots_today = f"{path_screenshots}/{datetime.now().strftime('%Y_%m_%d')}/"
        if time.time() - last >= 10:
            last = time.time()
            print(f"Checking folder {path_screenshots_today}...")

if __name__ == "__main__":
    main()


0 commentaires

1
votes

Le premier cas fonctionne parce que l'heure est vérifiée assez fréquemment, ce qui ne se produit pas dans le second cas à cause du retard introduit par le formatage de la chaîne. Un moyen plus robuste est le suivant:

while True:
    path_screenshots_today = f"{path_screenshots}/{datetime.now().strftime('%Y_%m_%d')}/"
    print(f"Checking folder {path_screenshots_today}...")
    time.sleep(10)

Et un moyen encore meilleur serait:

start_t = time.time()
while True:
    path_screenshots_today = f"{path_screenshots}/{datetime.now().strftime('%Y_%m_%d')}/"
    tt = time.time()
    if tt - start_t >= 10:
        print(f"Checking folder {path_screenshots_today}...")
        start_t = tt  # set last check time to "now"

Cela évite "l'attente occupée", c'est-à-dire garder le CPU en marche comme un fou.


0 commentaires

1
votes

C'est une coïncidence de la fréquence à laquelle le contrôle a lieu. Si vous effectuez une boucle et imprimez votre valeur, vous remarquerez qu'il s'agit d'une virgule flottante:

time.sleep(10)   # Sleep for 10 seconds

Vous verrez une sortie comme celle-ci:

Current value is,  0.45271849632263184
Current value is,  0.45272231101989746

Étant donné que vous faites si peu de choses dans votre boucle, il y a de bonnes chances que vous fassiez cette évaluation par hasard lorsque la valeur actuelle est exactement 0.0 . Mais lorsque vous ajoutez un calcul supplémentaire, même juste le formatage de la chaîne dans datetime, chaque itération de votre boucle prendra un peu plus de temps et vous pourriez sauter avec plaisir 0.0 .

Donc à proprement parler, vous devez convertir votre valeur en un entier avant de la comparer à 0. Par exemple, int ((time.time () - start_t)% 10) == 0 . Cela sera vrai pendant une seconde entière, jusqu'à ce que la valeur du module soit à nouveau différente de zéro, une seconde après qu'elle soit vraie.

Une meilleure solution, cependant, est probablement d'utiliser simplement l'heure . fonction sleep () . Vous pouvez appeler time.sleep pour qu'il dorme pendant quelques secondes:

    while(True):
        print('Current value is, ', (time.time()-start_t)%10)


0 commentaires