Search code examples
pythonscopepyqtexecself

generate functions to place widgets in pyqt using exec did not recognize self


refers this question, to-convert-string-to-variable-name-in-python

I would like to collect user input, but the input field differs through 'method' change, so i would like to generate something based on:

search_method = { 'id' : ['id'],
                  'detail' : ['catagory', 'price', 'enroll date'],
                  'drawer' : ['name', 'sex', 'state']
                  'internal' : ['transaction date', 'msg id']
                }

serves as dynamic input field

the expected result is that the key generates as radio button, and will generate ['label', 'line edit'] pairs to certain radio selection

here is the test of exec inside pyqt (clearLayout borrowed from user3369214

steps:

  1. create widgets
  2. create function that add them to layouts
  3. connect signal to function that change the layout

the advantage compared to add them one by one is that this is flexible to extend and convenient to collect data back,(most importantly, short), but program happens to say

NameError: global name 'self' is not defined
or SyntaxError: can't assign to function call

there might be some scope or internal problem, hope someone could help or some high-order function thing might help?

from PyQt4 import QtCore, QtGui
import sys

class TestDialog(QtGui.QDialog):
    def __init__(self, parent = None):
        super(TestDialog, self).__init__()
        self.initUI()

    def clearLayout(self, layout)
        if layout != None:
            while layout.count():
                child = layout.takeAt(0)
                if child.widget() is not None:
                    child.widget().deleteLater()
                elif child.layout() is not None:
                    self.clearLayout(child.layout())

        layout.setParent(None)

    def initUI(self):
        search_method = { 'id' : ['id'],
                          'detail' : ['catagory', 'price', 'enroll date'],
                          'drawer' : ['name', 'sex', 'state']
                          'internal' : ['transaction date', 'msg id']
                        }

        self.layTop = QtGui.QHBoxLayout()
        self.lblBy = QtGui.QLabel("By")
        self.layTop.addWidget(self.lblBy)

        for option in search_method.keys():
            exec('self.rad_' + option + ' = QtGui.QRadioButton("' + option + '")')
            exec('self.layTop.addWidget(self.rad_' + option + ')')

        self.vlay = QtGui.QHBoxLayout()
        self.vlay.addLayout(self.layTop)
        self.layInput = QtGui.QVBoxLayout()

        for option in search_method.keys():
            code =  'def by_' + option + '():'

            code += 'self.clearLayout(self.layInput)'

            for input_field in search_method[option]:
                code +=  'self.lay_' + input_field + ' = QtGui.QHBoxLayout()'
                code += ';self.lbl_' + input_field + ' = QtGui.QLabel("' + input_field + '")'
                code += ';self.edit_' + input_field + ' = QtGui.QLineEdit()'

                code += ';self.lay_' + input_field + '.addWidget(self.lbl_' + input_field + ')'
                code += ';self.lay_' + input_field + '.addWidget(self.edit_' + input_field + ')'
                code += ';self.layInput.addLayout(self.lay_' + input_field + ')'

            exec code

        for option in options.keys():
            exec('self.rad_' + option + '.toggled.connect(by_' + option + ')')

        self.setLayout(self.vlay)

app = QtGui.QApplication(sys.argv)
testDialog = TestDialog()
testDialog.show()
sys.exit(testDialog.exec_())

I've also test in normal class, but that works fine ( could recognize the 'self')

class A:
    def __init__(self, parm1, parm2):
        self.parm1 = parm1
        self.parm2 = parm2

    def display(self):
        to_exec = ''
        to_exec += 'def jack():'
        to_exec += 'print "hey hey hey"'
        exec(to_exec)

        exec('print self.parm' + '1')
        exec('print self.parm' + '2')
        exec('jack()')

    def aha(self):
        exec(self.display()')

a = A('hello', 'world')
exec 'a.aha()'

Solution

  • exec is not necessary. It make the code harder to read. How about using a dictionaries to hold widgets?

    import sys
    
    from PyQt4 import QtGui
    
    
    class TestDialog(QtGui.QDialog):
        def __init__(self, parent=None):
            super(TestDialog, self).__init__(parent)
            self.initUI()
    
        def clearLayout(self, layout):
            if layout is None:
                return
            while layout.count():
                child = layout.takeAt(0)
                if child.widget() is not None:
                    child.widget().deleteLater()
                elif child.layout() is not None:
                    self.clearLayout(child.layout())
    
            layout.setParent(None)
    
        def initUI(self):
            search_method = {
                'id': ['id'],
                'detail': ['catagory', 'price', 'enroll_date'],
                'drawer': ['name', 'sex', 'state'],
                'internal': ['transaction_date', 'msg_id'],
            }
    
            self.layTop = QtGui.QHBoxLayout()
            self.lblBy = QtGui.QLabel("By")
            self.layTop.addWidget(self.lblBy)
            self.radios = {}
            self.layouts = {}
            self.labels = {}
            self.edits = {}
    
            for option in search_method:
                r = self.radios[option] = QtGui.QRadioButton(option)
                self.layTop.addWidget(r)
    
            self.vlay = QtGui.QHBoxLayout()
            self.vlay.addLayout(self.layTop)
            self.layInput = QtGui.QVBoxLayout()
    
            def by_option(option):
                self.clearLayout(self.layInput)
                for input_field in search_method[option]:
                    lbl = self.labels[input_field] = QtGui.QLabel(input_field)
                    edit = self.edits[input_field] = QtGui.QLineEdit()
                    layout = self.layouts[input_field] = QtGui.QHBoxLayout()
                    layout.addWidget(lbl)
                    layout.addWidget(edit)
                    self.layInput.addLayout(layout)
                self.vlay.addLayout(self.layInput)
    
            for option in search_method:
                self.radios[option].toggled.connect(
                    lambda yesno, option=option: by_option(option)
                )
    
            self.setLayout(self.vlay)
    
    
    app = QtGui.QApplication(sys.argv)
    testDialog = TestDialog()
    testDialog.show()
    sys.exit(testDialog.exec_())