You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python项目中共享并复用类的单一实例?

问题:跨文件复用Tkinter按钮实例时状态不同步

场景复现

在button.py中定义MyButtons类并创建全局实例my_button,期望在main.py中通过Socket接收数据后调用该实例的update_btns方法更新界面按钮状态,但实际运行时(先启动monitor.py,再启动main.py),两个文件导入的my_button是独立副本,无法同步界面状态。

相关代码

button.py

import math
from frontend.utilities.screen_info import screen_info
from frontend.env import BTN_SIZE, SUCCESS_COLOR, FAIL_COLOR, WARNING_COLOR, BTN_X_PADDING, \
    BTN_Y_PADDING
from frontend.modules.master import master, Button
from backend.ip_list import ip_list

screen_width, screen_height = screen_info()


class MyButtons:
    def __init__(self):
        self.current_btn = None
        self.btns = []
        self.items = ip_list
        self.attach_all_buttons()

    def create_btn(self, item, k):
        btn = Button(
            master,
            text=item,
            bg=FAIL_COLOR,
            command=lambda: self.click_btn(k)
        )
        self.btns.append(btn)
        self.current_btn = btn

    def click_btn(self, id):
        if self.btns[id].cget('bg') == WARNING_COLOR:
            self.btns[id].config(bg=SUCCESS_COLOR)

    def update_btns(self, items):
        for i, item in zip(range(len(self.btns)), items):
            bg_color = SUCCESS_COLOR if item['is_alive'] == True else FAIL_COLOR
            self.btns[i].config(bg=bg_color)

    def configure_btn(self, col):
        master.grid_columnconfigure(col, minsize=BTN_SIZE)
        # btn.bind('<Enter>', lambda: btn.config(text='Mouse in'))
        # btn.bind('<Leave>', lambda: btn.config(text='Mouse out'))

    def attach_btn(self, row, col):
        self.current_btn.grid(
            row=row,
            column=col,
            padx=BTN_X_PADDING,
            pady=BTN_Y_PADDING,
            sticky='wens'
        )

    def attach_all_buttons(self):
        items_count = len(self.items)
        cols = math.floor(screen_width / BTN_SIZE)
        rows = math.ceil(items_count / cols)
        k = 0
        for row in range(rows):
            partial_items = self.items[row * cols:(row + 1) * cols]
            for col, item in zip(range(cols), partial_items):
                self.create_btn(item, k)
                self.attach_btn(row, col)
                self.configure_btn(col)
                k = k + 1


my_button = MyButtons()

monitor.py

from frontend.modules.master import master


def prepare_monitor():
    master.mainloop()


prepare_monitor()

main.py

from frontend.modules.button import my_button
from frontend.client_socket import client_socket

import pickle


def start():
    print('Monitor Network')
    while True:
        data = client_socket.client_socket.recv(4096)
        client_socket.message = pickle.loads(data)
        if client_socket.message:
            my_button.update_btns(client_socket.message)
            print('Server Socket data: ', client_socket.message)
            client_socket.message = None


start()

原因分析

你启动了两个独立的Python进程:monitor.py和main.py。每个进程都会独立导入button.py,并各自创建my_button实例,这两个实例完全独立,内存空间不共享,所以main.py中调用update_btns只会修改自身进程内的实例,不会影响monitor.py进程中的界面按钮。

解决方案

方案1:将Socket逻辑整合到Tkinter主进程中

Tkinter的UI更新必须在主事件循环所在的进程中执行,把Socket接收逻辑放到monitor.py中,用Tkinter的after方法实现非阻塞监听:

修改monitor.py:

from frontend.modules.master import master
from frontend.modules.button import my_button
from frontend.client_socket import client_socket
import pickle

def check_socket_data():
    try:
        # 设置非阻塞接收,避免阻塞主事件循环
        client_socket.client_socket.setblocking(False)
        data = client_socket.client_socket.recv(4096)
        if data:
            message = pickle.loads(data)
            if message:
                my_button.update_btns(message)
                print('Server Socket data: ', message)
    except BlockingIOError:
        # 没有数据时抛出该异常,忽略即可
        pass
    # 每隔100ms检查一次Socket
    master.after(100, check_socket_data)

def prepare_monitor():
    # 启动Socket检查任务
    check_socket_data()
    master.mainloop()

prepare_monitor()

之后只需要启动monitor.py一个进程即可,无需单独启动main.py。

方案2:使用进程间通信(IPC)共享状态

如果必须分开两个进程,可以用multiprocessing的Queue传递数据:

  1. 修改button.py,让MyButtons支持监听IPC队列:
# 在button.py顶部添加
from multiprocessing import Queue

class MyButtons:
    def __init__(self, update_queue=None):
        self.current_btn = None
        self.btns = []
        self.items = ip_list
        self.update_queue = update_queue
        self.attach_all_buttons()
        # 如果传入队列,启动监听
        if self.update_queue:
            self.check_queue()

    # ... 其他方法保持不变 ...

    def check_queue(self):
        try:
            if not self.update_queue.empty():
                items = self.update_queue.get()
                self.update_btns(items)
        except Exception as e:
            print(f"Queue error: {e}")
        master.after(100, self.check_queue)
  1. 修改monitor.py,创建队列并启动Socket子进程:
from frontend.modules.master import master
from frontend.modules.button import MyButtons
from multiprocessing import Queue, Process
import main

def prepare_monitor(update_queue):
    my_button = MyButtons(update_queue)
    master.mainloop()

if __name__ == "__main__":
    update_queue = Queue()
    # 启动main.py的Socket进程
    socket_process = Process(target=main.start, args=(update_queue,))
    socket_process.start()
    # 启动UI进程
    prepare_monitor(update_queue)
    socket_process.join()
  1. 修改main.py,接收队列并发送数据:
from frontend.client_socket import client_socket
import pickle

def start(update_queue):
    print('Monitor Network')
    while True:
        data = client_socket.client_socket.recv(4096)
        message = pickle.loads(data)
        if message:
            update_queue.put(message)
            print('Server Socket data: ', message)

关键注意事项

  • Tkinter的UI操作必须在主事件循环所在的进程中执行,不能在其他进程直接修改UI元素,否则会导致崩溃或无响应。
  • 多进程场景下,避免直接共享Tkinter对象,必须通过IPC传递数据,由主进程负责更新UI。

内容的提问来源于stack exchange,提问作者Makhsudjon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 02:56:25