如何使用Python持续检查JSON文件是否有更新?

3
我正在使用Tkinter和Python制作一款时钟。
我的目标是让时钟运行并自动检查JSON文件(包含时钟的视觉设置)是否已更新。如果JSON文件已更新,则时钟将实时更新。
但事实并非如此。我必须更新JSON文件,关闭时钟程序,然后重新打开时钟程序。只有在这种情况下,更改后的JSON设置才会生效。 clock.py
from tkinter import *
from datetime import datetime
from settings import *

# Updates the program and clock settings
def tick():
    time_string = datetime.now().strftime(time_format)
    date_string = datetime.now().strftime(date_format)

    root.config(bg=background_color)

    container.configure(bg=background_color)

    current_time.configure(text=time_string,
                           font=(time_font, time_size, time_weight, time_slant),
                           fg=time_color,
                           bg=background_color)

    current_date.configure(text=date_string,
                           font=(date_font, date_size, date_weight, date_slant),
                           fg=date_color,
                           bg=background_color)

    current_time.after(1, tick)


# TKInterface
root = Tk()
root.title(window_title)

# Binds 'Esc' key to exit program
root.bind('<Escape>', exit)

# Runs program in full-screen
if full_screen:
    root.attributes('-fullscreen', True)
    root.config(cursor='none')

# Creates container to hold time and date
container = Frame(root)
current_time = Label(container)
current_date = Label(container)

container.pack(expand=True)
current_time.pack()
current_date.pack()

tick()
root.mainloop()

settings.py

import os
import json

with open('settings.json') as json_settings:
    settings = json.load(json_settings)

    # Window
    full_screen = settings['window']['full_screen']
    window_title = settings['window']['window_title']

    # Background
    background_color = settings['background']['color']

    # Time
    time_font = settings['time']['font']
    time_size = settings['time']['size']
    time_weight = settings['time']['weight'] 
    time_slant = settings['time']['slant']
    time_color = settings['time']['color']    
    time_format = settings['time']['format']

    # Date
    date_font = settings['date']['font']
    date_size = settings['date']['size']
    date_weight = settings['date']['weight']
    date_slant = settings['date']['slant']
    date_color = settings['date']['color']
    date_format = settings['date']['format']

settings.json

{
    "window": {
      "full_screen": false,
      "window_title" : "chronoberry"
    },
    "background": {
      "color": "black"
    },
    "time": {
      "font": "arial",
      "size": 70,
      "weight": "bold",
      "slant": "roman",
      "color": "white",
      "format": "%-I:%M:%S %p"
    },
    "date": {
      "font": "arial",
      "size": 20,
      "weight": "normal",
      "slant": "roman",
      "color": "white",
      "format": "%A, %B %-d %Y"
    }
  }

期望效果:

如果我在JSON文件中更改背景颜色并保存,我的时钟应该能够在运行时更新其颜色。

更改背景颜色之前

更改背景颜色之后

我尝试过的方法:

  • 使用importlib.reload()重新导入settings.py模块,但是settings.py模块不是有效的参数。
  • 打开settings.json,从中读取数据,然后关闭它,再次打开。但是,一旦文件关闭,就无法再次打开它。

文件一旦关闭,就无法再次打开。不是这样的。你为什么这么说?当你尝试打开时是否出现错误? - John Gordon
1
你是否意识到 after(1, ...) 在1毫秒后运行,而不是一秒钟?由于你的时钟只有一秒钟的分辨率,你让CPU做了比必要更多的工作。 - Bryan Oakley
3个回答

2

您的settings模块基本上是一个常量模块,这使得它很难被重复使用。虽然您可以做一些不太正规的事情来强制执行reload,比如:

def tick():
    import settings, importlib
    importlib.reload(settings)
    from settings import *
    # ... rest of tick code ...

这样做既低效,而且对导入机制的滥用非常糟糕(现在不仅要读取一个文件,还要同时读取模块和其依赖的JSON文件)。

相反,我建议使用一个函数来读取数据并缓存它,使settings更具可重用性,而不是一堆全局变量:

import os
import json

class Settings:
    SETTINGS_FILE = 'settings.json'

    def __init__(self):
        self._load_settings()

    def update_settings(self):
        if self._last_update != os.stat(self.SETTINGS_FILE).st_mtime:
            self._load_settings()
            return True
        return False

    def _load_settings(self):
        with open(self.SETTINGS_FILE) as json_settings:
            settings = json.load(json_settings)
            self._last_update = os.fstat(json_settings.fileno()).st_mtime

            # Window
            self.full_screen = settings['window']['full_screen']
            self.window_title = settings['window']['window_title']

            # Background
            self.background_color = settings['background']['color']

            # Time
            self.time_font = settings['time']['font']
            self.time_size = settings['time']['size']
            self.time_weight = settings['time']['weight'] 
            self.time_slant = settings['time']['slant']
            self.time_color = settings['time']['color']    
            self.time_format = settings['time']['format']

            # Date
            self.date_font = settings['date']['font']
            self.date_size = settings['date']['size']
            self.date_weight = settings['date']['weight']
            self.date_slant = settings['date']['slant']
            self.date_color = settings['date']['color']
            self.date_format = settings['date']['format']

现在您的clock模块可以导入settings,预先构建一个Settings对象,并在每个tick调用update_settings()。如果update_settings返回True,它还应该重新应用配置。代码需要限定各种名称,因此不仅仅是说date_color,而是:

mysettings = Settings()

在顶层,可以通过以下方式引用date_color
mysettings.date_color

但这只是为了改进代码而付出的小代价。


记录一下,我同意 inotify 接口会是一个改进,并且应该被考虑,但是在我看来,它们对于你现在的技能水平来说太高级了。 - ShadowRanger
这是一个优雅的解决方案!我感谢你花时间向一名初学者程序员解释这个问题。谢谢! - leecharles_
据我基本的理解,修改后的settings.py文件所做的是:在创建Settings()对象时,会调用_load_settings()方法。在这里,JSON文件的最后修改时间被存储在_last_update中。 - leecharles_
@leecharles_:是的。每次调用 update_settings 时,它都会检查自上次加载以来 JSON 文件是否已被修改,如果是,则重新加载并返回 True(这样您就知道要使用新设置重新配置),否则返回 False(这样您就知道只需更新时间,而不必更改 Tk 配置)。 - ShadowRanger

1
这段代码有多严重?你可以定期使用 os.stat(path_to_file).st_mtime 检查文件的最后修改时间,并在它晚于上次检查的时间时刷新你的界面。快速而简单。

0

你有几个选项。第一种是benas建议的选项。每隔一秒钟左右检查json文件的最后修改时间,以查看是否已更改。

另一个选项是使用像PyiNotify这样的软件包。此软件包可以识别发生在文件和文件夹上的事件。其中一种事件是IN_MODIFY事件,当文件被修改时会触发该事件。可以在这里找到事件列表。

首先,您需要使用pip安装软件包:

pip install pyinotify

以下是示例代码(几乎全部来自他们的文档)
import pyinotify

# The watch manager stores the watches and provides operations on watches
wm = pyinotify.WatchManager()
wm.add_watch('/path/to/file', mask, rec=True)

mask = pyinotify.IN_MODIFY  # watched events

class EventHandler(pyinotify.ProcessEvent):
    def process_IN_MODIFY(self, event):
        print "Modifying:", event.pathname

我的理解是 pyinotify 的支持不太好(多年没有更新)。大多数人似乎推荐 inotify - ShadowRanger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接