0. 서론

이전에 만든 클래스를 활용하여 프로그램을 만들고자 한다.

프로그램은 캘린더를 활용하여 자신의 개인 공부를 관리할 수 있는 프로그램을 만들고자 한다.

먼저 프로그램을 만들려면 다양한 요소들이 필요하지만 그중 하나인 UI를 만들기 위하여 PyQt를 활용하기로 했다.

 

designer를 활용한 UI

1. PyQt5

PyQt5는 Qt라는 유명한 UI툴을 파이썬에서 작동시키도록 만든 것이라고 한다.

아쉽게도 설치방법은 여기서 따로 설명이 없을 예정이다...

따라서 구글링을 통하여 설치를 하면 좋겠다.

 

파이참 Terminal에서 designer를 입력하면 다음과 같이 qt 디자이너를 실행시킬 수 있다.

 

여기서 자신이 원하는 디자인으로 UI를 만들면 되겠다.

 

2. ui파일을 py파일로 변환

ui를 디자인하고 파일을 만들면 .ui 확장자로 파일이 생성된다. 

아까의 터미널에서 아래의 명령어로 py파일로 변경할 수 있다.

 

python -m PyQt5.uic.pyuic -x 파일이름.ui -o 생성할이름.py



3. 이벤트 핸들러

py파일을 확인하면 다음과 같은 코드를 발견할 수 있다.

 

...
        self.btInfo = QtWidgets.QPushButton(Dialog)
        self.btInfo.setGeometry(QtCore.QRect(300, 0, 100, 35))
        self.btInfo.setObjectName("btInfo")
...

 

각 요소들이 생성된 것을 알 수 있다.

 

나는 이 버튼에 대하여 클릭시 이벤트를 만들어주고 싶었다.

 

...
        self.btInfo = QtWidgets.QPushButton(Dialog)
        self.btInfo.setGeometry(QtCore.QRect(300, 0, 100, 35))
        self.btInfo.setObjectName("btInfo")
        self.btInfo.clicked.connect(test)
...

다음과 같이 clicked.connet()를 이용하였고 connect 안에는 실행할 메소드를 넣어주면 된다.

 

이로써 Info 버튼을 누를 때 test가 실행되도록 만들 수 있다.

 

4. 정리

아직 핸들링에 대해 완전한 이해를 하지는 못했지만 필요한 기능을 하나씩 알아가면서 프로그램을 완성시키고 있다.

 

사실 프로그램이라는 것을 만들 때는 동작의 구현은 어렵지 않으나 UI와 연결하는 과정이 상당히 귀찮다는 것을느꼈다.

 

Java FX로 프로젝트를 진행한 적 있는데, PyQt5와는 방식이 아에 달라서 처음부터 배우는 거라 시간이 생각보다 많이 걸리고있다..

 

더블클릭이나 키보드 상호작용 등 다양한 이벤트가 존재하는데 이에대한 정보는 아직 프로그램에서 필요가 없다보니 찾아보진 않았다.

 

따라서 이번 포스팅에서는 클릭 이벤트에 대한 핸들링을 중점적으로 보면 될 것 같다.

'Python > Python 문법 공부' 카테고리의 다른 글

다른 .py 파일을 main으로 import 하는 법  (0) 2020.12.23
파이썬 Class 활용 예제  (0) 2020.12.22
파이썬의 Class 개념  (0) 2020.12.22

0. 개요

이전시간에는 Class를 생성하고 응용을 했었다.

하지만 각 클래스를 만들었으나 이 모든 코드를 main.py에 저장할 수는 없을 것이다.

따라서 각 파일에 저장하고 저장된 코드를 main에서 불러오는 방법을 찾아야한다.

1. from~ import~

파이썬에서 다른 파일을 참조하는 것은 from ~ import ~절을 사용하면 된다.

이전에 daily.py 파일의 코드를 살펴보자.

 

# daily.py
import datetime
class daily:
    insCount = 0

    def __init__(self, name, desc, start, end=None):
        # INFOR INSTANCE MEMBER
        self.name = name
        self.describe = desc

        # TIME INSTANCE MEMBER
        self.start = start
        self.end = end
        self.dayCount = (datetime.datetime.now() - datetime.datetime.strptime(self.start, "%Y%m%d")).days

        # CLASS MEMBER
        daily.insCount += 1

        # IF INCLUDE END
        if end :
            # FORMAT STRING TO DATE
            start_day = datetime.datetime.strptime(self.end, "%Y%m%d")
            end_day = datetime.datetime.strptime(self.start, "%Y%m%d")
            day_sum = (start_day-end_day).days

            # INITIALIZE ACHIEVEMENT
            self.achievement = (self.dayCount/day_sum)*100

    def showInfor(self):
        print('---',self.name, '과목 정보---')
        print('describe : ', self.describe)
        print('시작일 : ', self.start)
        if self.end :
            print('종료일 : ', self.end)
            print('acive : ', self.achievement)
        print('현재', self.dayCount, '일째 진행중')
        print()

    def update(self):
        # UPDATE ACHIEVEMENT, DAY COUNT
        self.dayCount = (datetime.datetime.now() - datetime.datetime.strptime(self.start, "%Y%m%d")).days
        if self.end :
            # FORMAT STRING TO DATE
            start_day = datetime.datetime.strptime(self.end, "%Y%m%d")
            end_day = datetime.datetime.strptime(self.start, "%Y%m%d")
            day_sum = (start_day-end_day).days

            # INITIALIZE ACHIEVEMENT
            self.achievement = (self.dayCount/day_sum)*100

eng = daily('eng', '토익공부, 목표 점수 : 900 ', '20201210')
com = daily('com', '컴퓨터구조공부, 다음 학기에 있을 시험 대비 ', "20201111", "20211229")


daily.py라는 파일에 과목별 정보를 저장하는 코드를 작성하였다.

그렇다면 from ~ import ~ 절을 이용하여 main.py에서 호출해보자.

 

# main.py
from daily import eng
from daily import com

eng.showInfor()
com.showInfor()

com.achievement = 0
eng.achievement = 0
com.dayCount = 0
eng.dayCount = 0

eng.showInfor()
com.showInfor()

com.update()
eng.update()

eng.showInfor()
com.showInfor()

main.py의 코드에서 맨 윗줄에서 daily의 eng, com 인스턴스를 각각 import 하는 것으로 daily의 인스턴스를 활용할 수있다.

 

main함수를 실행시키면 다음과 같이 정상적인 작동을 확인할 수 있다.

 

main의 실행 결과 1

2. 클래스 자체를 import 하는 방법

마찬가지로 from ~ import ~ 를 이용하나, import절에서 참조하는 대상을 인스턴스가 아닌 클래스로 참조한다.

# main.py
# 6,7line을 daily.py에서 main.py로 옮겼다.

from daily import daily

eng = daily('eng', '토익공부, 목표 점수 : 900 ', '20201210')
com = daily('com', '컴퓨터구조공부, 다음 학기에 있을 시험 대비 ', "20201111", "20211229")

eng.showInfor()
com.showInfor()

com.achievement = 0
eng.achievement = 0
com.dayCount = 0
eng.dayCount = 0

eng.showInfor()
com.showInfor()

com.update()
eng.update()

eng.showInfor()
com.showInfor()

다음과 같은 코드를 작성해보았다.

 

daily를 참조하고 인스턴스를 만드는 코드를 main으로 이동하고 실행시켜보았다.

 

main의 실행 결과 2

 

역시나 잘 작동하는 것을 확인할 수 있다.

3. 마무리

클래스를 다룰 줄 아는 것도 중요하나 실제 작업에서는 클래스단위로 분할하여 모듈과 같이 필요하면 가져다쓰고 불필요하면 빼는 방식으로 구현할 줄도 알아야한다.

 

생각보다 어렵지 않았고 오히려 상당히 편리했다.

 

어렵지 않게 응용할 수 있을거라 생각한다.

 

0. 설계

자신이 어떤 공부를 하고 있다고 가정했을 때, 다음의 정보를 저장할 수 있는 클래스를 만들어 보았다.

 

1. 과목 이름

2. 설명

3. 시작시간

4. 경과시간

( 5. 예정 종료일 )

( 6. 달성도 )

 

5번과 6번의 경우 생략이 가능하도록 만들었고, 생성자에서 if문으로 조건을 주어

메소드 오버로딩과 비슷한 효과를 내었다.

 

0-1. 소스코드

import datetime

class daily:
    insCount = 0

    def __init__(self, name, desc, start, end=None):
        # INFOR INSTANCE MEMBER
        self.name = name
        self.describe = desc

        # TIME INSTANCE MEMBER
        self.start = start
        self.end = end
        self.dayCount = (datetime.datetime.now() - datetime.datetime.strptime(self.start, "%Y%m%d")).days

        # CLASS MEMBER
        daily.insCount += 1

        # IF INCLUDE END
        if end :
            # FORMAT STRING TO DATE
            start_day = datetime.datetime.strptime(self.end, "%Y%m%d")
            end_day = datetime.datetime.strptime(self.start, "%Y%m%d")
            day_sum = (start_day-end_day).days

            # INITIALIZE ACHIEVEMENT
            self.achievement = (self.dayCount/day_sum)*100

    def showInfor(self):
        print('---',self.name, '과목 정보---')
        print('describe : ', self.describe)
        print('시작일 : ', self.start)
        if self.end :
            print('종료일 : ', self.end)
            print('acive : ', self.achievement)
        print('현재', self.dayCount, '일째 진행중')
        print()

    def update(self):
        # UPDATE ACHIEVEMENT, DAY COUNT
        self.dayCount = (datetime.datetime.now() - datetime.datetime.strptime(self.start, "%Y%m%d")).days
        if self.end :
            # FORMAT STRING TO DATE
            start_day = datetime.datetime.strptime(self.end, "%Y%m%d")
            end_day = datetime.datetime.strptime(self.start, "%Y%m%d")
            day_sum = (start_day-end_day).days

            # INITIALIZE ACHIEVEMENT
            self.achievement = (self.dayCount/day_sum)*100

eng = daily('eng', '토익공부, 목표 점수 : 900 ', '20201210')
com = daily('com', '컴퓨터구조공부, 다음 학기에 있을 시험 대비 ', "20201111", "20211229")

eng.showInfor()
com.showInfor()

com.achievement = 0
eng.achievement = 0

eng.showInfor()
com.showInfor()

com.update()
eng.update()

eng.showInfor()
com.showInfor()

 

1.  datetime 모듈을 이용한 시간의 처리

 

datetime 모듈을 이용하여 현재시간과 시작, 종료일에 대한 시간 처리를 했다.

self.dayCount = (datetime.datetime.now() - datetime.datetime.strptime(self.start, "%Y%m%d")).days

 

시작일로부터 몇일이 지났는지는 현재 시간에서 시작 시간을 빼서 일수로 전환하여 계산하였다.

 

if end :
    # FORMAT STRING TO DATE
    start_day = datetime.datetime.strptime(self.end, "%Y%m%d")
    end_day = datetime.datetime.strptime(self.start, "%Y%m%d")
    day_sum = (start_day-end_day).days

    # INITIALIZE ACHIEVEMENT
    self.achievement = (self.dayCount/day_sum)*100

또한 달성도에 대해서는 시작시간에서 끝나는 시간을 빼서 총 소요되는 일수를 구하고, 

현재 시간에서 총 일수를 나눈 뒤 100을 곱해서 퍼센트화 시켰다.

 

datetime 모듈에 대해서 익숙해진다면 크게 어렵지 않는 활용법이라 생각한다.

 

2. 시간의 경과에 따른 데이터의 갱신

시간은 고정된 것이 아니라 계속 흐르기 때문에 그에 따른 데이터의 갱신이 필요하다.

 

따라서 경과 시간(dayCount)와 달성도(achievement)를 갱신하는 함수가 필요하다.

 

    def update(self):
        # UPDATE ACHIEVEMENT, DAY COUNT
        self.dayCount = (datetime.datetime.now() - datetime.datetime.strptime(self.start, "%Y%m%d")).days
        if self.end :
            # FORMAT STRING TO DATE
            start_day = datetime.datetime.strptime(self.end, "%Y%m%d")
            end_day = datetime.datetime.strptime(self.start, "%Y%m%d")
            day_sum = (start_day-end_day).days

            # INITIALIZE ACHIEVEMENT
            self.achievement = (self.dayCount/day_sum)*100

이는 위의 초기화 과정에서 사용했던 코드를 그대로 사용하면 된다.

 

3. 실행결과

2번의 데이터 갱신을 확인하기 위하여 중간에 다음과 같은 코드를 추가하였다.

com.achievement = 0
eng.achievement = 0
com.dayCount = 0
eng.dayCount = 0

갱신이 필요한 각 요소들을 0으로 초기화 시킨다음 업데이트 메소드를 호출하고 다시 정보를 출력한다면 갱신이 정확하게 되었는지 확인 할 수 있다.

 

 

결과를 보면 중간에 파란색 네모칸 안에 있는 결과가 정보를 강제로 수정 한 뒤의 출력 결과이다.

 

정보를 강제로 수정한 뒤 업데이트를 호출하여 현 시간에 맞게끔 데이터를 갱신한 뒤 다시 출력하였더니 원하는 결과가 나타났다.

 

따라서 잘 구현됬음을 알 수 있다.

 

4. 마무리

파이썬의 Class를 배우고 그에 대한 예제를 직접 만들고 구현해 보았다.

 

이번 예제를 해보면서 특이한 점을 발견했는데 파이썬은 서브펑션에 속해있는 변수들의 이름은 모두 소문자로 하는 것을 권장하더라..

 

자바에 익숙해 져서 두 단어를 연결할 때 두번째 단어를 대문자로 하는 스타일을 자주 썼는데, 언더바로 구분하는 네이밍을 해야할 필요성을 느꼈다.

 

또한 파이참에서 콤마 뒤에 띄워쓰기를 안하면 화이트스페이스가 있어야 한다고 알려주더라..

 

파이썬이라는 언어가 참 화이트스페이스에 민감한 언어인 것 같다.

 

들여쓰기가 제대로 안되면 코드가 안돌아가는 수준이니...

 

그래서

if(condition) : print("true")

이런식의 문법도 불가능하더라..

 

if문이 한줄만 있을 때 자바나 C에서 자주 애용하던 문법인데 아쉽다.

0. 파이썬의 Class를 만나기 앞서.

 

Class라는 개념을 처음 접했을 때는 정말 신박하다고 생각했다.

 

나는 C를 첫 언어로 사용하여 프로그래밍 했는데,

객체지향언어인 자바를 접하고 클래스라는 것을 접하니 정말 편리했다.

 

C는 포인터를 사용하여 주소값을 바탕으로 내가 만든 노드나 구조체에 접근했다.

 

이에 반해 자바는 클래스를 이용하여 객체를 생성했다.

 

그러다 보니 자바는 C와는 다르게 포인터라는 개념이 필요 없었고 단순히 각 객체에 대해서 접근을 하면 됬다.

 

자바의 클래스는 추상적인 존재로써 설계도와 같은 개념이었다.

 

이는 C의 구조체와 비슷한 구조이다.

 

그렇다면 파이썬은 어떨까, 나는 자바와 비슷하게 ADT(추상클래스)의 느낌으로, 클래스는 설계도일 것이다라고 생각을 했었다.

 

다만 파이썬에서는 고려를 해야할 것이 좀 더 있었다.

 

1. 파이썬의 Class

클래스는 클래스를 바탕으로 객체를 생성할 때 호출되는 생성자라는 개념이 존재한다.

자바에서는 클래스의 이름과 같고 반환형이 없는 메소드가 생성자가 되었다.

C에서는 주로 struct 포인터에 malloc을 사용하여 동적 할당을 하였다.

 

그렇다면 파이썬은 어떨까?

def __init__(self):

파이썬의 생성자는 이 형태로 고정이다.

매개변수에 self 이 외에 값들을 추가적으로 줄 수 있다.

 

하지만 이름은 __init__으로 고정되어야 한다.

 

2. 클래스 오브젝트와 인스턴스

먼저 클래스 오브젝트와 인스턴스를 설명하기 앞서 다음의 코드를 살펴보자.

class test:
    all_var = 0

    def __init__(self, name):
        test.all_var += 1
        self.name = name

    def __show__(self):
        print('all_var : ', test.all_var)
        print('name : ', self.name)

    def test(self):
        self.all_var = 4
        print(self.all_var)


s = test('s')
t = test('t')

s.__show__()
t.__show__()

print('modify instance all_var.')
s.test()
t.test()
print(test.all_var)

클래스에 대한 개념을 이해해보고자 test라는 클래스를 만들어 실험해보았다.

 

먼저 클래스 안에 선언되는 것이 클래스 변수,

__init__ 생성자 안에서 생성되는 것이 인스턴스 변수이다.

 

다음과 같이 생성할 수 있다.

self.변수이름

간단한 위의 코드는 다음과 같이 동작한다.

 

1. test 인스턴스인 s와 t를 생성하고 이의 인스턴스 변수인 이름을 s와 t로 정한다.

   인스턴스를 생성하는 순간 클래스 변수인 all_var를 1씩 증가시킨다.

2. 이후 __show__ 함수를 통하여 all_var의 값과 각 인스턴스 변수인 name을 출력한다.

3. 다음으로는 클래스 내에 test 함수를 통하여 각 인스턴스의 all_var 값을 4로 변경시킨다.

4. 각 인스턴스 변수의 all_var와 클래스 변수의 all_var 값을 출력한다.

 

이 과정에 대한 출력은 다음과 같다.

코드 실행 결과

먼저 두개의 인스턴스가 생성된 뒤 출력이 이루어지는데, 조금 특이한 것이 있다.

print('all_var : ', test.all_var)

하나는 각 인스턴스가 all_var를 가지고 있는 것이 아니라 클래스 변수 all_var를 직접 출력하는 것이고

두번째는 각 인스턴스가 공유하는 클래스 변수가 동일하다는 것이다.

 

즉 위와 같이 형성이 된다는 의미다.

s와 t는 각자의 인스턴스지만 test라는 클래스의 all_var라는 변수는 공유하는 셈이 된다.

 

그렇다면 s와 t의 all_var를 self연산자를 통하여 수정하면 어떻게 될까라는 생각이 들고,

 

위의 결과와 같이 4, 4, 2가 출력 된 것을 볼 수 있다.

 

그 의미는 다음과 같은 그림이 된다고 볼 수 있다.

 

클래스 변수인 all_var는 여전히 존재하고, 각 인스턴스에 클래스 변수인 all_var가 각각 할당 되었다는 말이다.

 

물론 이 이후에 다시 __show__() 함수를 사용한다 해도 all_var의 값은 4가 출력되는 것이 아니라 클래스 변수의 값인 2가 출력된다.

 

다만 s.all_var, t.all_var로 접근이 가능해진다는 점이 추가된다. 

 

최종적으로 실험한 코드는 이와 같다.

class test:
    all_var = 0

    # name을 받아서 이름으로 초기화 하면서 인스턴스를 생성
    def __init__(self, name):
        test.all_var += 1
        self.name = name

    # 클래스 변수 all_var를 출력, 인스턴스의 이름을 출력
    def __show__(self):
        print('all_var : ', test.all_var)
        print('name : ', self.name)

    # 각 인스턴스의 all_var를 4로 초기화 하고 인스턴스의 all_var를 출력
    def test(self):
        self.all_var = 4
        print(self.all_var)


#s, t 두개의 test 인스턴스 생성
s = test('s')
t = test('t')

# 이름과 all_var를 출력한다
# 여기서 all_var는 클래스 멤버를 출력
s.__show__()
t.__show__()

# 인스턴스의 all_var를 출력해도 클래스 멤버가 출력된다.
# 이는 인스턴스의 네임스페이스에 존재하지 않기때문에 class로 올라가게 된다.
print(s.all_var ,'변경이전 allvar')


# 각 인스턴스에 all_var를 초기화 시킨다.
# 이후 출력시킨다.
# 각 인스턴스와 클래스의 all_var가 다름을 확인
print('modify instance all_var.')
s.test()
t.test()
print(test.all_var)

# 이후 인스턴스에서 all_var에 접근하면 인스턴스의 all_var에 접근하는 것을 확인
print(s.all_var ,' 추가됨 ')

 

 

3. 정리

1. 파이썬의 클래스는 클래스 멤버와 인스턴스 멤버가 존재한다

2. 인스턴스들의 멤버는 굳이 각자가 클래스 멤버를 초기화 하지 않는 이상 클래스 멤버를 공유한다.

3. 2번의 사실로 인하여 각 인스턴스가 공유해야하는 값이 존재할 때는 클래스 멤버, 

   그렇지 않을 경우 인스턴스 멤버로 초기화 하면 된다.

 

3번의 예시)

학교의 출석 시스템에서, 각 학생들에 대한 정보는 인스턴스 정보로 저장해야하나

전체적인 학생 수와 같이 모두 동일하게 적용되야 하는 경우는 클래스 멤버로 적용한다.

 

구현 예 )

100명이 다니는 학교에 학생 A, B, C가 있을 때, 각 등수는 2, 30, 66일 때 성적 상위 백분위를 구하자고 하면,

등수/전체 학생수의 방식으로 구하기 때문에 전체 학생수(클래스 멤버) 와 등수(인스턴스 멤버)를 활용하여 구할 수 있다.

+ Recent posts