본문 바로가기

Python

Python PyQt5로 계산기 만드는 방법을 알려드립니다 + 중요한 예외처리 알고리즘 포함 !!

반응형

 

PyQt란

 

Qt의 레이아웃에 Python의 코드를 연결하여 GUI 프로그램을 만들 수 있게 해주는 프레임워크를 의미합니다. PyQt는 C++의 Cross Platform GUI Framework인 Qt를 영국의 Riverbank Computing에서 Python 모듈로 변환해 주는 툴을 만들어주며 시작되었습니다. 현재는 PyQt4 버전과 PyQt5 버전이 주로 사용되고 있습니다.

 

macOS - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 클래식 맥 OS에 대해서는 맥 OS 문서를 참고하십시오. macOS(한국어: 맥 오에스, 이전 명칭: OS X, 맥 OS X / Mac OS X)는 애플이 개발한 유닉스 기반 운영 체제[1]이다. 최

ko.wikipedia.org

 

 

PyQt5 설치 방법  v- MacOs

https://smart-factory-lee-joon-ho.tistory.com/343

 

MacOs에서 PyQt 설치 및 사용법 feat. Anaconda 설치

https://wikidocs.net/35478 01.01 PyQt란 무엇인가? [TOC] ##PyQt란? PyQt란, Qt의 레이아웃에 Python의 코드를 연결하여 GUI 프로그램을 만들 수 있게 해주는 프레임워크를 의미합니다. PyQt는 C++의 Cr… wikidocs.net Py

smart-factory-lee-joon-ho.tistory.com

우선 PyQt 설치 방법을 확인하고 싶으면 위의 링크 확인 바랍니다. 

설치 명령어 

pip install PyQt5
pip install pyqt5-tools

우선 PyQt는 디자이너라는 플랫폼을 제공하여 사용자가 쉽게 UI를 그릴 수 있게 도와 줍니다. 

https://codetorial.net/pyqt5/index.html

 

PyQt5 Tutorial - 파이썬으로 만드는 나만의 GUI 프로그램 - Codetorial

PDF & 예제 파일 이 튜토리얼의 내용을 오프라인과 모바일에서 더 편리하게 볼 수 있도록 약 280페이지 분량의 pdf ebook으로 제작하였습니다. PyQt5 튜토리얼 - pdf ebook. 온라인 서점을 통해서 구입이

codetorial.net

해당 홈페이지를 통해 다양한 스몰 프로젝트를 기초부터 따라해볼 수 있습니다.

 

<디자이너 사용법>

윈도우에서는 cmd 창에 designer라고 입력하면 

디자이너 프로그램이 바로 실행됩니다. 

중앙에 띄워진 새폼이라는 박스를 통해 첫 프로젝트의 기본 세팅을 골라 시작할 수 있습니다. 

맨 아래에 있는 Widget 을 선택하면 Form 만 있는 상자가 생기고 

자유롭게 위젯 상자에서 드래그엔 드롭으로 디자인을 하시면 됩니다. 

디자인 부분은 공식 홈페이지를 참고하시기 바랍니다. 

코딩을 위해 중요하게 확인해야 할 부분은 속성 명칭입니다.

기억하기 쉬운 본인만의 이름으로 바꿔 주시면 됩니다. 

계산기 디자인을 이쁘게 해 주셔서 저는 거기에 기능 추가를 좀 했습니다. 

코드는 IDE에서 개발을 해주시면 되는데 

저는 VsCode를 사용하였습니다. 

위 디자이너라는 프로그램에서 디자인을 마치고 저장 버튼을 누르면 

calculator.ui이라는 파일이 생성됩니다. 

해당 경로를 통해 VsCode를 실행해주시면 됩니다. 

cal이라는 파일 안에 크게 ui 파일과 py 파일이 있을 겁니다. 

저의 경우 Vscode 에 확장 프로그램으로 3가지를 설치해 주었습니다.

그랬더니 ui_calculator.py 파일이 자동 생성되었는데 코드를 보면 

코딩하기 쉽게 미리 코드를 디자인과 연결하여 세팅해 준 상태였습니다. 

저는 그냥 처음부터 다시 작성을 해보았습니다. 

 

우선 최초 창 띄우기 기본 예제를 토대로 진행해봅니다. 


< 기초 화면 세팅>

import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic  # UI를 연결한다

# ui 파일 연동
from_class = uic.loadUiType("calculator.ui")[0]

class Window(QWidget,from_class):

    def __init__(self):
        super().__init__()
        self.initUI()
        self.show()

    def initUI(self):
		self.setupUi(self)


if __name__ == '__main__':
   app = QApplication(sys.argv)
   myCalc = Window()
   sys.exit(app.exec_())

 

반응형

ui 파일의 디자인을 가져와 화면에 띄워주는 가장 베이스 코드입니다. 

버튼을 눌러도 아무 일도 일어나지 않지만 디자인 완성된 상태로 UI가 그려집니다.

세부적인 코드 설명은 해당 링크를 이용해 주세요

https://wikidocs.net/21920

 

01) 창 띄우기

![](https://wikidocs.net/images/page/21920/2_1_opening_sample.png) - 위 그림과 같은 작은 창을 하나 띄워보겠습니다.…

wikidocs.net

 


<UI와 버튼 연결>

    def initUI(self):
        self.setupUi(self)
        # 숫자
        self.btn_1.clicked.connect(self.btn_1_click)
        self.btn_2.clicked.connect(self.btn_2_click)
        self.btn_3.clicked.connect(self.btn_3_click)
        self.btn_4.clicked.connect(self.btn_4_click)
        self.btn_5.clicked.connect(self.btn_5_click)
        self.btn_6.clicked.connect(self.btn_6_click)
        self.btn_7.clicked.connect(self.btn_7_click)
        self.btn_8.clicked.connect(self.btn_8_click)
        self.btn_9.clicked.connect(self.btn_9_click)
        self.btn_0.clicked.connect(self.btn_0_click)
        # 사칙연산 + 기호
        self.btn_del.clicked.connect(self.btn_delete_click)
        self.btn_plus.clicked.connect(self.btn_plus_click)
        self.btn_minus.clicked.connect(self.btn_minus_click)
        self.btn_obelus.clicked.connect(self.btn_obelus_click)
        self.btn_times.clicked.connect(self.btn_times_click)
        self.btn_equal.clicked.connect(self.btn_equal_click)
        self.btn_point.clicked.connect(self.btn_point_click)

숫자 버튼과 그 외 버튼들로  주석을 통해 구분해 보았습니다. 

connect 함수 괄호 안에는 클릭 이벤트 함수명을 적어주면 됩니다.


<클릭 이벤트 함수>

#######################
### 클릭 이벤트 함수 ###
#######################


    def btn_1_click(self):
        self.number("1")

    def btn_2_click(self):
        self.number("2")

    def btn_3_click(self):
        self.number("3")

    def btn_4_click(self):
        self.number("4")

    def btn_5_click(self):
        self.number("5")

    def btn_6_click(self):
        self.number("6")

    def btn_7_click(self):
        self.number("7")

    def btn_8_click(self):
        self.number("8")

    def btn_9_click(self):
        self.number("9")

    def btn_0_click(self):
        self.number("0")

# 삭제

    def btn_delete_click(self):
        self.del_num()

# 사칙연산

    def btn_plus_click(self):
        exist_text = self.display.toPlainText()
        if exist_text:
            self.plus()

    def btn_minus_click(self):
        exist_text = self.display.toPlainText()
        if exist_text:
            self.minus()

    def btn_times_click(self):
        exist_text = self.display.toPlainText()
        if exist_text:
            self.times()

    def btn_obelus_click(self):
        exist_text = self.display.toPlainText()
        if exist_text:
            self.obelus()
# 결과

    def btn_equal_click(self):
        self.equal()

# . 기호

    def btn_point_click(self):
        exist_text = self.display.toPlainText()
        if exist_text:
            self.point()

각 버튼 별 클릭 이벤트 함수 안에는 눌렀을 때 작동되는 함수가 들어가 있습니다. 

사칙연산 및  . 기호 클릭 이벤트의 경우 

숫자 입력 전에 입력되는 걸 방지하기 위해 조건 문으로 예외처리가 되어 있습니다.


<기능함수>

위 클릭이벤트 함수 안에서 클릭 시 작동되는 함수들입니다.

1. 수식 추가, 연장 

2. 수식 삭제

3. 사칙연산 중복  입력 방지 함수

4.  [ . ] 기호 생성 제한 알고리즘 함수 :

저는 수식을 화면에 쭉~ 기재하는 방식의 계산기를 만들다 보니 위 알고리즘이 필요하였습니다.  

 

1. 수식 추가 ,연장

   # 수식 추가 , 연장

    def number(self, num):
        # 예외처리 #
        # 소수점 없이 0으로 시작하는 숫자 예외 처리
        # 0을 찍고 . 없이 바로 숫자를 쓰면 0삭제 후 마지막 입력 숫자만 등록

        select_btn = self.sender()
        # 클릭된 버튼에 표기된 text 반환
        select_btn_text = select_btn.text()
        # . 기호 유무 파악을 위한 변수 선언

        exist_text = self.display.toPlainText()
        # 각 변수에 해당 조건이 충족 안되면 최종 입력 값만 기록
        if exist_text == "0" and select_btn_text != ".":
            self.display.setText(select_btn_text)
        else:
            # 조건이 충족되면 추가 입력
            self.display.setText(exist_text + num)

예외 처리 : 최초 입력 시 숫자 0으로 시작하는 숫자는 기록되지 않도록 합니다. ex ) 023 + 52

sender() 함수를 사용해 클릭된 버튼의 정보를 받고 

그 버튼이 가지고 있는 이름을 text () 함수를 통해 가져 옵니다. 

그러면 우리가 무슨 버튼이 눌리고 있는지 알 수 있습니다. 

그래서 조건문은 숫자 0이 기록되고 [ . ] 기호가 입력되지 않는 경우는 기록된 걸 지우고 

마지막에 눌려진 버튼의 정보만 기록하고 그 외의 경우는 동일하게 기록한다 로 작성하였습니다. 

 

2. 수식삭제

def del_num(self):
        exist_text = self.display.toPlainText()
        # 문자열 [:-1]은 index 0 ~ 마지막 문자열 전까지
        self.display.setText(exist_text[:-1])

슬라이싱 문법을 알아야 좋습니다. 

https://twpower.github.io/119-python-list-slicing-examples

 

[Python] 파이썬 슬라이싱(slicing) 기본과 예제

Practice makes perfect!

twpower.github.io

 

exist_text[:-1]

이 코드의 뜻 은
>>> a = ['a', 'b', 'c', 'd', 'e']
>>> a[  : -1 ]
['a', 'b', 'c', 'd']

 

받은 문자열을 하나씩 빼고 보여주기 때문에 삭제되는 것과 동일하게 보입니다.

3. 사칙연산 중복  입력 방지 함수

calculator_icon_list = ["+", "-", "÷", "x", "."]
   # 사칙 연산 기호 중복 입력 방지 함수
    def no_double_math_icon(self):
        exist_text = self.display.toPlainText()
        if exist_text[-1] in calculator_icon_list:
            # 슬라이싱 문법 공부 필수 !
            self.display.setText(exist_text[:-1])

기존에 따로 리스트를 하나 선언해 놓았습니다. 

exist_text[-1]

이 코드는 입력된 마지막 문자를 의미하고

입력된 마지막 문자가 위 리스트 안에 있는 인자와 동일한 것이 있으면 

추가로 입력하는 문자가 삭제되도록 한 함수입니다. 

4.[ . ] 기호 생성 제한 알고리즘 함수

def point_generator(self):
        exist_text = self.display.toPlainText()
        # Counter 문법 확인 필 : 존재하는 인자 개수 확인하여 dict형식 생성
        counter_exist_text = Counter(exist_text)
        # 필수 !! #
        # 형변환 : Counter -> 딕셔너리
        counter_dict = dict(counter_exist_text)

        ### 중요 ###
        # 1. 없는 key 값은 에러를 발생시키는 dict 구조문 dict.get()로 해결 -> return NoneType
        # 2. NoneType 형식 반환이 문제 -> int(value or 0) 문법으로 None -> 0 변환

        # exist_text_icon_count = 연산 기호의 총 개수 #
        exist_text_icon_count = int(counter_dict.get("x") or 0) + \
            int(counter_dict.get("÷") or 0) + int(counter_dict.get("-") or 0) + \
            int(counter_dict.get("+") or 0)

        # 연산 기호의 개수와 "." 개수가 작거나 동일할 때만 추가 "." 입력 가능 조건문
        if exist_text_icon_count >= exist_text.count("."):
            self.number(".")

 

저는 수식을 한 화면 위에 죽 나열하다 보니 위 알고리즘이 필요했는데 

Dict , Counter 문법을 사용할 기회가 있었습니다.

https://www.daleseo.com/python-collections-counter/

 

파이썬 collections 모듈의 Counter 사용법

Engineering Blog by Dale Seo

www.daleseo.com

Counter는 받은 문자열을 각각의 문자의 개수까지 표기하여 dict 형식으로 반환해 주는 모듈입니다. 

하지만 받은 반환 값을 바로 는 사용 못하고 다시 dict으로 변환해주어야 합니다. 

저는 입력된 문자열 중 연산기호의 개수 보다 . 기호가 같거나 작아야 된다고 생각하여 

연산 기호의 개수를 확인하기 위해 위 문법을 사용하였습니다. 


 

###### . 기호 입력 조건 ######
# 1. 최초 숫자 없이 "." 입력 불가
# 2. 연산 기호 입력 전 숫자에는 단 1개의 "." 만 존재 가능
# 3. 연산 기호 후 2번 째 "." 입력 가능
# 4. 연산 기호 보다 " . " 적거나 같아야 됨

 

<사칙 연산 함수 + 기호 함수 + 결론 도출 함수>

###############################
########## 사칙 연산 함수 ######
###############################


    def plus(self):
        self.no_double_math_icon()
        self.number("+")

    def minus(self):
        self.no_double_math_icon()
        self.number("-")

    def obelus(self):
        self.no_double_math_icon()
        self.number("÷")

    def times(self):
        self.no_double_math_icon()
        self.number("x")

#############################
########## . 기호 함수 #######
#############################

###### . 기호 입력 조건 ######
# 1. 최초 숫자 없이 "." 입력 불가
# 2. 연산 기호 입력 전 숫자에는 단 1개의 "." 만 존재 가능
# 3. 연산 기호 후 2번 째 "." 입력 가능
# 4. 연산 기호 보다 " . " 적거나 같아야 됨

# 연산 기호 n개 삽입 기준 총 n+1개의 point가 있을 수 있다.
    def point(self):
        # 1. point 연달아 기재 금지 함수
        self.no_double_point_icon()
        # 2. point 2개 이하 사칙 연산 앞 뒤 1개 씩 입력
        self.point_generator()

###############################
########## 결과 도출 함수 ######
###############################

    def equal(self):

        # 문자열은 배열 형식이지만 수정불가 (튜플) ->수정 가능한 list 변환 과정
        exist_text = self.display.toPlainText()
        # list 형으로 변환
        exist_text_list = list(exist_text)

        try:
            # UI 통일 감을 주기 위한 코드 : 사칙연산 기호 내부 변환
            for i in exist_text_list:
                if i == "x":
                    exist_text_list[exist_text_list.index(i)] = "*"
                elif i == "÷":
                    exist_text_list[exist_text_list.index(i)] = "/"

            # 다시 join 메서드를 통해서 문자열로 변환
            str_text_list = "".join(exist_text_list)

            # Eval 함수는 문자열 식 값을 산수 하여 int로 반환
            answer = eval(str_text_list)

            # 소수점 길이 설정
            answer_round = round(answer, 6)

            # setText 에 맞는 str 형 변환
            self.display_result.setText(str(answer_round))

            # display 화면 초기화
            self.display.setText("")

        except Exception as e:
            print("Error", e)

결론 도출 함수 에는 연산기호 관련 조건문을 넣었습니다.

# UI 통일 감을 주기 위한 코드 : 사칙연산 기호 내부 변환
for i in exist_text_list:
    if i == "x":
        exist_text_list[exist_text_list.index(i)] = "*"
    elif i == "÷":
        exist_text_list[exist_text_list.index(i)] = "/"

컴퓨터가 인식하는 연산기호는 우리가 쓰는 기호와 다르기 때문에 

화면에 우리가 사용하는 연산기호를 입력하되 컴퓨터가 인식할 수 있는 기호로 한번 더 바꿔 주었습니다. 

문자열 -> 리스트 형 -> 수정 -> 다시 문자열 변환 

eval() 함수도 사용하였습니다.

eval(expression)
eval 함수는 한줄로 정리하자면
매개변수로 받은 expression (=식)을 문자열로 받아서, 실행하는 함수 입니다.
즉, 매개변수로 받은 expression은 파이썬에서 실행 가능한 문자열이 들어와야 한다는것이고, 문자열로 들어온 그 expression을 파이썬이 실행해주는 그런 함수 입니다.

문자열 식을 자동으로 산수 하여 int 값으로 반환해줍니다. 

소수점 자리도 6개로 제한하고 값을 세컨드 디스플레이로 보내주고 

메인 디스플레이는 초기화해 줍니다. 

!! 하다 보니 놓친 예외처리도 많습니다!! 

단순 참고하시어 도움 되 시길 바라겠습니다.  

calculator.ui
0.01MB
mycalculator.py
0.01MB

반응형