简体   繁体   中英

Detect reload of PyQt5 script, due to promoted widget in QtDesigner?

Often times, I have tasks that I need to do in a PyQt5 GUI designed in QtDesigner, that are small enough, that I want to keep all code in a single .py file. So, consider this PyQt5 example:

test3.py :

import sys
from PyQt5 import QtCore, QtWidgets, QtGui, uic

mod_name = vars(sys.modules[__name__])['__package__'] # SO:1389044
code_exec_as = 'module named {}'.format(mod_name) if mod_name else 'script'
cmdline = "{} {}".format( sys.executable, " ".join(sys.argv) )
try: # SO:6038898
  reloading
except NameError:
  reloading = False # means the module is being imported
else:
  reloading = True # means the module is being reloaded

print("Starting: code executed as {}; reloading {}; command line '{}'".format(code_exec_as, reloading, cmdline))

class MyCustomButton(QtWidgets.QPushButton):
  def __init__(self, parent=None):
    super().__init__(parent)
    print("MyCustomButton init!")

class MyMainWindow(QtWidgets.QMainWindow):
  def __init__(self):
    super(MyMainWindow, self).__init__()
    uic.loadUi("test3.ui", self)
    self.show()

def main():
  app = QtWidgets.QApplication(sys.argv)
  window = MyMainWindow()
  sys.exit(app.exec_())

if __name__ == "__main__":
  main()

test3.ui :

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>382</width>
    <height>232</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QHBoxLayout" name="horizontalLayout">
    <item>
     <widget class="QPushButton" name="pushButton">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="MyCustomButton" name="pushButton_2">
      <property name="text">
       <string>CustomPushButton</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>382</width>
     <height>21</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <customwidgets>
  <customwidget>
   <class>MyCustomButton</class>
   <extends>QPushButton</extends>
   <header>test3</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

When I run this from the command line, I get:

$ python3 test3.py
Starting: code executed as script; reloading False; command line 'C:/msys64/mingw64/bin/python3.exe test3.py'
Starting: code executed as script; reloading False; command line 'C:/msys64/mingw64/bin/python3.exe test3.py'
MyCustomButton init!

Note that the "Starting: ..." printout has run twice: apparently:

  • the first time is due to the script being called normally
  • the second time is due to pushButton_2 in the .ui being promoted to a MyCustomButton class, which is listed as being defined in test3.py , so that file needs to be reloaded so that the MyCustomButton class definition is read

Can I somehow detect which time the script is being run, from within the script (more specifically, from the part of the code after the imports, but before any class definitions and __main__ calls)?

As you can see, I have already tried something in test3.py , based on:

... but these approaches give me the same output for both "runs" of the script, so I cannot use them to discriminate whether the script is in the first or the second run.

Ok, I think I got it: took a look at How to list imported modules? - then printed the modules and compared: it turns out, the first run of the script, there are less modules in sys.modules - and more specifically, in the second run of the script, there are classes not present in the first run, most importantly PyQt5.uic.Loader is present in second run, and also test3 (the name/class of the Python script itself, as being used in the QtDesigner file) is present. So, one could use either of these two classes to discriminate.

So, I changed script to this:

import sys
from PyQt5 import QtCore, QtWidgets, QtGui, uic

mod_name = vars(sys.modules[__name__])['__package__'] # SO:1389044
code_exec_as = 'module named {}'.format(mod_name) if mod_name else 'script'
cmdline = "{} {}".format( sys.executable, " ".join(sys.argv) )
try: # SO:6038898
  reloading
except NameError:
  reloading = False # means the module is being imported
else:
  reloading = True # means the module is being reloaded

first_time_run = not( 'PyQt5.uic.Loader' in sys.modules.keys() )
print("Starting: code executed as {}; reloading {}; command line '{}'; num modules {}; first_time_run {}".format(code_exec_as, reloading, cmdline, len(sys.modules.keys()), first_time_run))
#print("..in sys.modules.keys():\n  {}".format( "\n  ".join( sorted(sys.modules.keys()) ) ))

class MyCustomButton(QtWidgets.QPushButton):
  def __init__(self, parent=None):
    super().__init__(parent)
    print("MyCustomButton init!")

class MyMainWindow(QtWidgets.QMainWindow):
  def __init__(self):
    super(MyMainWindow, self).__init__()
    uic.loadUi("test3.ui", self)
    self.show()

def main():
  app = QtWidgets.QApplication(sys.argv)
  window = MyMainWindow()
  sys.exit(app.exec_())

if __name__ == "__main__":
  main()

... and now the printout is:

$ python3 test3.py
Starting: code executed as script; reloading False; command line 'C:/msys64/mingw64/bin/python3.exe test3.py'; num modules 113; first_time_run True
Starting: code executed as script; reloading False; command line 'C:/msys64/mingw64/bin/python3.exe test3.py'; num modules 117; first_time_run False
MyCustomButton init!

... which confirms to me, that the first time run is correctly detected.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM