0
import sys
from functools import partial
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
    QApplication,
    QWidget,
    QMainWindow,
    QGridLayout,
    QVBoxLayout,
    QLineEdit,
    QPushButton
)

ErrorMessage = "ERROR"


class CalculatorUi(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Calculator")
        self.setFixedSize(300, 300)
        self.generatLayout = QVBoxLayout()
        central_widget = QWidget()
        central_widget.setLayout(self.generatLayout)
        self.setCentralWidget(central_widget)
        self._createDisplay()
        self._createButtons()

    def _createDisplay(self):
        self.display = QLineEdit()
        self.display.setReadOnly(True)
        self.display.setMaxLength(12)
        self.display.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.display.setFixedHeight(50)
        self.generatLayout.addWidget(self.display)

    def _createButtons(self):
        self.buttonMap = {}
        buttonLayout = QGridLayout()
        keyboard = [
            ["7", "8", "9", "/", "C"],
            ["4", "5", "6", "*", "("],
            ["1", "2", "3", "-", ")"],
            ["0", "00", ".", "+", "="]
        ]


        for row, keys in enumerate(keyboard):
            for col,key in enumerate(keys):
                self.buttonMap[key] = QPushButton(key)
                self.buttonMap[key].setFixedSize(50, 50)
                buttonLayout.addWidget(self.buttonMap[key], row, col)

        self.generatLayout.addLayout(buttonLayout)

    def _getDisplayText(self):
        return self.display.text()

    def _setText(self, text):
        self.display.setText(text)

    def _clearDisplay(self):
        self.display.setText('')


class Calculate:
    def __init__(self, model, view):
        self._evaluate = model
        self._view = view
        self._connectSignalsAndSLot()


    def _connectSignalsAndSLot(self):
        for key, button in self._view.buttonMap.items():
            if key not in ("C", "="):
                button.clicked.connect(
                    lambda: print(key)
                )




def evaluate_expression(expression):
    try:
        result = str(eval(expression))
    except Exception:
        result = ErrorMessage
    return result


def main():
    app = QApplication([])
    calculator = CalculatorUi()
    calculator.show()
    Calculate(evaluate_expression, calculator)
    sys.exit(app.exec())

if __name__ == "__main__":
    main()

what i am expecting is that i want print the key that i pressed other than 'C' and '='. But wheneever i click some numeric button and arthematic operator button it just prints out '='. i want it to print the button that i pressed i couldnt understant this behavior. Is there any wrong with my callback or is it due to some other reasonss. plus if i use partial function from the functools it works perfectly fine.

  • `lambda` is always evaluated at runtime within its *scope*, meaning that it will always use the *last* values of a for loop. The "simple" solution of your case is to change to `lambda _, key=key: print(key)`. The *proper* solution depends on the case, but be aware that: 1. `Calculate` is potentially immediately destroyed by the garbage collector (your target functions only "survive" due to their scope); 2. "controller" classes and their instances should be used with awareness: if you need a controller, it must be kept alive by keeping a reference to it, which doesn't happen in your case. – musicamante Sep 14 '22 at 03:22
  • but what does lambda _, key=key do here? can you make it a bit clear? can you demonstrate it with some example. Plus how using partial(function, key) works fine here – mind overflow Sep 14 '22 at 04:02
  • Values of arguments in lambdas are evaluated at runtime, the *default* values of *named* ("keyword") arguments at declaration, just like the value of keyworded arguments in functions and methods. Signals can have arguments, the [`clicked`](//doc.qt.io/qt-5/qabstractbutton.html#clicked) signal has a standard `checked` argument (which defaults to `False` for uncheckable buttons), so if you want to declare a "fixed" argument at runtime you have to ensure that it's not overriden by the signal call. That's why we add a *further* argument, that defaults to a value declared during declaration. – musicamante Sep 14 '22 at 04:12
  • Partial, instead, takes the arguments expressed at its declaration: `for i in range(2): something.setCallback(lambda: print(i))` will always result in `1` since `i` is evaluated when the for loop has ended (so, `i` is `2`), while `for i in range(2): something.setCallback(partial(print, i))` will give `0` **and** `1`, because `i` is evaluated when `partial` is called. I strongly suggest you to do some research on the concept of "scope" of objects in general and in Python. – musicamante Sep 14 '22 at 04:18

0 Answers0