요즘은 많은 공공기관에서 REST API를 제공하고 있다.

 

정식적으로 공공API를 사용하기 위해서는 활용신청 허가를 받아야한다.

 

https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15001106 

 

광주광역시 BIS 도착정보

광주광역시 BIS 도착정보에 대한 데이터를 제공하고 있습니다. 정류소 ID에 해당하는 정류소의 도착 정보 조회가 가능합니다.

www.data.go.kr

 

네이버로도 간편 회원가입을 할 수 있고, 휴대폰 인증을 거치면, 다음과 같이 간단히 신청을 할 수 있다.

 

참고 횟수는 데일리로 100회까지 지원이 되는 것 같다. 아직 정식으로 계속 필요한게 아니라서,

 

이정도로도 만족한다. (인증키가 공유되면 안되므로 가려놓음)

 

 

웹브라우저에서는 다음처럼 잘 불러와진다.

그리고 다음과 같이

import json
import requests
import pickle


def request_bus_info_from_api():
    service_key = 'TOP_SECRET'
    response = requests.get(f"http://api.gwangju.go.kr/json/arriveInfo?serviceKey={service_key}&BUSSTOP_ID=838")
    with open('temp_data.pickle', 'wb') as file:
        pickle.dump(response.content, file)


def load_bus_file():
    with open('temp_data.pickle', 'rb') as file:
        b_bus_stop_data = pickle.load(file)

    s_bus_stop_data = b_bus_stop_data.decode('utf-8')
    bus_stop_data = json.loads(s_bus_stop_data)
    return bus_stop_data


def get_bus_remain_info():
    bus_data = load_bus_file()
    result_list = list()
    for a_bus in bus_data['BUSSTOP_LIST']:
        result_list.append(str(a_bus['SHORT_LINE_NAME']))
        result_list.append(str(a_bus['REMAIN_STOP']))
        result_list.append(str(a_bus['REMAIN_MIN']))
    return result_list

버스 정보를 불러와서 피클로 저장해놓는 함수를 만들고,

import datetime
import sys

import PyQt5
import bus_info
from scratch_for_test import *


class MyBusInfoWindow(PyQt5.QtWidgets.QWidget, bus_info.Ui_MainWidget):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.refresh_count = 0
        self.init_pixmaps()
        self.init_labels_setting()
        self.init_timer()
        self.init_btn_mapping()
        self.label_status.hide()

    def init_btn_mapping(self):
        self.btn_refresh.clicked.connect(lambda state: self._clicked_refresh_btn())

    def init_pixmaps(self):
        self.green_good = PyQt5.QtGui.QPixmap('src/green_bus_good-removebg.png')
        self.green_doubt = PyQt5.QtGui.QPixmap('src/green_bus_doubt-removebg.png')
        self.green_worry = PyQt5.QtGui.QPixmap('src/green_bus_worry-removebg.png')
        self.yellow_good = PyQt5.QtGui.QPixmap('src/yellow_bus_good-removebg.png')
        self.yellow_doubt = PyQt5.QtGui.QPixmap('src/yellow_bus_doubt-removebg.png')
        self.yellow_worry = PyQt5.QtGui.QPixmap('src/yellow_bus_worry-removebg.png')

    def init_timer(self):
        self.inner_timer = PyQt5.QtCore.QTimer()
        self.inner_timer.setInterval(1000)  # 1초
        self.label_now_time.setText(self._get_now_time())
        self.inner_timer.timeout.connect(lambda: self.label_now_time.setText(self._get_now_time()))
        self.inner_timer.start()

        self.bus_refresh_timer = PyQt5.QtCore.QTimer()
        self.bus_refresh_timer.setInterval(1000 * 60)
        self._call_station_info()
        self.bus_refresh_timer.timeout.connect(lambda: self._call_station_info())
        self.bus_refresh_timer.timeout.connect(lambda: self.initialize_refresh_count())
        self.bus_refresh_timer.start()

    def _get_now_time(self):
        return datetime.datetime.now().strftime("%H:%M:%S")

    def initialize_refresh_count(self):
        self.refresh_count = 0
        self.label_status.hide()

    def init_labels_setting(self):
        self.frame_1_labels = list()
        self.frame_2_labels = list()
        self.frame_3_labels = list()
        self.frame_4_labels = list()
        self.frame_1_labels.append(self.frame_1_title)
        self.frame_1_labels.append(self.frame_1_time_left)
        self.frame_1_labels.append(self.frame_1_time_arrival)
        self.frame_1_labels.append(self.frame_1_time_left_title)
        self.frame_1_labels.append(self.frame_1_time_arrival_title)

        self.frame_2_labels.append(self.frame_2_title)
        self.frame_2_labels.append(self.frame_2_time_left)
        self.frame_2_labels.append(self.frame_2_time_arrival)
        self.frame_2_labels.append(self.frame_2_time_left_title)
        self.frame_2_labels.append(self.frame_2_time_arrival_title)

        self.frame_3_labels.append(self.frame_3_title)
        self.frame_3_labels.append(self.frame_3_time_left)
        self.frame_3_labels.append(self.frame_3_time_arrival)
        self.frame_3_labels.append(self.frame_3_time_left_title)
        self.frame_3_labels.append(self.frame_3_time_arrival_title)

        self.frame_4_labels.append(self.frame_4_title)
        self.frame_4_labels.append(self.frame_4_time_left)
        self.frame_4_labels.append(self.frame_4_time_arrival)
        self.frame_4_labels.append(self.frame_4_time_left_title)
        self.frame_4_labels.append(self.frame_4_time_arrival_title)

    def _call_station_info(self):
        request_bus_info_from_api()
        self.refresh_count += 1
        bus_info_list = get_bus_remain_info()
        for i in range(1, 5):
            for label in getattr(self, f'frame_{i}_labels'):
                label: PyQt5.QtWidgets.QLabel
                label.setText('')
        showing_size, temp = divmod(len(bus_info_list), 3)

        for i in range(1, showing_size + 1):
            temp_list = getattr(self, f'frame_{i}_labels')
            temp_list[0].setText(bus_info_list[(i - 1) * 3])
            temp_list[1].setText(bus_info_list[(i - 1) * 3 + 1] + ' 정거장')
            temp_list[2].setText(bus_info_list[(i - 1) * 3 + 2] + ' 분')
            temp_list[3].setText('남은 정류소')
            temp_list[4].setText('남은 도착시간')

        self._update_pixmap()

    def _clicked_refresh_btn(self):
        if self.refresh_count > 1:
            self.label_status.show()
            return
        else:
            self._call_station_info()

    def _update_pixmap(self):
        self.is_yellow_1 = self._assert_is_yellow_bus(1)
        self.is_yellow_2 = self._assert_is_yellow_bus(2)
        self.is_yellow_3 = self._assert_is_yellow_bus(3)
        self.is_yellow_4 = self._assert_is_yellow_bus(4)

        for i in range(1, 5):
            if getattr(self, f"frame_{i}_labels")[0].text() != '':
                if getattr(self, f"is_yellow_{i}") is True:
                    remain_time = int(getattr(self, f"frame_{i}_labels")[2].text()[:2].rstrip())
                    if remain_time > 8:
                        getattr(self, f"frame_{i}_img").setPixmap(self.yellow_good)
                    elif remain_time > 4:
                        getattr(self, f"frame_{i}_img").setPixmap(self.yellow_worry)
                    else:
                        getattr(self, f"frame_{i}_img").setPixmap(self.yellow_doubt)
                else:
                    remain_time = int(getattr(self, f"frame_{i}_labels")[2].text()[:2].rstrip())
                    if remain_time > 8:
                        getattr(self, f"frame_{i}_img").setPixmap(self.green_good)
                    elif remain_time > 4:
                        getattr(self, f"frame_{i}_img").setPixmap(self.green_worry)
                    else:
                        getattr(self, f"frame_{i}_img").setPixmap(self.green_doubt)
            self._assert_is_blank_cell_and_clear(i)

    def _assert_is_yellow_bus(self, index):
        if '봉선' in getattr(self, f'frame_{index}_labels')[0].text():
            return True
        else:
            return False

    def _assert_is_blank_cell_and_clear(self, index):
        if getattr(self, f'frame_{index}_labels')[0].text() == '':
            getattr(self, f'frame_{index}_img').clear()


if __name__ == '__main__':
    app = PyQt5.QtWidgets.QApplication(sys.argv)
    myWindow = MyBusInfoWindow()
    myWindow.show()
    app.exec_()

Qt에 적용시켜주는 로직을 만들어주면?

 

이런식으로 파일이 만들어진다.

 

그리고 간단하게 exe로 실행시킬 수 있게, pyinstaller를 적용해주면,

 

 

매번 pycharm을 실행시킬 필요 없이, 바탕화면에서도 버스 정보를 간단하게 불러올 수 있게 된다.

 

레전드오브복이 프로젝트 전부터 조금씩 진행중이던 개인프로젝트 였는데, 연휴동안 큰 탈 없이 마무리되었다.

 

이제 버스도착정보를 미리 보고 떠날 수 있으니, 시간낭비가 줄어들 것 같다.

 

직접 만든 프로그램을 유용하게 써먹을 수 있게 되어 기분이 좋다.

 

감사합니다.

 

 

 

오늘은 데이터 다루기 연습을 해보았다.

 

목표 : csv 또는 excel 형태의 공공데이터를 pandas로 불러와 sqlite3로 db에 저장한다.

db에 저장해둔 데이터를 query를 이용해 필요한 데이터를 불러온다.

 

타겟 : https://www.data.go.kr/data/15088456/fileData.do

 

광주광역시_시내버스 노선별 승하차 인원 정보_20230430

광주광역시 내 시내버스 승하차 인원정보에 대한 데이터로 일자별, 노선명, 정류장명, 시간별, 승하차별 거래건수를 제공합니다.

www.data.go.kr

나는 필요이상의 데이터를 요하지 않아 4월 27일 ~ 4월 30일 4일간의 데이터만 불러왔다. (그래도 64999 rows)

 

import pickle

import pandas as pd


# test_file = pd.read_excel('bus_info.xlsx')
#
# with open('bus_info_as_byte', 'wb') as file:
#     pickle.dump(test_file, file)

def open_pickle_return_dataframe():
    with open('bus_info_as_byte', 'rb') as file:
        load_bus_info = pickle.load(file)
    load_bus_info: pd.DataFrame
    return load_bus_info
# '일자', '노선명', '정류장명', 'ARS_ID', '시간', '승하차', '거래건수'


if __name__ == '__main__':
    df = open_pickle_return_dataframe()
    print(df.index)

 

우선 pandas로 불러들인 object를 pickle을 이용해 byte로 저장했다.

 

그렇지 않으면 반복적으로 읽을 때 너무 느리다.

 

import sqlite3
import open_xlsx


def create_table():
    conn = sqlite3.connect('bus_info.db')
    c = conn.cursor()
    # '일자', '노선명', '정류장명', 'ARS_ID', '시간', '승하차', '거래건수'
    c.execute("drop table mybus_info")
    c.execute("""
        CREATE TABLE mybus_info (
                  id INTEGER PRIMARY KEY AUTOINCREMENT, 
                  date text, 
                  bus_no text,
                  bus_stop_name text,
                  bus_stop_id int,
                  measured_time text,
                  boarding_type text,
                  transaction_count int
          )
    """)
    conn.commit()
    conn.close()


def fetch_all():
    conn = sqlite3.connect('bus_info.db')
    c = conn.cursor()
    for i in c.execute('select * from mybus_info').fetchall():
        print(i)
    conn.close()


def insert_all():
    conn = sqlite3.connect('bus_info.db')
    c = conn.cursor()
    df = open_xlsx.open_pickle_return_dataframe()
    df_filled = df.fillna(0)
    for idx in df.index:
        temp = (
            str(df_filled.iloc[idx][0]), df_filled.iloc[idx][1], df_filled.iloc[idx][2], int(df_filled.iloc[idx][3]),
            str(df_filled.iloc[idx][4]), df_filled.iloc[idx][5],
            int(df.iloc[idx][6]))
        c.execute("""
            insert into mybus_info 
            (date,bus_no,bus_stop_name,bus_stop_id,measured_time,boarding_type,transaction_count)
            values (?,?,?,?,?,?,?)
        """, temp)
    conn.commit()
    conn.close()


def select_busy_bus_stop(number):
    if not isinstance(number, int):
        raise
    conn = sqlite3.connect('bus_info.db')
    c = conn.cursor()
    for i in c.execute('select * from mybus_info where transaction_count > (?)', (number,)).fetchall():
        print(i)
    conn.close()


if __name__ == '__main__':
    select_busy_bus_stop(100)

 

이후 db를 생성해주는 함수와 dataframe에서 불러들여온 정보를 하나씩 insert 해주는 함수를 만들었다.

 

중간에 NAN으로 승강장이 들어가있지 않는 cell들이 있어 그런 경우엔 '0'으로 채웠다.

 

또한 query를 날려 승하차 건수가 1시간동안 100건이 넘는 정보를 출력하도록 함수를 하나 만들었다.(select_busy_bus_stop)

 

(8360, '20230427', '금남57', '0', 0, '17', '승차', 108)
(8680, '20230427', '금남58', '동림삼익아파트', 4148, '7', '승차', 107)
(25696, '20230427', '마을777', '전남대용봉탑', 4663, '14', '승차', 106)
(25699, '20230427', '마을777', '전남대용봉탑', 4663, '16', '승차', 137)
(36927, '20230427', '문흥18', '전남대공과대학', 4411, '16', '승차', 113)
(48867, '20230427', '봉선37', '호남대', 5265, '15', '승차', 165)
(48868, '20230427', '봉선37', '호남대', 5265, '16', '승차', 137)

 

데이터를 분석해보면, 목요일 14시, 16시 전남대 용봉탑과 공과대학쪽, 호남대는 대학생들이 많이 버스를 이용하고 있다는 것을 알았다.

 

하지만, 이는 옳지 않은 해석이다. 단일 버스 노선의 데이터이기 때문에, 정류장을 기준으로 다시 카운트해줘야하는데..

 

당분간 계속 조작하면서 이것저것 다뤄봐야겠다.

 

또한 깊은 고민 발생, 이것을 어떻게 시각화할 수 있을까..??

 

감사합니다.

 

집에서 홀로 공부하면서 코드리뷰도 하고, 그동안 파이썬 공부하면서 의구심이 생겼던 부분에 대해서 알아보는 시간을 가졌습니다.

 

저는 공부하는 스타일이 "왜 그러지?" 라는 물음에서 시작해서, 다양한 자료 습득을 시행하고, 간단하게 요점정리하여

 

나중에 실제 꺼내보는 일은 드물지만 빠르게 요점을 리마인드 할 때 쓰기 위해 파워포인트로 정리해놓는 것을 선호합니다.

 

그리하여 자세한 설명은 접어두고, 정리된 포인트 몇장 올리고 마무리하겠습니다!

 

다른 분들도 흥미롭게 보실 수 있는 소재라고 생각되니 꼭 읽어보시길 권장합니다.

 

감사합니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
감사합니다.

 

 

 

 

'노리끼리-노르스름-연노랑-누런-샛노랑-노랑-노릇노릇-진노랑-쩐노랑'

 

노란색을 부르는 이름도 다양도 하다. 자주 쓰는 색깔도 이렇게 부르는 이름이 다양한데,

 

보이지 않는 변수, 함수의 이름은 어떻게 불러야할까?

 

 

 

 

슬슬 협동 플레이를 해야하는데...

 

내 맘대로 클래스, 함수, 변수명을 지으면 동료들이 알아볼 수 있을까??

 

 

 

(가정을 해본다)

 

로직이 마음대로 풀리지 않는다. 담당 사수에게 내가 짠 코드를 보여주고 왜 안풀리는지 물어보고 싶다.

 

그런데, "얘는 뭘 뜻하는거야?" "이건 뭘 의미하는거야?"...

 

하루종일 스무고개만 하다가 그냥 처음부터 다시 짜오라고 시킨다.

 

 

어디서 잘못된 걸까??

 

문제의 근본은 내 맘대로 짠 코드들이다.

 

https://peps.python.org/pep-0008/

 

PEP 8 – Style Guide for Python Code | peps.python.org

PEP 8 – Style Guide for Python Code Author: Guido van Rossum , Barry Warsaw , Nick Coghlan Status: Active Type: Process Created: 05-Jul-2001 Post-History: 05-Jul-2001, 01-Aug-2013 Table of Contents This document gives coding conventions for the Python co

peps.python.org

 

 

PEP8 Python Enhancement Proposal 8의 줄임말이다.

 

이곳에서는 내맘대로 짠 코드들(=일관성이 떨어지는)은 작은 홉고블린이라고 표현하고 있다.

 

또한, 일관성 있는 코드들에 대한 중요성을 설파하는데 그 이유는 다음과 같다.

 

* 코드가 쓰여지는 것보다 훨씬 더 자주 읽힌다.

* (PEP8을 준수하면) 가독성을 향상시키고 Python 코드의 광범위한 스펙트럼에서 일관성을 유지하도록 의도한다.

* 스타일 가이드는 일관성이다. 여기 가이드를 따라해는 것은 매우 중요하다.

* 그러나 때로는 일관성을 유지하지 않을 때도 있다.

* PEP8을 준수할지, 기존의 스타일을 유지할지 판단은 당신의 몫, 어떤 것이 가장 좋아 보이는지 판단하라.

* 기존에 있는게 더 읽기 쉬울수도 있으니깐, 기존에 이미 있는걸 PEP8 맞춘다고 다 갈아엎진 마라.

 

 

그럼 도대체 어떻게 하라고?

 

대부분은 Pycharm에서는 Ctrl + Alt + L 만 누르면 된다.

 

그래서 오늘 요약 정리하려는 것은 변수명, 함수명, 클래스명 정도이다.

 

변수명은 다음과 같이 정해라.

Variable names should be lowercase, with words separated by underscores.

변수명은 반드시 소문자를 써라. 띄어쓰기는 언더스코어를 써라.

 

If a variable name needs to be composed of multiple words, the recommended style is to use underscores between the words, rather than camelCase or mixedCase.

만약 변수명이 여러단어로 조합해야할 필요가 있다면, 카멜케이스 기법이나 다른 것 보단 언더스코어를 써라.

 

Variable names should be descriptive and indicative of the variable's purpose.

변수명은 변수의 목적을 암시, 내포하게 정해라.

 

Single-character variable names should be avoided except for certain cases like looping variables.

반복용으로 쓰이는 변수아니면, 외자 변수명 쓰지마라.

(a.k.a a=10)

 

Constants should be named using all capital letters, with words separated by underscores.

안변하는 수(상수) 선언할 때는 전부 대문자로 쓰고 띄어쓰기는 언더스코어 써라.

 

 

함수명은 다음과 같이 정해라.

Function names should be lowercase, with words separated by underscores.

함수는 모두 소문자, 띄어쓰기는 언더스코어 써라.

 

Function names should be descriptive and convey the purpose of the function.

함수의 이름은 함수의 목적이 충분히 설명되게 정해라.

 

If a function name consists of multiple words, each word should start with a capital letter except for the first word. This is called CamelCase or PascalCase.

여러 단어로 조합된 단어로 구성됧 경우는 첫번째만 소문자 시작하고 카멜 케이스로 작성해라.

 

Function names should not be too long or too short. A good guideline is to aim for names that are between 3-20 characters in length.

함수 이름 너무 길게도, 짧게도 쓰지 마라.

 

Avoid using single letter names or abbreviations that are not widely known or understood.

한글자 함수나 아무도 모르겠는 축약어로 함수명 쓰지 마라.

 

If a function is intended for internal use only, start the name with an underscore.

함수를 내부적으로만 쓰고 싶으면, 앞에 언더스코어를 붙여라.

 

If a function is intended to be used as a class method, start the name with a lowercase letter.

클래스 매소드로만 쓸꺼면, 맨 앞글자 소문자로 적어라.

 

# Function name in lowercase with words separated by underscores
def calculate_average(numbers_list):
    pass

# Function name in CamelCase
def calculateAverage(numbersList):
    pass

# Function name in PascalCase
def CalculateAverage(NumbersList):
    pass

# Function intended for internal use only
def _helper_function(input_data):
    pass

# Function intended to be used as a class method
def add_item(self, item):
    pass

 

 

클래스명은 다음과 같이 정해라.

 

Class names should normally use the CapWords convention. This means that the first letter of each word in the name is capitalized, and there are no underscores between words.

클래스 네이밍은 캡워드가 근본이다. 띄워쓰기 대신 첫글자 대문자 써라.

 

Exception classes should end in "Error".

에러클래스 이름은 마지막에 에러라고 붙여라.

 

Names of classes that are meant to be used as interfaces (i.e., subclassed but not instantiated directly) should end in "ABC" (which stands for "Abstract Base Class").

인터페이스로 쓸 용도의 클래스는 뒤에 ABC 붙여라.

 

Private class attributes and methods should start with a single underscore. For example, _internal_method().

접근제한 클래스의 속성과 함수는 언더스코어 붙여라.

 

클래스 예제

 

class MyClass:
    pass

class MyExceptionError(Exception):
    pass

class MyInterfaceABC(metaclass=ABCMeta):
    @abstractmethod
    def my_method(self):
        pass

class _MyPrivateClass:
    pass

감사합니다.

 

오늘 GUI에서의 잠시 일탈(?)하여 그동안 공부하고 싶었던 단위 테스트를 공부해보고자 했다.

 

Q. 테스트를 왜 해야할까?

 

A. 소프트웨어가 잘 작동하는지 확인하려면, 테스트가 필요하다.

 

 

Q. 마지막 한번만 통과하면 되는거 아닌가?

 

A. 예를 들어, 마지막에 추가한 기능이 다른 요소에도 영향을 미쳤다면? 원래 되던 동작이 안될수도 있는데?

매번 모든 동작과 기능을 확인할 필요가 있다.

 

 

Q. 현실적으로 매번 모든 동작을 어떻게 확인하겠는가? 불가능하지 않을까?

 

A. "Unit Test"를 사용하면 가능하다.

 

 

 

얼리 억세스나 베타 테스트 같이 출시 전 유저가 테스트로 플레이해보는 거의 완성 단계에 테스트들이 일반인에겐 익숙할 것이다.

 

하지만, 개발자라면, 가장 많이 진행해야할 테스트가 바로 유닛 테스트(Unit Test)이어야 한다.

 

내가 방금 만든 함수가 멀쩡하게 작동하는지 어떻게 확신할 수 있을까?

 

 

 

가정해보자.

 

중도에 테스트 없이 기능을 몽땅 한꺼번에 만들어놓았다.

 

그러다 갑자기 쌔해서, 실행시켜본 결과 에러가 생겼다....

 

이것도 수정해보고... 저것도 수정해보고... 결국 모두 지웠다가 처음 쓰던지, 하던 프로젝트를 접어버린다.

 

이렇게 어디서 잘못됐는지 몰라 답답한 경우가 있지 않는가?

 

 

그래서 선대의 개발자들은 코딩 스킬만큼 테스트도 잘 만들어왔다. A=> B => C 로 가는 로직이 있는데

 

함수 A부터 짠다고 생각해보자.

 

그리고 A에 대한 테스트 A` 을 만든다.

 

다양한 오류가 날 수 있는 상황에도 A는 어떻게든 우리가 의도한대로 작동해야한다.

(예를 들어, 잘못된 data type이 들어갈 경우 console에 무엇이 잘못됐다고 print하고 raise 를 일으킨다.)

 

함수 B를 만들면서 테스트 B`를 만든다. 그리고 테스트 A`, B`를 시행한다. 문제가 없다.

 

함수 C를 만들고, 테스트 C`를 만든다. 그리고 테스트 A`, B`, C` 를 수행한다.

 

별 것 아닌 것 같지만, 함수와 클래스가 점점 많아지고 두꺼워질수록, 기능 하나 넣는 것이 두려워진다.

 

어디선가 잘못 값을 건드리고 있는 것이 아닌가.. 하고 말이다.

 

개발하는 와중에 테스트들을 차곡차곡 쌓아왔다면?? 새로운 기능을 추가하는게 안두렵지 않을까?

 

이미 테스트 로직들이 국밥처럼 버티고 있으니 새로운 모듈을 붙인다던지, 기능을 추가하더라도 금방금방

 

어디서 문제가 발생하는지 알아낼 수 있게 된다.

 

 

저기서 순서만 조금 바꾸면 바로 Tdd (Test Driven Development)가 된다.

오늘의 주제는 TDD가 아니기 때문에 오늘 내가 직접 파이썬에서 Unit test 한 자료에 대해 정리해보려 한다.

"""
    FileName: My_Error.py
    Created: 2023-05-09
    Description :
        내가 맘대로 정의한 예외 클래스

"""

class MyError(Exception):
    pass
 

이것은 내가 정의한 예외이다.

"""
    FileName: LottoNumberMaker.py
    Created: 2023-05-09
    Description :
        로또 추출기 클래스
"""
import random
from My_Error import MyError


class LottoExtractor():
    def __init__(self):
        self.extracted_list = []

    def get_new_lotto_num_list(self):
        self.lotto_num_reset()
        return self.extracted_list

    def lotto_num_reset(self):
        self.extracted_list = random.sample(range(1, 46), 7)

    def lotto_result(self, list_):
        self.verifying_input_list(list_)
        same_count = 0
        has_bonus = False
        for user_num in list_:
            if user_num in self.extracted_list[:6]:
                same_count += 1
            if user_num == self.extracted_list[6]:
                has_bonus = True

        if same_count == 6:
            return 1
        elif same_count == 5:
            if has_bonus is True:
                return 2
            else:
                return 3
        elif same_count == 4:
            return 4
        elif same_count == 3:
            return 5
        else:
            return 6

    def verifying_input_list(self, list_):
        if len(list_) != 6:
            raise MyError("6개의 숫자가 들어와야합니다.")

        for i in list_:
            if type(i) != "int":
                raise MyError("숫자가 아닙니다.")
 

어제 만든 로또 번호 추출 클래스를 간단하게 만들어봤다.

 

로또 번호를 매번 새로 갱신해서 갖고오는 get_new_lotto_num_list 함수와

 

선택번호 6자리 리스트를 입력하면 당첨 결과를 주는 lotto_result 함수만 테스트 진행했다.

(원래는 함수 정의한 개수만큼 만들어야함)

 

 

"""
    FileName: my_first_unit_test.py
    Created: 2023-05-09
    Description :
        로또 추출기 클래스에 대한 테스트 케이스
"""
from unittest import TestCase
from LottoNumberMaker import LottoExtractor
from My_Error import MyError


class MyTests(TestCase):

    def setUp(self) -> None:
        """
        로또 추출기 객체 생성, 테스트마다 새로운 객체로 매번 갱신됨
        :return:
        """
        self.extractor = LottoExtractor()

    def test_lotto_making_seven_elements(self):
        """
        로또 추출기는 7자리 수를 만들어내야한다.
        :return: None
        """
        self.extractor = LottoExtractor()
        lotto_num_list_1 = self.extractor.get_new_lotto_num_list()
        lotto_num_list_2 = self.extractor.get_new_lotto_num_list()
        self.assertEqual(len(lotto_num_list_1), 7)
        self.assertEqual(len(lotto_num_list_2), 7)

    def test_lotto_nums_all_different(self):
        """
        로또 추출기의 모든 요소들은 서로 다른 값을 가지고 있다.
        :return: None
        """
        lotto_num_list_1 = self.extractor.get_new_lotto_num_list()
        for _ in range(100):
            for i, e1 in enumerate(lotto_num_list_1):
                for j, e2 in enumerate(lotto_num_list_1):
                    if i != j:
                        self.assertNotEqual(e1, e2)

    def test_extractor_takes_only_numbers(self):
        """
        로또 추출기에 넣을 리스트는 숫자만 집어 넣을 수 있다.
        :return: None
        """
        self.assertRaises(Exception, self.extractor.lotto_result, ['a', 1, 2, 3, 4, 5])
        self.assertRaises(Exception, self.extractor.lotto_result, ['a', "b", "c", "d", "e", "f"])
        self.assertRaises(Exception, self.extractor.lotto_result, ['1', "2", "3", "4", "5", "6"])
        # with self.assertRaises(Exception) as context:
        #     self.extractor.lotto_result(['a', 1, 2, 3, 4, 5])

    def test_extractor_raise_when_take_string_list(self):
        """
        숫자가 아닌 것이 들어가면 예외가 발생한다.
        :return: None
        """
        error_message = "숫자가 아닙니다."
        with self.assertRaises(Exception) as assert_error:
            self.extractor.lotto_result("123456")
        self.assertEqual(assert_error.exception.args[0], error_message)
        # self.assertRaises(MyError, self.extractor.lotto_result, "123456")

    def test_extractor_return_one_to_six(self):
        """
        6개 숫자가 아닌 것이 들어가면 예외가 발생한다.
        :return: None
        """
        error_message = "6개의 숫자가 들어와야합니다."
        with self.assertRaises(MyError) as assert_error_1:
            self.extractor.lotto_result([1, 2, 3, 4, 5])
        with self.assertRaises(MyError) as assert_error_2:
            self.extractor.lotto_result([1])
        with self.assertRaises(MyError) as assert_error_3:
            self.extractor.lotto_result([])
        self.assertIn(assert_error_1.exception.args[0], error_message)
        self.assertIn(assert_error_2.exception.args[0], error_message)
        self.assertIn(assert_error_3.exception.args[0], error_message)

    def test_extractor_take_num_range_1_to_45(self):
        """
        범위 밖 숫자가 들어올 경우 에러를 던진다.
        :return: None
        """
        error_message = "1~45 범위의 숫자가 들어와야합니다."
        with self.assertRaises(MyError) as assert_error_1:
            self.extractor.lotto_result([100, 1, 2, 3, 4, 5])
        self.assertEqual(assert_error_1.exception.args[0], error_message)

        with self.assertRaises(MyError) as assert_error_2:
            self.extractor.lotto_result([1, 0, 2, 3, 4, 45])
        self.assertEqual(assert_error_2.exception.args[0], error_message)

        with self.assertRaises(MyError) as assert_error_3:
            self.extractor.lotto_result([-1, 1, 2, 3, 4, 45])
        self.assertEqual(assert_error_3.exception.args[0], error_message)
 

 

각 함수에 대한 설명은 내부에서 확인할 수 있다.

 

 

Pycharm에서는 각각의 정의한 테스드들을 따로 돌릴 수 있게 해준다.

 

초록 버튼을 누르면 이런 결과창이 뜬다.

 

또한 해당 클래스 전체를 한꺼번에 테스트를 할 수 있다.

 
 

 

 
 

 

아쉽게도 Community Version의 Pycharm에서는 Code Coverage의 제한이 있어 각각의 테스트들이 몇 초나 걸렸는지 보여주지는 않는다. 다만, 밀리 세컨드에 대한 데이터가 필요할 정도 서비스라면 Professional Version을 사는게 맞을 것 같다.

 

그리고, 유닛 테스트도 만능이 아니라는걸 늘 잊지말자.

적절한 유닛테스트와 통합테스트는 늘 필요하다는 것.

 

모든 테스트를 통과했다고 좋은 SW도 아니며, 사용자의 요구를 기반으로 테스트가 만들어진다는 것이다.

 

이상 파이썬으로 만들어본 첫 유닛 테스트 경험기였습니다.

 

감사합니다.

 

 

주말동안 열심히 공부하면서 교육자료를 짧게 만들어보았다.

 

감사하게도 요약한 내용에 대해 발표할 기회를 주셔서, 짧게 1시간 정도 발표할 수 있었습니다.

 

많은 분들이 집중하여 들어주셔서 큰 용기가 되었습니다.

 

How to use Qt Designer.pdf
2.50MB

 

오늘의 과제로 로또 번호 추첨기를 만들었다.

 

6개가 되면 some_widget.setEnabled(False)를 줘서 추가로 선택하지 못하게 기능을 추가해보았습니다.

 

소스코드는 다음과 같습니다.

"""
    fileName : MyLottoLogic.py
    description : 로또 추출기 클래스
"""

import random


class LottoNumExtractor():
    def __init__(self):
        self.to_check_list = []
        self.this_time_extraction_numbers = []

    def __repr__(self):
        return f"now check_list = {self.to_check_list} and now extracted num = {self.this_time_extraction_numbers}"

    def make_new_lotto_num(self):
        self.this_time_extraction_numbers = random.sample([x for x in range(1, 46)], 7)

    def get_extracted_lotto_num(self):
        return self.this_time_extraction_numbers

    def get_lotto_result(self, list_):
        if len(list_) != 6:
            raise "6개 숫자가 들어와야 합니다"
        for n in list_:
            if not 0 <= n <= 45:
                raise f"1~45의 숫자가 들어와야합니다. 문제가 되는 입력 : {n}"

        self.make_new_lotto_num()
        count = 0
        for n in list_:
            if n in self.this_time_extraction_numbers[:6]:
                count += 1

        if count == 6:  # 1등인 경우
            return 1
        elif count == 5:  # 2, 3등인 경우
            if self.this_time_extraction_numbers[6] in list_:
                return 2
            else:
                return 3

        elif count == 4:  # 4등인 경우
            return 4
        elif count == 3:  # 5등인 경우
            return 5

        return 6


if __name__ == '__main__':
    app = LottoNumExtractor()
    for i in range(100):
        print(app.get_lotto_result([1, 2, 3, 4, 5, 6]))
"""
    fileName : lotto_window.py
    description : 로또 번호 고르고, 당첨결과를 조회하는 GUI 화면 클래스
"""

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import SelectionLottoWindow
import MyLottoLogic


class MyLottoWindow(QMainWindow, SelectionLottoWindow.Ui_LottoWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self) # desinger 디자인대로 셋업
        self.make_widget_groups() # 위젯(combobox, checkbox, pushbutton) 그룹을 만들어줌
        self.make_lotto_machine() # 로또 번호 생성하고, 6자리 리스트를 넣으면 당첨결과를 반환하는 객체를 만듦
        self.make_my_lucky_num_list() # 각 라인별로 선택한 번호를 저장할 리스트를 생성함

        #### 시그널 선언 ####
        self.connect_signal_comboboxs() # 콤보박스 시그널 매칭
        self.connect_signal_checkboxs() # 체크박스 시그널 매칭
        self.connect_signal_pushbuttons() # 푸시버튼 시그널 매칭

        # "당첨결과 확인하기" 눌렀을 경우
        self.get_lotto_btn_1.clicked.connect(lambda x: self.check_lotto_result(1)) # 1번 라인 당첨 결과 확인
        self.get_lotto_btn_2.clicked.connect(lambda x: self.check_lotto_result(2)) # 2번 라인 당첨 결과 확인
        self.get_lotto_btn_3.clicked.connect(lambda x: self.check_lotto_result(3)) # 3번 라인 당첨 결과 확인
        # TODO: <기능> <통계> 버튼 눌렀을 경우, 메시지창 띄우기

    # TODO: lineEdit_1 중복 있을 시, result_label_1 에 중복값 있다고 setText
    def make_my_lucky_num_list(self):
        self.my_lucky_num_list_1 = [1, 2, 3, 4, 5, 6]
        self.my_lucky_num_list_2 = []
        self.my_lucky_num_list_3 = []

    def make_lotto_machine(self):
        self.lotto_machine = MyLottoLogic.Lotto_Num_Extractor()

    def make_widget_groups(self):
        ### 변수 그룹 선언 ###
        self.combo_group = []  # combobox
        self.cb_group = []  # checkbox
        self.pb_group = []  # pushbutton
        for i in range(1, 7):
            self.combo_group.append(self.__getattribute__("combo_" + f"{i}"))

        for i in range(1, 46):
            self.cb_group.append(self.__getattribute__("cb_" + f"{i:02}"))

        for i in range(1, 46):
            self.pb_group.append(self.__getattribute__("pb_" + f"{i:02}"))

    def connect_signal_comboboxs(self):
        for i, combobox in enumerate(self.combo_group):
            combobox.currentIndexChanged.connect(lambda x, y=i: self.add_my_lucky_num_list_1(x, y))

    def connect_signal_checkboxs(self):
        for i, checkbox in enumerate(self.cb_group):
            checkbox.toggled.connect(lambda x, y=i: self.add_my_lucky_num_list_2(x, y + 1))

    def connect_signal_pushbuttons(self):
        for i, pushbutton in enumerate(self.pb_group):
            pushbutton.toggled.connect(lambda x, y=i: self.add_my_lucky_num_list_3(x, y + 1))

    def console_print_group_state(self):
        """
        콘솔에 group 찍기
        :return: None
        """
        print('combo_group')
        for i in self.combo_group:
            print(i.objectName())

        print('checkbox_group')
        for i in self.cb_group:
            print(i.objectName())

        print('pushbutton_group')
        for i in self.pb_group:
            print(i.objectName())

    def check_lotto_result(self, btn_index):
        """
        "당첨결과 확인하기" 눌렀을 경우 , 해당 my_lucky_num_list 의 선택번호대로 당첨결과를 메세지박스로 알려줌
        :param btn_index:
        :return: None
        """
        if btn_index == 1:
            result = self.lotto_machine.get_lotto_result(self.my_lucky_num_list_1)
        elif btn_index == 2:
            result = self.lotto_machine.get_lotto_result(self.my_lucky_num_list_2)
        elif btn_index == 3:
            result = self.lotto_machine.get_lotto_result(self.my_lucky_num_list_3)

        if result == 6:
            result = "낙첨"
        else:
            result = str(result) + "등"
        QMessageBox.about(self, "당첨 결과",
                          f"""추첨번호 : {sorted(self.lotto_machine.get_extracted_lotto_num()[:6])}, 보너스 번호 : {self.lotto_machine.get_extracted_lotto_num()[6]} 
    선택 번호 : {sorted(self.__getattribute__(f"my_lucky_num_list_{btn_index}"))}\n당첨 결과는 {result} 입니다.""")

    def add_my_lucky_num_list_1(self, num, list_index):
        """
        combobox의 값을 설정할 때 마다, my_lucky_num_list_1 에 값을 변경 하는 함수
        :param num: 콤보 박스에서 선택한 값
        :param list_index: 몇 번째 콤보박스에서 값을 던지는지 인덱스를 받음
        :return:
        """

        self.my_lucky_num_list_1.pop(list_index)
        self.my_lucky_num_list_1.insert(list_index, num + 1)
        print(self.my_lucky_num_list_1)
        self.show_my_lucky_num_list(1)

    def add_my_lucky_num_list_2(self, toggle_value, num):
        if toggle_value is True:
            self.my_lucky_num_list_2.append(num)
        else:
            self.my_lucky_num_list_2.remove(num)

        if len(self.my_lucky_num_list_2) < 6:
            for i, checkbox in enumerate(self.cb_group):
                checkbox.setEnabled(True)
        else:
            for i, checkbox in enumerate(self.cb_group):
                if (i + 1) not in self.my_lucky_num_list_2:
                    checkbox.setEnabled(False)
        self.show_my_lucky_num_list(2)

    def add_my_lucky_num_list_3(self, toggle_value, num):
        if toggle_value is True:
            self.my_lucky_num_list_3.append(num)
        else:
            self.my_lucky_num_list_3.remove(num)

        if len(self.my_lucky_num_list_3) < 6:
            for i, checkbox in enumerate(self.pb_group):
                checkbox.setEnabled(True)
        else:
            for i, checkbox in enumerate(self.pb_group):
                if (i + 1) not in self.my_lucky_num_list_3:
                    checkbox.setEnabled(False)
        self.show_my_lucky_num_list(3)

    def show_my_lucky_num_list(self, index):
        """
        my_lucky_num_list_1에 따라 label 표시 함수
        :return: None
        """
        sorted_string_list = []

        for i in sorted(self.__getattribute__(f"my_lucky_num_list_{index}")):
            if i != 0:
                sorted_string_list.append(str(i))

        result_string = ", ".join(sorted_string_list)

        self.__getattribute__("result_label_" + str(index)).setText(result_string)

    def list_has_no_repeat(self, list_):
        """
            로또 번호는 중복값이 없음. 뽑은 숫자 리스트를 인자로 받음
            중복값이 있을 경우 False를
            중복값이 없을 경우 True를 반환한다.
        :return: boolean
        """
        list_set = set(list_)
        if len(list_set) != len(list_):
            return False
        else:
            return True


if __name__ == '__main__':
    app = QApplication(sys.argv)
    myWindow = MyLottoWindow()
    myWindow.show()
    app.exec_()​

 

 

 

 

감사합니다.

 

오늘은 오전부터 점점 UI 디자인에 대해 깨달음을 얻은 것 같았다.

 

게임사는 UI 디자인에 진심이기 때문에, 이걸 따라하면 또 무언갈 깨닫지 않을까 고심하였고,

 

그 서늘하고 묵직한 느낌 아니까.. 따라하기 Topic을 이와 같이 선정해보았다.

 

 

 

교수님께서 꾸미는 디자인보단 우선적으로 기능 구현을 해놓은 뒤에 디자인을 입히라고 말씀하셔서

 

큰 골조만 짜고 후에 디자인을 추후에 채워보기로 한다.

 

그리하여 이미지도 대충 붙여놓았으니, 이점 양해 바랍니다.

 

Qt 디자이너로 다음과 같은 login_window.ui를 하나 제작하고

 

대표사진 삭제

사진 설명을 입력하세요.

로그인 결과를 띄워주는 Dialog를 만들어보았다.

 

추후에 로그인이 성공하면 메인화면을 띄우는 로직을 실행시키고,

 

실패하면 이런 다이어로그가 뜨게 하는 것이다.

 

 

그 다음, 우선 메인메뉴를 들어가야하니깐, 일단 MainMenu 버튼을 만들었다.

 


막상 구현하려니까 상단 메뉴와 하부 이미지를 어떻게 통일시켰을지 예상이 되면서, 프론트엔드에 깊은 애도를..

 

소스는 uic를 통해 구현하였고, 스택 위젯을 적용시켰다.

 

import sys
import os
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5 import uic


def resource_path(relative_path):
    base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)


login_window = resource_path('login_window.ui')
login_window_class = uic.loadUiType(login_window)[0]

login_result_modal = resource_path('login_modal.ui')
login_result_modal_class = uic.loadUiType(login_result_modal)[0]

main_window = resource_path('main_menu.ui')
main_window_class = uic.loadUiType(main_window)[0]


class MainWindowClass(QMainWindow, main_window_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setWindowFlags(Qt.FramelessWindowHint)

        # 여기에 시그널, 설정
        self.btn_play.clicked.connect(self.btn_play_clicked)
        self.btn_home.clicked.connect(self.btn_home_clicked)
        self.btn_profile.clicked.connect(self.btn_profile_clicked)
        self.btn_collection.clicked.connect(self.btn_collection_clicked)
        self.btn_TFT.clicked.connect(self.btn_TFT_clicked)
        self.btn_user_box.clicked.connect(self.btn_user_box_clicked)
        self.btn_shop.clicked.connect(self.btn_shop_clicked)
        self.btn_user_logo.clicked.connect(self.btn_user_logo_clicked)
        self.btn_qustion.clicked.connect(self.btn_question_clicked)
        self.btn_minimize.clicked.connect(self.btn_minimize_clicked)
        self.btn_setting.clicked.connect(self.btn_setting_clicked)
        self.btn_close.clicked.connect(self.btn_close_clicked)
        self.btn_online_state.clicked.connect(self.btn_online_state_clicked)
        self.btn_this_week_lotation.clicked.connect(self.btn_this_week_lotation_clicked)

    # 여기에 함수 설정
    def upper_bar_frame(self): # 상단 프레임 드래그앤드롭
        pass

    def btn_play_clicked(self):
        self.main_body_stack.setCurrentIndex(0)

    def btn_home_clicked(self):
        self.main_body_stack.setCurrentIndex(1)

    def btn_profile_clicked(self):
        self.main_body_stack.setCurrentIndex(2)

    def btn_collection_clicked(self):
        self.main_body_stack.setCurrentIndex(3)

    def btn_TFT_clicked(self):
        self.main_body_stack.setCurrentIndex(4)

    def btn_user_box_clicked(self):
        pass

    def btn_shop_clicked(self):
        pass

    def btn_user_logo_clicked(self):
        pass

    def btn_question_clicked(self):
        pass

    def btn_minimize_clicked(self):
        pass

    def btn_setting_clicked(self):
        pass

    def btn_online_state_clicked(self):
        pass

    def btn_this_week_lotation_clicked(self):
        pass

    def btn_close_clicked(self):
        self.close()


class LoginWindowClass(QMainWindow, login_window_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setWindowFlags(Qt.FramelessWindowHint)

        # 여기에 시그널, 설정
        self.btn_login.clicked.connect(self.btn_login_clicked)
        self.btn_main.clicked.connect(self.btn_main_menu_clicked)

    # 여기에 함수 설정
    def btn_login_clicked(self):
        self.login_result_window = LoginResultModalClass()
        self.login_result_window.show()

    def btn_main_menu_clicked(self):
        self.main_window = MainWindowClass()
        self.main_window.show()
        self.close()


class LoginResultModalClass(QWidget, login_result_modal_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setWindowFlags(Qt.FramelessWindowHint)

        # 여기에 시그널, 설정
        self.btn_confirm.clicked.connect(self.btn_confirm_clicked)

    # 여기에 함수 설정
    def btn_confirm_clicked(self):
        self.close()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    myWindow = LoginWindowClass()
    myWindow.show()
    app.exec_()
 

 

소스코드는 우선 아직 간단하게 짜보았다. 기능 구현하는 것은 스택 위젯과 닫기 버튼정도.

 

setWindowFlags(Qt.FramelessWindowHint) 함수를 써서 외곽 프레임을 없애고, 버튼을 새로 만들어 주었다.

(그래야 나중에 UI입힐 때 통일성 줄 수 있음)

 

그래서 만들어진 화면은 다음과 같다.

 

감사합니다.

 

 

 

 

 

 

아직 주로 import 하는 세가지 모듈에서 써보거나 구경한 class들만 마킹을 해보았다.

 

각 클래스들마다 각각 고유의 클래스들이 또 있다보니, 정의된 함수를 모두 암기하는 것은 많이 쉽지 않을 것 같다는 결론이 들었다.

 

그래서 오늘의 계획은 잘 만드는 사람의 영상을 따라하며 어깨너머 감을 익혀보기로 했다.

 

https://www.youtube.com/watch?v=adC48qZ8p5Y&t

 

영상에선 설명이 자세하지 않아 따라 누르기만 하느라 시간이 금방 흘러버렸다.

 

그리고 기본 UI도 달랐다. (자체 외부 플러그인을 써서 designer 컬러가 다르나 버튼 위치는 동일했음)

 

feather icon에서 기본제공하는 아이콘들도 다 블랙 기본이라, 사용한 SVG icon들도 컬러를 화이트로 바꿔주는 작업이 필요했다.(유튜브 영상에선 안나옴)

 

벡터 이미지를 표현하는 svg 파일도 메모장으로 열면 열린다.

 

그렇게 하나씩 따라해보면서 하는 중인데..

 

 

import sys
import os
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5 import uic


def resource_path(relative_path):
    base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)


form = resource_path('interface.ui')
form_class = uic.loadUiType(form)[0]


class WindowClass(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    myWindow = WindowClass()
    myWindow.show()
    app.exec_()

 

오늘은 아직 이정도까지만 구현했다. 디자인 서식을 하나씩 따라하면서 해보는 중인데, 이것도 밥먹듯하면 개념이 익숙해질 것 같았다. 오히려 css보다 쉬울지도..?

대신에 후반부에 소스를 끌어와 함수를 적용시키는 건 당분간 쉽지 않을 것 같다.

 

방법이 있다.

 

될 때까지.. 이해할 때까지 트라이하다보면 언젠가 깨닫기 때문에..

 

언젠간 이해하기 때문에..

 

계속 해보는 방법이 있다.

 

내일도 또 열심히 달려보겠습니다.

 

감사합니다.

 

 

오늘부터 PyQt5 학습을 시작했다.

 

어떻게? "알아서 공부하기"

 

나는 이 방식이 좋다. 실제 업무를 하더라도 그렇게 다들 기술 스택을 쌓아가니까.

 

지금 당장엔 당황스럽고 어렵고 속도가 안나오더라도...

 

독립하고, 시니어가 되면 이제 알려주는 사람이 없다.

 

결국 언젠간 스스로 공부하고 터득할 줄 알아야하기 때문에.

 

이런 교육 방식은 의미있는 가르침이라 생각한다.

 

 

그리하여 오늘 스스로 공부한 내용은 무엇인가?

 

* PyQt5 내 모듈들의 종류와 간략한 요약

* PyQt5 의 클래스 상속 분석

* PyQt5 실제 타이핑 연습한 것

 

 

"""

* PyQt5 모듈

- Qt Core

: 그래픽과 관련되지 않은 핵심 클래스들

- Qt GUI

: OpenGL 을 포함한 기초적인 GUI 컴포넌트들

- Qt Multimedia

: 오디오, 비디오, 라디오, 카메라 기능들에 관한 클래스들

- Qt Multimedia Widgets

: 위젯 베이스의 멀티미디어 기능들

- Qt Network

: 네트워크 프로그래밍을 보다 쉽게 만들어주는 클래스들

- Qt QML

: Qt modeling language(QML) 과 JavaScript 언어에 관한 모듈

- Qt Quick

: 커스텀 인터페이스를 제공하는 동적 어플리케이션 제작에 필요한 선언적인 프레임 워크

- Qt Quick Controls

: 가벼운 QML 형식의 유저 인터페이스 제공 모듈, 데스크탑, 임베디드, 모바일에서 가볍게 적용 가능

- Qt Quick Dialogs

: Qt Quick apple에 필요한 System dialog를 만들어줌

- Qt Quick Layouts

: Quick 2 베이스를 기반한 레이아웃을 만들어줌

- Qt Quick Test

: QML 어플리케이션에 관한 유닛 테스트를 지원함. 자바스크립트로 쓰여짐

- Qt SQL

: 데이터베이스 통합 관한 클래스들

- Qt Test

: Qt 어플리케이션에 대한 유닛 테스트 관한 모듈

-Qt Widgets

: 넓은 범위의 GUI 클래스들 제공

 

"""

 

이것은 Qt4의 상속관계이다. (최근 이미지는 못찾음)

 

QWidget이 QtGUI에 소속되어 있는 모습이다.

2일정도 PyQt를 공부하면서 봐온 함수들을 마킹해보았다.

 

 

import datetime
import sys

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *


class Main(QDialog):
    def __init__(self):
        super().__init__()
        self.config_ui()

    def config_ui(self):
        main_layout = QFormLayout()
        # 이름 h-box-layout
        name_layout = QHBoxLayout()
        name_edit_line = QLineEdit()
        name_layout.addWidget(name_edit_line)

        # 생일 h-box-layout
        birthday_layout = QHBoxLayout()
        year_combo_box = QComboBox()
        month_combo_box = QComboBox()
        date_combo_box = QComboBox()
        next_year = datetime.datetime.now().year + 1
        year_combo_box.addItems([str(x) for x in range(1900, next_year)])
        month_combo_box.addItems([str(x) for x in range(1, 13)])
        date_combo_box.addItems([str(x) for x in range(1, 32)])
        birthday_layout.addWidget(year_combo_box)
        birthday_layout.addWidget(month_combo_box)
        birthday_layout.addWidget(date_combo_box)

        # 주소 h-box-layout
        address_layout = QVBoxLayout()
        address_box = QComboBox()
        address_box.addItems([
            "서울", "광주", "경기", "울산", "부산", "강원"
        ])
        detail_address_edit_line = QLineEdit()
        address_layout.addWidget(address_box)
        address_layout.addWidget(detail_address_edit_line)

        # 이메일 h-box-layout
        email_layout = QHBoxLayout()
        email_edit_line = QLineEdit()
        email_company_line = QLineEdit()
        email_company_combobox = QComboBox()
        email_company_combobox.addItems([
            "google.com", "naver.com", "daum.net"
        ])
        email_layout.addWidget(email_edit_line)
        email_layout.addWidget(QLabel("@"))
        email_layout.addWidget(email_company_line)
        email_layout.addWidget(email_company_combobox)

        # 전화번호 h-box-layout
        phone_number_layout = QHBoxLayout()
        phone_number_front = QLineEdit()
        phone_number_middle = QLineEdit()
        phone_number_end = QLineEdit()
        phone_number_layout.addWidget(phone_number_front)
        phone_number_layout.addWidget(QLabel("-"))
        phone_number_layout.addWidget(phone_number_middle)
        phone_number_layout.addWidget(QLabel("-"))

        phone_number_layout.addWidget(phone_number_end)

        # 키 h-box-layout
        height_layout = QHBoxLayout()
        height_widget = QSpinBox()
        height_widget.setValue(20)
        height_layout.addWidget(height_widget)

        # 개인정보 제공 h-box-layout
        privacy_information_share_layout = QHBoxLayout()
        privacy_info_share_agree_checkbox = QCheckBox()
        privacy_info_share_agree_checkbox.setChecked(True)
        privacy_information_share_layout.addWidget(privacy_info_share_agree_checkbox)
        privacy_information_share_layout.addWidget(QLabel("동의"))
        privacy_information_share_layout.addStretch()

        # 플레인 텍스트 h-box-layout
        self_introduction_layout = QHBoxLayout()
        self_intro_text = QPlainTextEdit()
        self_introduction_layout.addWidget(self_intro_text)

        # Save or cancel button h-box-layout
        save_cancel_layout = QHBoxLayout()
        save_button = QPushButton()
        save_button.setText("저장")
        cancel_button = QPushButton()
        cancel_button.setText("취소")
        save_cancel_layout.addWidget(save_button)
        save_cancel_layout.addWidget(cancel_button)

        # add row on main layout
        main_layout.addRow("이름: ", name_layout)
        main_layout.addRow("생일: ", birthday_layout)
        main_layout.addRow("주소: ", address_layout)
        main_layout.addRow("E-mail: ", email_layout)
        main_layout.addRow("전화번호: ", phone_number_layout)
        main_layout.addRow("키: ", height_layout)
        main_layout.addRow("개인정보제공 동의: ", privacy_information_share_layout)
        main_layout.addRow("자기소개: ", self_introduction_layout)
        main_layout.addRow("", save_cancel_layout)

        # showing
        self.setFont(QFont("맑은 고딕", 12))
        self.setLayout(main_layout)
        self.resize(300, 400)
        self.show()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    sample_window = Main()
    app.exec()
 
 

실행화면은 이렇다.

 

내일은 현재 Qt5 모듈에 대해 조금 더 분석해보고,

 

이것 저것 시도해볼 예정이다.

 

감사합니다.

 

# bool type 자료형

print(type(True))
print(type(False))
 
<class 'bool'>
<class 'bool'>
 

# 다양한 자료형의 인식

print('a is True ? ', bool('a'))
print('\'\' is True ? ', bool(''))
print('1 is True ? ', bool(1))
print('0 is True ? ', bool(0))
print('[1,2] is True ?', bool([1, 2]))
print('[] is True ?', bool([]))
# print('(1,2) is True ?', bool(tuple(1, 2))) # error!! tuple 내 값이 존재할 경우.
print('() is True ?', bool(tuple()))
print('dict{"a":"1"} is True ?', bool({'a': '1'}))
print('dict{} is True ?', bool(dict()))
print('None is True ?', bool(None))
 
a is True ?  True
'' is True ?  False
1 is True ?  True
0 is True ?  False
[1,2] is True ? True
[] is True ? False
() is True ? False
dict{"a":"1"} is True ? True
dict{} is True ? False
None is True ? False
 

# 조건문 사용

print('[True and True] is True?', bool(True and True))
print('[True and False] is True?', bool(True and False))
print('[False and False] is True?', bool(False and False))
print('[True or True] is True?', bool(True or True))
print('[True or False] is True?', bool(True or False))
print('[True ^ True] is True?', bool(True ^ True))
print('[True ^ False] is True?', bool(True ^ False))
print('[True & False] is True?', bool(True & False))  # && 연산자 없음!
print('[True | False] is True?', bool(True | False))  # || 연산자 없음!
list_ = [1, 2, 3]
print('[5 in list_] is True?', bool(5 in list_))
print('[1 in list_] is True?', bool(1 in list_))
print('[5 not in list_] is True?', bool(5 not in list_))
print('[True ^ bool(None)] is True?', bool(True ^ bool(None)))
 
[True and True] is True? True
[True and False] is True? False
[False and False] is True? False
[True or True] is True? True
[True or False] is True? True
[True ^ True] is True? False
[True ^ False] is True? True
[True & False] is True? False
[True | False] is True? True
[5 in list_] is True? False
[1 in list_] is True? True
[5 not in list_] is True? True
[True ^ bool(None)] is True? True
 

# python 의 '=='의 용도

실제 다른 객체임에도 불구하고(주소 값이 다름) '==' 를 했을 때, 내부 요소를 직접 비교하여 같은 값을 나타내고 있으면, True 를 반환한다.

list_1 = [1.2]
list_2 = [1.2]
print('id(list_1) : ', id(list_1))
print('id(list_2) : ', id(list_2))
print('bool(list_1 == list_2) is True?', bool(list_1 == list_2))
 
id(list_1) :  1796999509696
id(list_2) :  1797000082240
bool(list_1 == list_2) is True? True
 

# 변수 = 리터럴(각종 자료값)

# python 에서 구분 하는 literal 은 5가지

print('str : "abc"')
print('numeric : 0, 2, 3..')
print('bool : True, False')
print('collection : list, dict, tuple,..')
print('special : None')
 
str : "abc"
numeric : 0, 2, 3..
bool : True, False
collection : list, dict, tuple,..
special : None
 

# input은 str으로 결과를 반환 한다

my_name = input('이름이 무엇입니까?')
print(type(my_name)) # // "1"를 입력했을 때
 
<class 'str'>
 

 

# 같은반 학우들과 같이 공부하자는 취지에서 몇가지 문제를 출제 해보았다.

많은 분들이 어려운 문제임에도 불구하고 도전하고 클리어하셔서 감사했습니다. 분명 많은 도움이 되셨으리라 생각됩니다. 디스코드에도 문제를 올렸지만, 추후에 참고 및 기억용으로 남겨놓겠습니다.

# 문제1: 다음은 경찰청 부산광역시경찰청 교통사고 발생 현황.csv 의 일부 내용입니다.
"""구분,발생건수,사망자 수,부상자 수
2019,13250,127,18357
2020,12091,112,16664
2021,11550,118,15880
2022,11133,119,15335
"""
# 문제 1-1) 2019 ~ 2022년도 사망자 수와 부상자 수 평균을 구하세요(str함수 사용!)
# 해설:
# ta : traffic_accident
ta_data = """구분,발생건수,사망자 수,부상자 수
2019,13250,127,18357
2020,12091,112,16664
2021,11550,118,15880
2022,11133,119,15335
""".split('\n')

ta_data_2019 = ta_data[1].split(',')
ta_data_2020 = ta_data[2].split(',')
ta_data_2021 = ta_data[3].split(',')
ta_data_2022 = ta_data[4].split(',')

death_sum = 0
death_sum += int(ta_data_2019[2])
death_sum += int(ta_data_2020[2])
death_sum += int(ta_data_2021[2])
death_sum += int(ta_data_2022[2])

injury_sum = 0
injury_sum += int(ta_data_2019[3])
injury_sum += int(ta_data_2020[3])
injury_sum += int(ta_data_2021[3])
injury_sum += int(ta_data_2022[3])
print(f"2019 ~ 2022년도 사망자 수 평균 : {death_sum / 4:10.1f}명")
print(f"2019 ~ 2022년도 부상자 수 평균 : {injury_sum / 4:10.1f}명")

# 문제 1-2) 최근 4년 동안 교통사고 발생건수의 합은 몇 건입니까?
ta_sum = 0
ta_sum += int(ta_data_2019[1])
ta_sum += int(ta_data_2020[1])
ta_sum += int(ta_data_2021[1])
ta_sum += int(ta_data_2022[1])
print(f"2019 ~ 2022년도 교통사고 발생건수 합 : {ta_sum:10d}건")
 
2019 ~ 2022년도 사망자 수 평균 :      119.0명
2019 ~ 2022년도 부상자 수 평균 :    16559.0명
2019 ~ 2022년도 교통사고 발생건수 합 :      48024건
 
# 문제 2: 다음은 인천광역시 미추홀구_제설함 현황(도화 1동).csv 의 일부 내용입니다.
prevent_snow_data = """연번,구분,관리부서,전화번호,도로명주소
1,제설함,도화1동,032-728-6281,인천광역시 미추홀구 주안서로33번길 14
2,제설함,도화1동,032-728-6281,인천광역시 미추홀구 주안서로53번길 65
3,제설함,도화1동,032-728-6281,인천광역시 미추홀구 주안서로33번길 52
4,제설함,도화1동,032-728-6281,인천광역시 미추홀구 주안서로33번길 71
5,제설함,도화1동,032-728-6281,인천광역시 미추홀구 경인로265번길 43""".split("\n")

data_title = prevent_snow_data[0].split(',')
data_row_1 = prevent_snow_data[1].split(',')
data_row_2 = prevent_snow_data[2].split(',')
data_row_3 = prevent_snow_data[3].split(',')
data_row_4 = prevent_snow_data[4].split(',')
data_row_5 = prevent_snow_data[5].split(',')

# 문제 2-1) 이 데이터를 하나의 딕셔너리 구조로 만들어보세요.
# 해설 : 여러 방법 존재합니다.
prevent_icing_road_box_type_1 = {
    '구분': '제설함',
    '관리부서': '도화1동',
    '전화번호': '032-728-6281',
    '연번, 주소': [(1, '인천광역시 미추홀구 주안서로33번길 14'),
               (2, '인천광역시 미추홀구 주안서로53번길 65'),
               (3, '인천광역시 미추홀구 주안서로33번길 52'),
               (4, '인천광역시 미추홀구 주안서로33번길 71'),
               (5, '인천광역시 미추홀구 경인로265번길 43')]
}

prevent_icing_road_box_type_2 = {
    data_title[1]: data_row_1[1],
    data_title[2]: data_row_1[2],
    data_title[3]: data_row_1[3],
    data_title[0] + ', ' + data_title[4]: [(data_row_1[0], data_row_1[4]),
                                           (data_row_2[0], data_row_2[4]),
                                           (data_row_3[0], data_row_3[4]),
                                           (data_row_4[0], data_row_4[4]),
                                           (data_row_5[0], data_row_5[4])]
}
print(prevent_icing_road_box_type_1)
print(prevent_icing_road_box_type_2)
 
{'구분': '제설함', '관리부서': '도화1동', '전화번호': '032-728-6281', '연번, 주소': [(1, '인천광역시 미추홀구 주안서로33번길 14'), (2, '인천광역시 미추홀구 주안서로53번길 65'), (3, '인천광역시 미추홀구 주안서로33번길 52'), (4, '인천광역시 미추홀구 주안서로33번길 71'), (5, '인천광역시 미추홀구 경인로265번길 43')]}
{'구분': '제설함', '관리부서': '도화1동', '전화번호': '032-728-6281', '연번, 도로명주소': [('1', '인천광역시 미추홀구 주안서로33번길 14'), ('2', '인천광역시 미추홀구 주안서로53번길 65'), ('3', '인천광역시 미추홀구 주안서로33번길 52'), ('4', '인천광역시 미추홀구 주안서로33번길 71'), ('5', '인천광역시 미추홀구 경인로265번길 43')]}
 
# 문제 3: 다음은 대구광역시교육청 대구광역시립달성도서관_인기도서목록.csv 의 일부 내용입니다.
book_table_list = """순위,등록번호,청구기호,서명,저자,발행자,자료실,대출횟수
1,BPG000239319,J 219-그239-30,"그리스 로마 신화. 30, 기나긴 모험 그 끝의 이야기",박시연 글 ; 최우빈 그림,북이십일아울북,[달성]어린이자료실,12
2,BPG000188450,J 475-곰225ㅁ-2,미생물 세계에서 살아남기. 2,곰돌이 co. 글 ; 한현동 그림 ; 연합뉴스 ...[등]사진,미래엔 아이세움,[달성]어린이자료실,11
3,BPG000198559,J 594-조72ㅇ-2,"요리스타 청. 2, 아이스크림 대결",조재호 글 ; 은하수 그림,주니어김영사,[달성]어린이자료실,10
4,BPG000203012,J 813.8-반79-2,반지의 비밀일기. 2,종이 원작 ; 대원키즈 편집부 편,대원씨아이,[달성]어린이자료실,10
5,BPG000201625,J 407-곰225ㄴ-42,"내일은 실험왕 : 본격 대결 과학실험 만화. 42, 중력과 무중력",스토리 a. 글 ; 홍종현 그림,아이세움,[달성]어린이자료실,10
6,BPG000198464,J 508-도839,(도티&잠뜰) 빅데이터 : 빅브라더를 잡아라!,글: 전판교  ; 그림: 최우빈,대원키즈 ,[달성]어린이자료실,10""".split("\n")

table_title = book_table_list[0].split(',')
book_data_row_1 = book_table_list[1].split(',')
book_data_row_2 = book_table_list[2].split(',')
book_data_row_3 = book_table_list[3].split(',')
book_data_row_4 = book_table_list[4].split(',')
book_data_row_5 = book_table_list[5].split(',')
book_data_row_6 = book_table_list[6].split(',')
# 1, 3, 5 는 하나의 셀 안에 ,로 나뉘어져있음, 그럴 경우엔 ""(큰따옴표)로 묶어줄 필요 있음,
# 추후에 조건문과 반복문을 배우게 되면 함수 처리가 가능하지만, 우리가 배운 방식으로도 해결할 수 있습니다.
temp_title = book_data_row_1[3] + book_data_row_1[4]
del book_data_row_1[4] # 뒤쪽 인덱스부터 지워야 의도대로 삭제할 수 있습니다
del book_data_row_1[3]
book_data_row_1.insert(3, temp_title) # 이하는 마찬가지 방식으로..


temp_title = book_data_row_3[3] + book_data_row_3[4]
del book_data_row_3[4]
del book_data_row_3[3]
book_data_row_3.insert(3, temp_title)

temp_title = book_data_row_5[3] + book_data_row_5[4]
del book_data_row_5[4]
del book_data_row_5[3]
book_data_row_5.insert(3, temp_title)



# 문제 3-1) 다음 데이터를 리스트[딕셔너리] 구조로 만드세요
high_rank_rental_book_list = []
book_1 = {
    table_title[1]: book_data_row_1[1],
    table_title[2]: book_data_row_1[2],
    table_title[3]: book_data_row_1[3],
    table_title[4]: book_data_row_1[4],
    table_title[5]: book_data_row_1[5],
    table_title[6]: book_data_row_1[6],
}
book_2 = {
    table_title[1]: book_data_row_2[1],
    table_title[2]: book_data_row_2[2],
    table_title[3]: book_data_row_2[3],
    table_title[4]: book_data_row_2[4],
    table_title[5]: book_data_row_2[5],
    table_title[6]: book_data_row_2[6],
}
book_3 = {
    table_title[1]: book_data_row_3[1],
    table_title[2]: book_data_row_3[2],
    table_title[3]: book_data_row_3[3],
    table_title[4]: book_data_row_3[4],
    table_title[5]: book_data_row_3[5],
    table_title[6]: book_data_row_3[6],
}
book_4 = {
    table_title[1]: book_data_row_4[1],
    table_title[2]: book_data_row_4[2],
    table_title[3]: book_data_row_4[3],
    table_title[4]: book_data_row_4[4],
    table_title[5]: book_data_row_4[5],
    table_title[6]: book_data_row_4[6],
}
book_5 = {
    table_title[1]: book_data_row_5[1],
    table_title[2]: book_data_row_5[2],
    table_title[3]: book_data_row_5[3],
    table_title[4]: book_data_row_5[4],
    table_title[5]: book_data_row_5[5],
    table_title[6]: book_data_row_5[6],
}
book_6 = {
    table_title[1]: book_data_row_6[1],
    table_title[2]: book_data_row_6[2],
    table_title[3]: book_data_row_6[3],
    table_title[4]: book_data_row_6[4],
    table_title[5]: book_data_row_6[5],
    table_title[6]: book_data_row_6[6],
}
high_rank_rental_book_list.append(book_1)
high_rank_rental_book_list.append(book_2)
high_rank_rental_book_list.append(book_3)
high_rank_rental_book_list.append(book_4)
high_rank_rental_book_list.append(book_5)
high_rank_rental_book_list.append(book_6)

print(high_rank_rental_book_list)
# 문제 3-2) 만든 리스트에서 인기 순위 3위 이내의 출판사를 조합으로 만들어 출력하세요.
high_rank_publisher = {high_rank_rental_book_list[0]['발행자'],
                       high_rank_rental_book_list[1]['발행자'],
                       high_rank_rental_book_list[2]['발행자']}
print(high_rank_publisher)
 
[{'등록번호': 'BPG000239319', '청구기호': 'J 219-그239-30', '서명': '"그리스 로마 신화. 30 기나긴 모험 그 끝의 이야기"', '저자': '박시연 글 ; 최우빈 그림', '발행자': '북이십일아울북', '자료실': '[달성]어린이자료실'}, {'등록번호': 'BPG000188450', '청구기호': 'J 475-곰225ㅁ-2', '서명': '미생물 세계에서 살아남기. 2', '저자': '곰돌이 co. 글 ; 한현동 그림 ; 연합뉴스 ...[등]사진', '발행자': '미래엔 아이세움', '자료실': '[달성]어린이자료실'}, {'등록번호': 'BPG000198559', '청구기호': 'J 594-조72ㅇ-2', '서명': '"요리스타 청. 2 아이스크림 대결"', '저자': '조재호 글 ; 은하수 그림', '발행자': '주니어김영사', '자료실': '[달성]어린이자료실'}, {'등록번호': 'BPG000203012', '청구기호': 'J 813.8-반79-2', '서명': '반지의 비밀일기. 2', '저자': '종이 원작 ; 대원키즈 편집부 편', '발행자': '대원씨아이', '자료실': '[달성]어린이자료실'}, {'등록번호': 'BPG000201625', '청구기호': 'J 407-곰225ㄴ-42', '서명': '"내일은 실험왕 : 본격 대결 과학실험 만화. 42 중력과 무중력"', '저자': '스토리 a. 글 ; 홍종현 그림', '발행자': '아이세움', '자료실': '[달성]어린이자료실'}, {'등록번호': 'BPG000198464', '청구기호': 'J 508-도839', '서명': '(도티&잠뜰) 빅데이터 : 빅브라더를 잡아라!', '저자': '글: 전판교  ; 그림: 최우빈', '발행자': '대원키즈 ', '자료실': '[달성]어린이자료실'}]
{'주니어김영사', '미래엔 아이세움', '북이십일아울북'}
 

감사합니다.

 

 

개별 공부한 내용도 조금 섞여있으니, 이해가 안되더라도 그런갑다하고 넘어가주세요 :D

튜플 Tuple

# 튜플은 + 연산 사용 가능, 추가 시 메모리 주소가 바뀐다.

tu = (1, 2, 3) + (4,)
print(id(tu))
tu += (5,)
print(id(tu))
 
2097661672192
2097661668912
 

# 같은 값을 갖는 튜플은 id가 어떨까?

tu_name_1 = (1,)
tu_name_2 = (1,)
print("id(tu_name_1) :", id(tu_name_1))
print("id(tu_name_2) :", id(tu_name_2))  # 같은 주소값을 가진다!
tu = 1, 2  # 이런 식으로도 선언 가능하지만 비추천
print(id(tu))
 
id(tu_name_1) : 2097659620336
id(tu_name_2) : 2097659620336
2097660591040
 

# 튜플 곱연산 가능

tu_2 = (1, 2) * 10
print(tu_2)
 
(1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2)
 

# 다음과 같이도 선언 가능

tu_3 = tuple(range(1, 11))
print(tu_3)
 
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
 

# slicing 가능

print(tu_3[1])
print(tu_3[0: 5: 2])
 
2
(1, 3, 5)
 

# 이중 배열도 사용 가능, 접근 가능

tu_4 = (1, 2, (2,), (4, 5))
print(tu_4[3][1])
 
5
 

 

# 다양한 자료형 혼입 가능, list까지도!

str_tu = ('hi', 'mike',)
str_tu += (1,)
str_tu_2 = ('hi', 1, 2.0)
str_tu_3 = (['hello', 'jason'], 2)
print(str_tu)
print(str_tu_2)
print(str_tu_3)
 
('hi', 'mike', 1)
('hi', 1, 2.0)
(['hello', 'jason'], 2)
 

 

# 튜플 내 리스트에 자료 추가가 가능할까? 가능! tuple id 자체도 변경 안됨.

# 다만, tuple 내 list는 굳이 사용할 목적이 없음!

list_tu = (['1', 2, 3, 4], 5, 6, 7)
print('before add "list_tu" id: ', id(list_tu))
list_tu[0].append(0)
print('after add "list_tu" id: ', id(list_tu))
print(list_tu)
 
before add "list_tu" id:  2097662113520
after add "list_tu" id:  2097662113520
(['1', 2, 3, 4, 0], 5, 6, 7)
 

 

# Tuple 수정, 요소 일부만 삭제 불가

str_tu = ('hi', 'mike')
# // str_tu[0] = str_tu[0].replace('hi', 'hello') # error!
print(str_tu)
print(str_tu[0][1])
tu_del = (1, 2, 3,)
# // del tu[1] error!
del tu #는 가능
 
('hi', 'mike')
i
 

# 길이 구하는 방식은 동일

tu_len = tuple(range(10))
print('len(tu_len): ', len(tu_len))
 
len(tu_len):  10
 

 

 

딕셔너리 Dictionary

# Dictionary 딕셔너리 선언

dict_ = dict()
dict_another_declare = {} # 이런 선언 방식은 추천하지 않음, 집합과 선언방식이 동일하여 가독성 해침
dict_structure = {'김윤재': 1, '송준혁': 2, '김혜빈': 3}
print(dict_structure)
 
{'김윤재': 1, '송준혁': 2, '김혜빈': 3}
 

 

 

# 값 가져오기

print(dict_structure['김윤재'])
print(dict_structure.get('김윤재'))
print(dict_structure.get('박광현'))  # None 출력
 
1
1
None
 

 

 

# 값 추가 / 변경하기

dict_structure['주혜인'] = 4
dict_structure['김윤재'] = 0
print(dict_structure)
 
{'김윤재': 0, '송준혁': 2, '김혜빈': 3, '주혜인': 4}
 

 

# 값 삭제하기, 대신 없는 것을 삭제하려면 error

del dict_structure['박광현'] # // error!!
print(dict_structure)
 
Traceback (most recent call last):
  File "C:\Users\KDT107\Desktop\learn_py\lecture.py", line 87, in <module>
    del dict_structure['박광현']
        ~~~~~~~~~~~~~~^^^^^^^
KeyError: '박광현'
 

 

 

# for 지원은 해준다 : for문으로 value값 가져오기

for i in dict_structure:
    print(i)
 
0
2
3
4
 

 

 

# dict 로 key값으로 tuple도 가능 (주소값이 안바뀌는 속성을 가진 얘들만 key가능해서)

dict_tuple = {(1,): 'hello'}
print(dict_tuple)
 
{(1,): 'hello'}
 

 

 

# dict 복잡 구조 선언

# key 쌍 갖고 오기, type은 dict_keys, indexing은 불가

student = {
    'name': 'park',
    'age': 31,
    'address': 'gwang-ju',
    'cell-1': '010-',
    'cell-2': 'null',
    'enable': {'1st': 'python',
               '2nd': 'java',
               '3rd': 'C'},
    'tuple_type': (1, 2,),
    'set_type': set(range(10)),
    'subject': ['nursing', 's/w programming'],
    'tryNone': None
}

print(student.keys())
print(student.values())
print(student.items())
print(type(student.keys()))
print(type(student.values()))
print(type(student.items()))
# print(student.keys()[0])  # error!
 
dict_keys(['name', 'age', 'address', 'cell-1', 'cell-2', 'enable', 'tuple_type', 'set_type', 'subject', 'tryNone'])
dict_values(['park', 31, 'gwang-ju', '010-', 'null', {'1st': 'python', '2nd': 'java', '3rd': 'C'}, (1, 2), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ['nursing', 's/w programming'], None])
dict_items([('name', 'park'), ('age', 31), ('address', 'gwang-ju'), ('cell-1', '010-'), ('cell-2', 'null'), ('enable', {'1st': 'python', '2nd': 'java', '3rd': 'C'}), ('tuple_type', (1, 2)), ('set_type', {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), ('subject', ['nursing', 's/w programming']), ('tryNone', None)])
<class 'dict_keys'>
<class 'dict_values'>
<class 'dict_items'>
 

 

 

# for문 응용

print('[for test start]')
for k, v in student.items():
    print('key : ', k)
    print('value : ', v)
print('[for test end]')
 
[for test start]
key :  name
value :  park
key :  age
value :  31
key :  address
value :  gwang-ju
key :  cell-1
value :  010-
key :  cell-2
value :  null
key :  enable
value :  {'1st': 'python', '2nd': 'java', '3rd': 'C'}
...
key :  tryNone
value :  None
[for test end]
 

 

# 지우기

student.clear()
print(student)  # {} 로 출력됨
del student
# print(student)  # error!
 
{}
 

 

# dict update로 element 추가 하는 방식

dict_ = {'sample': 'apple'}
dict_.update({'greeting': 'hello'})
print(dict_)
 
{'sample': 'apple', 'greeting': 'hello'}
 

 

 

 

# dict pop 쓰기

# data = dict_.pop() # error!
# data = dict_.pop(1)  # error!
data = dict_.pop('sample')
print(data)  # value만 리턴!
 
apple
 

 

 

 

# dict fromkeys : 자동으로 키 값 쌍 벨류를 만들어준다.

x = ('key1', 'key2', 'key3')
y = 0
example_dict = dict.fromkeys(x, y)  # 또는
example_dict_2 = dict.fromkeys(x, -1)
print(example_dict)
print(example_dict_2)
 
{'key1': 0, 'key2': 0, 'key3': 0}
{'key1': -1, 'key2': -1, 'key3': -1}
 
 

집합 Set

# 집합 set 선언

s_list = [1, 7, 1, 3, 4, 5, 6, 3, 2, 1]
s = set(s_list)
s_direct = {3, 5, 6}
# s_range = {[range(10)],} # error, 중괄호 안에 리스트 사용 불가
s_range = set(range(10))  # 대신 set()함수 안에는 사용 가능함
print('s :', s)
print('s_direct :', s_direct)
print('s_range :', s_range)
 
s : {1, 2, 3, 4, 5, 6, 7}
s_direct : {3, 5, 6}
s_range : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 

# 집합 str 사용

s_string = 'Hello! Every body!'
s_str = set(s_string)
print(s_str)
 
{'b', 'H', 'e', 'r', ' ', 'E', 'y', 'l', 'o', 'v', '!', 'd'}
 

 

# set 함수 [ add() ] : set 요소 추가

# set 함수 [ update() ] : 다른 iterable obj 추가할 때

s_range = set(range(10))
# s_range += {10} # error!  plus 연산자 안됨
# s_range.update(11) # error! update는 iterable만 가능!
s_range.add(10)
s_range.update((10,))
print(s_range)
 
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
 

# set 함수 [ copy() ] : 복사하기

s_range = set(range(10))
another_set = set.copy(s_range)
s_range = s_range | {11, 12,} # 합집합, s_range 정보를 변경함
another_set2 = s_range.copy()  # 둘다 사용 가능
print('s_range:', s_range)
print('another_set:', another_set)
print('another_set2:', another_set2)
 
s_range: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
another_set: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
another_set2: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
 

# set 함수 [ difference() ], 연산자 ' - ' : 차집합 계산하기 ※ 피연산자 순서 유의!

differ_set = set('abcde')
vowel_set = set('aeiou')
differ_vowel_set = differ_set - vowel_set  # 동일
print(differ_set.difference(vowel_set))  # {'c', 'd', 'b'}
 
{'b', 'd', 'c'}
 

 

# set 함수 [ discard() ] : 특정 요소 버리기

discard_set = {'a', 'b', 'c'}
discard_set.discard('b')
print(discard_set)
 
{'a', 'c'}
 

 

 

 

# set 함수 [ intersection() ] : 교집합 찾기

odd_set = {1, 3, 5, 7, 9}
even_set = {0, 2, 4, 6, 8}
result_set = odd_set.intersection(even_set)
result_set2 = odd_set & even_set  # 동일
print("result_set :", result_set)  # set() 공집합
print("result_set2 :", result_set2)  # set() 공집합
 
result_set : set()
result_set2 : set()
 

 

 

# set 함수 [ union() ] : 합집합 리턴

result_set = odd_set.union(even_set)
result_set2 = odd_set | even_set
print("result_set :", result_set)  # {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
print("result_set2 :", result_set2)
 
result_set : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
result_set2 : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
 

# set 함수 [ isdisjoint() ] : 교집합이 null인지 확인

print(odd_set.isdisjoint(even_set))  # True
 
True
 

# set 함수 [ issubset() ] : 부분집합인지 확인

alpha_set = set("abcdefghijklmnopqrstuvwxyz")
hello_set = set("hello")
print(alpha_set.issubset(hello_set))  # False
print(hello_set.issubset(alpha_set))  # True
 
False
True
 

# set 함수 [ issuperset() ] : 모집합인지 확인

print(alpha_set.issuperset(hello_set))  # True
print(hello_set.issuperset(alpha_set))  # False
 
True
False
 

# set 함수 [ pop() ] : 하나 요소 추출

pop_set = set(range(10))  # {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
result_element = pop_set.pop()
print(result_element)  # 0
result_element = pop_set.pop()
print(result_element)  # 1
result_element = pop_set.pop()
print(result_element)  # 2
 
0
1
2
 

 

# set 함수 [ remove() ] : 요소 제거

rm_set = set(range(10))
rm_set.remove(1) # 1개만 인자를 받음
print("rm_set :", rm_set)  # {0, 2, 3, 4, 5, 6, 7, 8, 9}
// # rm_set.remove((4,5,6)) # error! iterable 타입 사용 불가
rm_set = rm_set - {4, 5, 6} # 차집합으로 응용 가능
print("rm_set :", rm_set)  # {0, 2, 3, 7, 8, 9}
 
rm_set : {0, 2, 3, 4, 5, 6, 7, 8, 9}
rm_set : {0, 2, 3, 7, 8, 9}
 

 

# comprehension으로 iterable 선언 하기

list_ = [n ** 2 for n in range(10)]
print(list_)

random_list = [(random.randint(2, 30)) ** 3 for n in range(10)]
print(random_list)

even_set = set(x for x in range(101) if x % 2 == 0)
print(even_set)

subjects = ['english', 'korea', 'science', 'social']
scores = [80, 100, 50, 20]
midterm_score_dict = {key: value for key, value in zip(subjects, scores)}
print(midterm_score_dict)

gen = (math.sqrt(x) for x in range(10, 13))
print(gen)
print(next(gen))
print(next(gen))
print(next(gen))
# print(next(gen)) #error! 3개만 선언했으므로!

alpha_list = ['a', 'b', 'c', 'd']
num_list = [1, 2, 3, 4]
cartesian_multi_list = [[a, n] for a in alpha_list for n in num_list]
print("cartesian_multi_list :", cartesian_multi_list)

matrix_1 = [
    [1, 2, 3],
    [4, 5, 6],
]

flatten = [e for r in matrix_1 for e in r]
print("flatten :", flatten)
 
[0, 1, 4, 9, 16]
[9261, 64, 512, 64, 27000]
{0, 2, 4}
{'english': 80, 'korea': 100, 'science': 50, 'social': 20}
<generator object <genexpr> at 0x000001B26112ADC0>
3.1622776601683795
3.3166247903554
3.4641016151377544
cartesian_multi_list : [['a', 1], ['a', 2], ['a', 3], ['b', 1], ['b', 2], ['b', 3], ['c', 1], ['c', 2], ['c', 3]]
flatten : [1, 2, 3, 4, 5, 6]
 

 

# 얕은 복사 응용

# list 기본 확보되는 용량이 64byte라는걸 알 수 있다.

test_list_1 = []
test_list_2 = []
test_list_3 = []
test_list_4 = []
print(id(test_list_1))
print(id(test_list_2))
print(id(test_list_3))
print(id(test_list_4))

test_list_1.append(test_list_2)
test_list_1.append(test_list_2)
test_list_1.append(test_list_2)
test_list_2.append(2) 
print(test_list_1)
 
1865644838272
1865644838208
1865644838336
1865644838400
[[2], [2], [2]]
 

 

감사합니다.

 

 

 

★ 왜 행렬?

행렬은 영어로 Matrix, 왜 쓰는지 찾아보면, 연립 방적식의 해를 구하는데 좋다.. 어쩔때 좋다.. 등등 쓰는 이유가 많다. 하지만, 우리가 처한 상황에서 이해하기 좋으려면, 매우 많이 쓰고 달리 대체할 도구가 없기 때문이다.

 

0 0 1 0 0

0 1 0 1 0

1 0 0 0 1

0 1 0 1 0

0 0 1 0 0

 

단순한 5x5 행렬이다. 무엇으로 보이는가? just text?

그러면 이번엔 칸에 색을 칠해보자.

 

  • 0열 선택0열 다음에 열 추가
  • 1열 선택1열 다음에 열 추가
  • 2열 선택2열 다음에 열 추가
  • 3열 선택3열 다음에 열 추가
  • 4열 선택4열 다음에 열 추가
  • 0행 선택0행 다음에 행 추가
  • 1행 선택1행 다음에 행 추가
  • 2행 선택2행 다음에 행 추가
  • 3행 선택3행 다음에 행 추가
  • 4행 선택4행 다음에 행 추가

























모양이 나타난다.

 

그러면, 0과 1 대신에 크기의 개념을 집어넣으면? 어떻게 표현할 수 있을까?

 

0 0 3 0 0

0 2 0 5 0

2 0 1 2 3

0 5 0 2 0

0 0 3 0 0

 


























아 그러면, 지금은 불분명하니까 적당히 2 이하는 0처리, 3이상은 5처리 해줘.

 

0 0 5 0 0

0 0 0 5 0

0 0 0 0 5

0 5 0 0 0

0 0 5 0 0

 

























이렇게 되면, 적당히 의미 있는 값들과 의미 없는 값들이 구분되어진다.

 

포토샵에서 마스킹(masking), 샤픈(sharpen)이나 블러(blur)를 써본 사람은 어떤 느낌인지 올 것이다.

 

여기 영역만 쓰던가, 적당히 주변값과 대비되어지거나, 주변 값과 비슷해지거나..

 

이처럼 이미지와 행렬은 큰 연관이 있다.

 

비단 이미지 뿐만이 아니다. 영상, 소리, 전기 신호, 지리, 위치 등 표현할 수 있는 수단이 많다.

 

추후에 관심이 있는 사람은 '행렬과 차원의 관계'에 대해서 더 알아보길 권한다.

 

 

 Numpy를 쓰는 이유 :

Python 기본 제공 list 대비 numerical 자료에 대해 빠르고 효과적으로 넓게 사용가능한 배열을 만들어줌. 또한, python의 list는 다양한 자료형을 받아들이지만, numpy는 모두 같은 타입(실수형만 또는 정수형만)의 배열을 만들고, 그 타입을 제한한다. (값 타입을 제한함으로써 속도와 안정성이 올라감)

 

■ 만들기 과정

 

※ 직접 list를 넣을 때

 

※ 0 또는 특정 수로 채우고, 사이즈 결정만 하고 싶을 때

np.zeros(2)

np.ones(2)

np.empty(2)

 

※ 특정 범위 수로 채울 건데, 내가 정한 크기 기준으로 값을 넣고 싶을 때

>> np.arange(2, 9, 2)

array([2, 4, 6, 8])

 

※ 특정 범위 수로 채울 건데, 내가 정한 개수 기준으로 값을 넣고 싶을 때

>> np.linspace(0, 10, num=5)

array([ 0. , 2.5, 5. , 7.5, 10. ])

 

 

 

※ 데이터 자료형 크기 변경하고 싶을 때

x = np.ones(2, dtype=np.int64) int16, int32, int64, float16, float32, float64

array([1, 1])

print(x.dtype) #int64

 

 

■ 데이터 변경

※ 오름차순 정렬할 때

arr = np.array([2, 1, 5, 3, 7, 4, 6, 8])
np.sort(arr)
 
[1, 2, 3, 4, 5, 6, 7, 8]
 

 

 

※ 행렬 연결(extend 개념)

a = np.array([1, 2, 3, 4])
b = np.array([5, 6, 7, 8])
np.concatenate((a, b))
 
[1, 2, 3, 4, 5, 6, 7, 8]
 

결과 : array([1, 2, 3, 4, 5, 6, 7, 8])

 

※ 행렬 상태 확인

array_example = np.array([[[0, 1, 2, 3],
[4, 5, 6, 7]],
[[0, 1, 2, 3],
[4, 5, 6, 7]],
[[0 ,1 ,2, 3],
[4, 5, 6, 7]]])
 
>> array_example.ndim
3
>> array_example.size
24
>> array_example.shape
(3, 2, 4)
 

 

※ 행렬 형태 변경(convert)

a = np.arange(6)
b = a.reshape(3, 2)
 
 [[0 1]
 [2 3]
 [4 5]]
 

 

※ 행 추가, 열 추가

a = np.array([1,2,3,4,5,6])
a2 = a[np.newaxis, :] # 행추가
a3 = a[:, np.newaxis] # 열추가
print(a.shape)
print(a2)
print(a2.shape)
print(a3)
print(a3.shape)
 
(6,)
[[1 2 3 4 5 6]]
(1, 6)
[[1]
 [2]
 [3]
 [4]
 [5]
 [6]]
(6, 1)
 

추후 공부하는대로 더 정리하면 올리겠습니다.

 

감사합니다.

 

+ Recent posts