PySimpleGUI机器可用性GUI报错:TypeError: 'str'对象不可调用
问题
使用PySimpleGUI开发机器可用性查看GUI时,集成downtime函数触发错误。完整代码如下:
import PySimpleGUI as sg import pyodbc import databaseconfig as conf from datetime import datetime, timedelta, time import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg sg.theme('GrayGrayGray') # Connect to the SQL server cnxn = pyodbc.connect(DRIVER= conf.driver, SERVER= conf.server, DATABASE= conf.database, UID= conf.username, PWD= conf.password) cursor = cnxn.cursor() # Selects the 'device' of database table cursor.execute("SELECT device FROM kt_machines") device = cursor.fetchall() list_devices = [(row)[0] for row in device] # Selects the 'Omschrijving' of database table cursor.execute("SELECT Omschrijving FROM kt_machines") omschrijving = cursor.fetchall() omsch = [(row)[0] for row in omschrijving] # This will remove everything that comes after '.'. now it prints like: 2023-02-10 07:20:10.747000. After this function it prints like: 2023-02-10 07:20:10 def removeAfterN(yourStr, nth, occurenceOf): return occurenceOf.join(yourStr.split(occurenceOf)[:nth])#print(removeAfterN(str(data[0]),1,".")) # This function removes certain prefix def my_removeprefix(omsch, prefix): if omsch.startswith(prefix): return omsch[len(prefix):] else: return omsch # Removes prefix of your choosing def removePrefix(num,prefixToDel): return my_removeprefix(omsch[num], prefixToDel) # Gets timestamp from database def timeStamp(): cursor.execute(f"SELECT TS FROM kt_2010 WHERE id=(SELECT max(id) FROM kt_2010)") data = cursor.fetchone() if data[0] is not None: window[f'TS1'].update(data[0]) return str(data[0]) else: return 0 def timeStampDiff(): # Get current time stamp. This shows the date and the time cursor.execute(f"SELECT TS FROM kt_2010 WHERE id=(SELECT max(id) FROM kt_2010)") data = cursor.fetchone() timestampp = datetime.now() difference = (timestampp - data[0]) differenceMin = (difference.seconds / 60) differenceMinn = removeAfterN(str(differenceMin),1,".") return differenceMinn or 0 def createCircle(): graph = window['graph0'] circle = graph.draw_circle((88, 15), 7, fill_color='red', line_color='black') if int(timeStampDiff()) > 5:# If difference between current timestamp and latest timestamp from table is greater that 5 the circle will turn red. graph.TKCanvas.itemconfig(circle, fill='red') elif int(timeStampDiff()) < 5:# If difference between current timestamp and latest timestamp from table is less that 5 the circle will turn green. graph.TKCanvas.itemconfig(circle, fill='green') return circle or 0 def downtime(): # Define when the workday starts, also define when the workday ends start_time = datetime.combine(datetime(2023,2,21), time(7, 30)) end_time = datetime.combine(datetime(2023,2,21), time(16, 15)) # Calculate how much minutes there are in the work day time_range_minutes = int((end_time - start_time).total_seconds() / 60) time_range_minutes = time_range_minutes - 45 # Set the time threshold for downtime to 5 minutes (300 seconds) time_threshold = 300 # Execute a SQL query to retrieve the timestamps of all the 1s from the sensor query = f"SELECT TS FROM kt_2010 WHERE message = 1 AND TS BETWEEN ? AND ? ORDER BY TS" cursor.execute(query, [start_time, end_time]) # Initialize downtime counter downtime_counter = 0 # Initialize previous timestamp variable prev_timestamp = None # Loop through the query results for row in cursor: # Extract the timestamp from the row timestampp = row[0] # If this is the first timestamp or if the previous timestamp was more than the time threshold if prev_timestamp is None or (timestampp - prev_timestamp).total_seconds() >= time_threshold: # Add the threshold to the downtime_counter downtime_counter += time_threshold # Otherwise: add the difference between the current timestamp and the previous one to the downtime counter else: downtime_counter += (timestampp - prev_timestamp).total_seconds() # This updates the prev_timestamp prev_timestamp = timestampp # If the last '1' was seen before they day ends (16:15) add the remaining time until the day ends(16:15) if prev_timestamp is not None and (end_time - prev_timestamp).total_seconds() >= time_threshold: downtime_counter += (end_time - prev_timestamp).total_seconds() # Calculate the total downtime in minutes downtime_minutes = int(downtime_counter / 60 - 45) #Beschikbaarheid = str(int(downtime_counter / 60 - 45) / time_range_minutes * 100) Beschikbaarheid = str((downtime_counter / 60 - 45) / time_range_minutes * 100) Beschikbaarheid = Beschikbaarheid.split('.') Beschikbaarheid = Beschikbaarheid[0] #Beschikbaarheid = Beschikbaarheid_partss #print(f"Total downtime for kt_2010: {downtime_minutes} minutes out of {time_range_minutes} minutes") print(f'Beschikbaarheid: {Beschikbaarheid}') window['besch1'].update(Beschikbaarheid) return Beschikbaarheid Frame_Row1 = [[sg.Frame("",[[sg.Text(list_devices[0], font=('Helvetica', 12))],[sg.Text(removePrefix(0,'2010 - '), font=('Helvetica', 12))],[sg.Text('', font=('Helvetica', 12), key='TS1')],[sg.Text('', font=('Helvetica', 12), key='kt_2010'), sg.Text('', font=('Helvetica', 12), key='besch1')], [sg.Graph(canvas_size=(350,100), graph_bottom_left=(0,0),graph_top_right=(100,50),enable_events=True,key='graph0')]],size=(350,250)),]] layout = [Frame_Row1] window= sg.Window('Status kantbanken', layout, size= (1900,800), element_justification='c', finalize=True, grab_anywhere=True) stamptime = timeStamp() diffStamp = timeStampDiff() Circlecreation = createCircle() timeDown = downtime() #print(downtime(datetime(2023,2,21).date(),'kt_2010',1)) import threading t = threading.Timer(2,timeDown) t.start() #timer_thread = threading.Thread(target=timeDown) #timer_thread.start() while True: event, values = window.read(timeout=500) # returns every 5sec if event == 'close' or event == sg.WIN_CLOSED: break # If timout=100 gets to 100 it will do the following actions: it wll refresh the latest timestamp for every table. It will also create a red or green circle (see function 'createCircle()') if event == sg.TIMEOUT_EVENT: # Updates the timestamp for 9 bending machines (the other ones dont have a sensor) timeStamp() # Creates a circle for 9 bending machines (the other ones dont have a sensor) createCircle() pass # Close the cursor, connection and window cursor.close() cnxn.close() window.close()
收到的错误信息:
Exception in thread Thread-1: Traceback (most recent call last): File "C:\Users\stagiair\AppData\Local\Programs\Python\Python311\Lib\threading.py", line 1038, in _bootstrap_inner self.run() File "C:\Users\stagiair\AppData\Local\Programs\Python\Python311\Lib\threading.py", line 1394, in run self.function(*self.args, **self.kwargs) TypeError: 'str' object is not callable
解决方案
错误根源
你执行了timeDown = downtime(),这会直接调用downtime函数并将其返回值(一个字符串)赋值给timeDown。而threading.Timer的target参数需要传入可调用的函数对象,不是字符串,因此触发类型错误。
修复步骤
修正定时器参数
先单独执行downtime()获取初始数据,然后将定时器的target设为函数对象downtime(不要加括号):# 先执行一次获取初始可用性数据 downtime() # 启动定时器,传入函数对象而非执行结果 import threading t = threading.Timer(2, downtime) t.start()解决线程安全问题
PySimpleGUI的GUI元素更新必须在主线程中进行,子线程直接调用window['besch1'].update()可能引发未知问题。修改downtime函数,通过事件通知主线程更新GUI:- 修改
downtime函数的最后部分:# 替换原有的window['besch1'].update(Beschikbaarheid) window.write_event_value('-UPDATE_AVAILABILITY-', Beschikbaarheid) return Beschikbaarheid - 在主循环中处理该事件:
while True: event, values = window.read(timeout=500) if event == 'close' or event == sg.WIN_CLOSED: break if event == sg.TIMEOUT_EVENT: timeStamp() createCircle() # 处理子线程发来的更新事件 elif event == '-UPDATE_AVAILABILITY-': window['besch1'].update(values[event])
- 修改
内容的提问来源于stack exchange,提问作者Kay
相关产品推荐
相关产品推荐

