PyQt 예제 쉬어가기 - 계산기 만들기 2

이번 글 역시 지난 번에 이어지는 계산기 만들기에 대한 글이다.

-----------------------------------------------------------------------------------

이제부터 메소드 부분을 코딩해야 한다. 그러나 딱 코딩을 하려고 하면 조금 막막하다. 버튼 누르는 순서도 있고, 이상한 순서로 버튼을 누르게 되면 계산기가 오작동 할 수도 있기 때문이다.

그래서 일단, 계산기가 작동되는 방법을 확인해봐야 한다.

1. 가장 일반적인 경우 : 1 + 1 = 2
2. 숫자를 이어서 붙임 : 1234 ...
3. 계산을 마치지 않고 계속 연산을 함 : 1 + 1 + 1 + ...

위의 3가지가 일반적으로 계산기가 작동되는 경우이다. 이번에는 오작동하는 경우이다.

1. 연산자를 여러 번 누름 : 1 + - * ...
2. = 버튼을 여러 번 누름 : 1 + 2 = = = = ...
3. 숫자를 누른 후 바로 = 버튼 : 1 =
4. 연산자를 누른 후 = 버튼 : 1 + =

위의 경우 외에도 오작동하는 경우가 더 있을 수 있다.


먼저, 일반적으로 계산기가 작동하는 방식을 구현하기 위해서 이전 글에서 언급했던 self.new 와 self.old, self.operator 를 사용해서 연산을 할 것이다.

self.new 는 레이블에 표시되는 값을 저장하고, self.old 는 레이블에 표시되지 않는 값을 저장한다. self.operator 는 연산자를 저장하게 된다.

이들 속성을 가지고 오작동이 일어나지 않도록 구현을 해보면 아래와 같은 알고리즘으로 구성을 할 수 있다.
(실제로, 알고리즘 없이 코딩을 하다가 코드가 복잡해져서 직접 그려본 알고리즘이다.)

(오픈오피스 그리기에서 작업했다)

버튼이 눌리게 되면 먼저 버튼을 3가지로 구별을 하게 된다. = 버튼에서는 연산자와 이전 값이 존재할 경우에만 연산을 하도록 했다.

연산자 버튼에서는 이전 값이 없다면 바로 연산자를 self.operator 에 대입하지만 이전 값이 있다면, 즉 1 + 1 과 같은 상태이라면 이전 식을 계산한 후 연산자를 대입해야 할 것이다.

숫자와 소수점 버튼에서는 이전 값이 없는데, 연산자가 있는 경우, 즉 1 + 와 같은 상태라면 화면에 표시된 값(self.new)를 self.old 에 대입을 하고 숫자가 입력이 되야 할 것이다.

이 경우 외에는 숫자와 소수점을 구별한 다음 화면에 표시된 숫자가 0일 경우 바로 숫자를 입력시키고, 0이 아니면 숫자를 이어 붙이게 한다. 소수점일 경우에는 화면에 표시된 숫자에 소수점이 없는 경우에만 소수점을 추가하도록 한다.


이와 같이 알고리즘을 구성 했는데, 이것을 직접 코드로 나타내면 아래와 같이 나타낼 수 있다.
    # 레이블 업데이트
    def updateUi(self, text) :
        self.label.setText('<p align="right"><font size=30><b>' + text + '</b></font></p>') # 항상 현재 값을 표시.
    
    # 계산 메소드
    def calculate(self, who) :
        try :
            # = 버튼 처리
            if who == '=' :
                # 이전 값과 연산자가 있을 때만 계산
                if self.old != '' and self.operator != '' :
                    self.new = str(eval(self.old + self.operator + self.new))
                    self.old = ''
                    self.operator = who
            # 연산자 처리
            else :
                # 이전 값이 있을 경우 이전 값과 현재 값을 계산 후 연산자 대입.
                if self.old != '' :
                    self.new = str(eval(self.old + self.operator + self.new))
                    self.old = ''
                self.operator = who
        except :
            self.new = '오류!'
            self.old = ''
            self.operator = ''
        self.updateUi(self.new)
    
    # 숫자와 소수점 처리
    def num_input(self, who) :
        # 오류가 발생한 경우 초기화
        if self.new == '오류!' :
            self.new = '0'
            self.old = ''
            self.operator = ''
        # 계산 후 숫자를 누르면 초기화
        if self.operator == '=' :
            self.new = '0'
            self.operator = ''
        # 이전 값은 없고, 연산자가 있을 경우 이전 값에 현재 값을 대입하고, 현재 값 새로 입력.
        if self.old == '' and self.operator != '' :
            self.old = self.new
            if who == '.' :
                self.new = '0.'
            else :
                self.new = who
        # 일반적인 경우
        else :
            # 소수점일 경우 현재 값에 소수점이 없을 때만 소수점 입력.
            if who == '.' :
                if '.' not in self.new :
                    self.new = self.new + who
            else :
                # 현재 값이 0일 경우 숫자를 누르면 그 숫자가 바로 입력.
                if self.new == '0' :
                    self.new = who
                else :
                    self.new = self.new + who
        self.updateUi(self.new)
메소드를 3개로 나누었는데, 처음에는 한 개로 구성하였다가 너무 길어지는 것 같아서 3개의 메소드로 나누었다.

updateUi 에서는 레이블에 표시해주는 역할을 하고, calculate 에서는 연산자와 = 버튼을 받는다. num_input 에서는 숫자와 소수점 버튼을 받게 된다.


이제, 메소드를 모두 구성하였으므로 시그널과 슬롯을 연결 해주어야 한다. 이전 글에서 언급한 for 문 내에 아래와 같이 코드를 작성해주면 된다.
for index, value in enumerate(['7', '8', '9', '/', '4', '5', '6', '*', '1', '2', '3', '-', '0', '.', '=', '+']) :
    button = QPushButton(value)
    layout.addWidget(button, index//4+1, index%4)
    if value in ['/', '*', '-', '+', '='] :
        self.connect(button, SIGNAL('clicked()'), lambda who = value : self.calculate(who))
    else :
        self.connect(button, SIGNAL('clicked()'), lambda who = value : self.num_input(who))
람다 함수를 사용해서 value 값을 인자로 넘기게 된다. 이때, 메소드를 3개로 나누었으므로 버튼을 종류에 따라서 서로 다른 메소드를 연결 해주어야 한다.

참고로, 람다 함수를 lambda : self.calculate(value) 와 같이 코딩을 하게 되면 제대로 연결이 되지 않는다.
(만약, self.num_input(value) 라고 코딩을 하게 되면 숫자나 소수점을 눌렀을 때 + 가 입력이 된다. 즉, lambda 함수가 하나의 함수로 value 변수를 계속 가지고 있기 때문에 connect 후에도 value 값을 넘기려고 하기 때문인 것 같다.따라서 마지막 값인 + 가 인자로 넘겨지게 된다. self.num_input(who) 의 경우에는 who 가 각각의 람다 함수 내에 존재하는 변수이기 때문에 서로 다른 값을 가질 수 있게 된다.)


이렇게 코딩을 하고 난 후 프로그램을 실행시켜보면 계산기가 잘 작동하는 것을 볼 수 있다. 그런데 계산기에 삭제 및 지움 버튼이 없는데, 만약 계산을 새로 시작하려면 오류를 내거나 계산을 끝낸 후 숫자를 새로 누르면 새롭게 계산을 할 수 있다.

지금까지 언급한 코드는 아래 파일에 모두 작성되어 있다.


지금까지 해서 PyQt 시작하기를 모두 마쳤다. 다음부터는 "시작하기"가 아닌 더 자세한 내용을 다루도록 하겠다.

ps.
6 -2 와 6 - 3에서 언급한 코드는 완벽하지 않을 수 있다. 나도 공부하는 입장이고 전문 프로그래머도 아니기 때문에 깔끔한 코드가 아닐 수 있다. 단지, 내가 코딩한 것을 예로 들었을 뿐이다.

댓글 2개:

  1. AttributeError: 'Form' object has no attribute 'connect'
    이런 오류가뜨는데 어떡하죠 ㅠㅠ

    답글삭제
  2. pyqt5에선 connect를 다르게 써야하네요 ㅎㅎ

    답글삭제