0

I am electrical/power electronic engineer and I am trying to develop extremely simple-to-extend engineering calculator based on SymPy for equation solving and PyQt for simple user interface.

Now I am very stuck trying to achieve readability and correct execution in following stages:

  1. Generate widgets (and setup properties, connect signals to update values method) from dictionary using exec to achieve predictable names in few (easy understandable) lines of code. Works nicely.
        self.variable_list = {
          "R_1": 'R_1',
          "R_2": 'R_2',
          "R_load": 'R_load',
          ...
          }

        self.var_list = list(self.variable_list.keys())
        self.var_names = list(self.variable_list.values())
        
        for name in self.var_list:
            exec("self.%s_input = gui_common.input_checked_field(\"%s\")" %(name, name))
            exec("self.%s_input.setValidator(QtGui.QDoubleValidator())" %(name))
            exec("self.%s_input.editingFinished.connect(self.update)" %(name))
            exec("main_layout.addWidget(self.%s_input)" %(name))


  1. Initialize symbolic variables from same variables list in update function. Works nicely. Generates all variables as SymPy ones from single list.
        for i in range(len(self.var_list)):
            temp_string = "vars(sys.modules[__name__])[\'%s\'] = sp.symbols(\'%s\')" %(self.var_list[i], self.var_names[i])
            exec(temp_string )#, globals(), locals()
            exec("print(%s)" %(self.var_list[i]) )

  1. Update these already defined variables with numerical values before solving equation system. And these stage fails miraculously, since variables are not changed in exec. Why? Because it is not possible, as far as I understand, since CPython is optimizing something with local function variables. Setting variables with exec inside a function Variable not define after exec('variable = value')
        for name in self.var_list:
            temp_string = name + " = update_if_checked(" + name + ", self."+name+"_input)"  
            exec(temp_string) #can be supplied with globals() argument, what leads to self.* names not defined

Function update_if_checked is very simple, since it only checks if corresponding input field has its numeric value lock checkbox checked and if so modifies the variable.

def update_if_checked(var_in, field_in):
    if field_in._checkbox.isChecked():
        print("updating variable")
        #nonlocal var_in
        var_in = float(field_in._field.text())
    return var_in

I have done research and understood that my MCU C with macro preprocessor background is completely out of water in this case, since using exec and eval in Python is heavily unrecommended. And has severe limitations such as inability to update variables in functions.

Can someone push me in right direction with this usecase, please? Any notes on the most Pythonic way to achieve readable batch variables operation are very welcomed. I am not afraid of learning something new, or researching complex case. But I am completely stuck since everything I have came upon severely damages symbolic equation parts readability. Exec-based approach allows to have native symbolic/numerical variables in equation system, allowing non-software-coders write equations efficiently:

        system_without_load = [
                     I_in - U_in/(R_1+R_2),
                     U_out - (U_in - I_in*R_1),
                     P_div - I_in**2*(R_2+R_1),
                     Ratio - U_out/U_in,
                     ]

If I understand correctly, one can define variables as calc's custom widget class fields and use them via "self." prefix, or one can define them as input field's custom widget field and use them with ".var" post-fix, both of which lead to equation system over-complication like:

        system_without_load = [
                     I_in.var - U_in.var/(R_1.var+R_2.var),
                     U_out.var - (U_in.var - I_in.var*R_1.var),
                     P_div.var - I_in.var**2*(R_2.var+R_1.var),
                     Ratio.var - U_out.var/U_in.var,
                     ]

Thanks everyone who reaches this far, reading this puzzle-headed from non-programmer engineer.

  • To get/set instance attributes, you must use [`getattr()`](https://docs.python.org/3/library/functions.html#getattr) and [`setattr()`](https://docs.python.org/3/library/functions.html#setattr). While I understand your issue in providing an "eval"-uable interface, using `exec`/`eval` is discouraged as it has security issues and can easily fail (and crash the program) if the syntax is not correct. Unfortunately, the only proper way to do so is by *parsing* the input string(s), which is often achieved through regular expressions. – musicamante Aug 26 '22 at 14:38
  • https://docs.sympy.org/latest/modules/core.html#module-sympy.core.sympify `sympify` is a `sympy` specific version of `eval`, converting a string into a sympy expression. – hpaulj Aug 26 '22 at 21:44

0 Answers0