상세 컨텐츠

본문 제목

피로그래밍14기: day6 <PYTHON - class>

환 codes web/PYTHON

by 퍼블리셔환 2021. 1. 14. 09:16

본문

 

리버풀의 전설적인 감독인 빌 샹클리이다. 폼은 일시적이지만 클래스는 영원하다는 명언을 남겼다. 이번 게시물은 영원하다는 그 클래스에 대한 내용이다. 

 


1. 파이썬과 객체지향 프로그래밍

많은 사람들이 파이썬의 매우 큰 특징 중 하나로 객체지향 언어라는 점을 뽑는다. 사실 나도 이 객체지향이라는 말을 정확히 이해하고 있다고 말할수는 없기 때문에 구글에 객체지향을 검색해 보았다.

 

이 말조차 어렵다. 객체들이 서로 상호작용을 한다는데 그럼 이건 과연 무슨 뜻일까? 애초에 객체란 무엇일까? 참고) 이후부터 제가 설명할 내용은 정확한 사실이 아닐 수 있으며, 초보자의 입장에서 스스로 이해한 바를 최대한 쉽게 풀어쓴 내용입니다. 만약 틀린 점이 있으면 알려주세요 :)

우선 객체, object는 데이터의 모임이다. 전류가 전자의 흐름이듯, 프로그래밍은 데이터들의 흐름이다. 이때 이 데이터들을 모으고 가공해서 의미가 있는 상태로 만든 것이 객체, 즉 object이다.


그렇다면 객체지향 프로그래밍은 무엇일까? 객체지향 프로그래밍은 이런 object들이 서로 의미를 가지고 연결되어 상호작용하게끔 하는 프로그래밍 방법이다. 이렇게만 말하면 잘 와닿지 않지만, 이렇게 object들을 이용해서 프로그래밍을 하면 한눈에 보기도 쉽고 연결되어 있는 내용을 수정하기에도 쉽다는 장점이 있다. 처음 class나 object의 개념을 접한 사람들에게는 이해가 어려울 수도 있다. 그래서 곧이어 class를 설명하면서 object에 대해서 더 설명하겠다.

2. 파이썬과 Class

Class는 앞서 설명한 객체지향 프로그래밍을 가능하게 해 주는 중요한 요소이며, 이 자체로 object라고 볼 수 있다. 즉, object는 데이터의 모음이기 때문에 class역시 데이터들의 모음이라고 생각하면 쉽다.

예를 들어, 야구선수 육성 게임을 한다고 생각해보자. 아래 예시는 LG Twins의 에이스, 케이시 켈리 선수의 마구마구 인게임 모습이다.

 

위 사진에서 볼 수 있듯이, 캐릭터에는 이 캐릭터를 설명해주는 여러 능력치가 함께 붙어있다. 체력, 제구력, 구속, 포심, 체인지업... 이러한 항목들 옆에 써있는 수치들이 "케이시 켈리"라는 선수를 나타내고 있다.

 

마찬가지로, 파이썬의 class는 캐릭터라고 볼 수 있다. 캐릭터의 능력치들이 캐릭터를 설명하는 것 처럼, class 안에 있는 데이터들이 class를 설명해주는 것이다. 만약 내가 켈리 선수를 잘 육성해서 체력이 10만큼 오른다면, 나는 켈리라는 class 안에 접근해서 체력이라는 데이터 속성을 + 10 와 같이 수정할 수 있는 것이다.


그런데 위 켈리는 능력치만 있는 것은 아니다. 켈리는 야구게임의 투수 캐릭터이기 때문에 투구를 할 수 있다. 다음과 같이 캐릭터의 행위를 정의하는 것도 class의 특징인데, 이를 class의 method라고 한다. 다시 말해, class는 데이터의 모음(속성, attribute)과 데이터의 상호작용(메서드, method)이라고 볼 수 있다. 따라서 켈리에게 공을 던지게 하려면 켈리라는 class에 접근해서 투구라는 method를 호출하면 된다.

그럼 이제 위에 있는 켈리라는 선수를 파이썬 class로 나타내보자. 다음과 같이 표현할 수 있다.

class Player:
    name = ''
    strength = 0
    accuracy = 0
    speed = 0

kelly = Player()
kelly.name = "Casey Kelly"
kelly.strength = 90
kelly.accuracy = 97
kelly.speed = 90

다음과 같이 Player라는 class를 만들고, class의 attribute로 name, strength, accuracy, speed를 추가했다. 보통 class는 맨 앞글자는 대문자로 사용한다. 이 Player라는 class는 아직까지는 케이시 켈리 선수를 지칭하는 것이 아니고 전반적인 야구게임의 캐릭터를 나타내기 때문에 처음에는 모든 속성에 빈 값 혹은 0을 넣었다.


그런 뒤에, kelly라는 선수에 해당하는 데이터를 넣을 것이기 때문에 kelly = Player()라고 빈 클래스를 하나 만들어서 이름을 kelly라고 할당했다. 그리고 난 뒤 kelly라는 class 안에 있는 속성에 접근하기 위해서는 kelly.name 과 같이 점을 찍고 내부에 있는 속성을 호출한다. 보이는 것과 같이 모든 요소들을 입력하면 kelly라는 class가 생성된 것이다. 우리가 정확히 만들었는지 알기 위해서 한번 print로 찍어보자.

print(kelly.name)

입력한 대로, Casy Kelly라는 값이 정확하게 출력되는 것을 확인할 수 있다.
만약 체력이 10 올라서 해당 내용을 반영하고 싶으면 다음과 같이 수정하면 된다. 호출까지 해보자.

kelly.strength += 10
print(kelly.strength)

입력한 90에서 10이 추가된 100이 찍힌 것을 확인할 수 있다.

그러면 앞선 설명대로, class에 attribute 말고도 method도 출력할 수 있다고 했으니 이 내용도 확인해보자.

 

method로 투구를 했으니, pitch라는 method를 넣고, 공을 던질때마다 체력이 줄 것이니 strength에서 -5씩 빼는 함수를 작성하자. 그리고 투구 내용을 우리가 볼 수 있게 공의 정확도와 속도를 출력하면 좋을 것이다.

class Player:
    name = ''
    strength = 0
    accuracy = 0
    speed = 0

    def pitch(self):
        self.strength -= 5
        print(f'{self.name}의 투구 내용')
        print(f'제구: {self.accuracy} 구속: {self.speed}')
        print(f'남은 체력: {self.strength}')


kelly = Player()
kelly.name = "Casey Kelly"
kelly.strength = 90
kelly.accuracy = 97
kelly.speed = 90

kelly.strength += 10

kelly.pitch()

위 코드에서 보이는 것 처럼 class의 method는 일반 함수와 같이 def 로 선언하지만, 괄호 안에 self를 넣어서 자기 자신을 정의 한다. 이 이름은 함수 내에서 self.xxx의 꼴로 사용하기 위함이다. 그리고 함수를 정의하는 나머지 내용은 일반적인 함수와 같다.

3. 피로그래밍 코딩테스트

피로그래밍 동아리에 지원할 때 코딩테스트가 있었다. 파이썬으로 class를 이용해서 코딩테스트를 풀고 제출하는 과제였는데, 그 때 당시에는 파이썬을 할 줄 몰랐어서 열심히 자료를 인터넷에서 찾아가면서 풀었다. 정말 눈물나는 고군분투였다. c언어만 조금 할 줄 알고 파이썬을 몰랐어서 indent로 코드와 함수를 나눈다는게 도저히 이해가 가지 않았다.

 

어찌어찌 문제를 풀어서 제출했고, 결국 통과까지 해서 피로그래밍 동아리에서 정말 좋은 경험을 쌓긴 했는데... 내가 정말 이 문제를 잘 푼 것인가? 라는 의문이 생겼다. 그래서 이제 class에 대해서 조금 아는 입장으로 다시 문제를 풀어보려고 한다. 눈물나는 고군분투기를 보고 싶은 사람은 아래 게시글을 참고하면 된다.

 

 

피로그래밍 14기: day0 <피로그래밍 지원, 합격>

지난 2020년 여름방학부터 웹을 코딩하는 법에 대해서 배우고 싶다는 생각이 들었습니다. 마침 좋은 기회에 에브리타임에서 피로그래밍 14기 모집 공고를 확인했고, 처음 봤을 때 부터 꼭 이 동아

nwy1996.tistory.com


코딩테스트 문제는 다음과 같다.

1. 전세계적으로 퍼져 있는 코로나를 치료할 치료제가 개발되었고, 각 나라들에게 백신을 투여하여 완치율을 높이는 게임을 만들어라.
2. 백신1, 백신2, 백신3이 각각 25%, 50%, 100%의 치료율을 가지고 있고, 한국, 미국, 일본, 독일, 중국의 5개 나라에 투여한다. 각 나라는 이미 일정 확진자 수를 보유하고 있다. (이 확진자 수도 공개되었고, 정확한 수치는 기억이 안납니다.)
3. 총 5라운드 동안 한 라운드별로 한 개의 백신을 한 개의 나라에 투여하고, 첫 번째 라운드를 제외하고는 어떤 백신이 어떤 나라에 투입될지는 랜덤으로 정한다.
4. 해당 라운드에서 백신이 투여되는 나라를 제외하고는 확진자가 30%씩 증가한다. (원래 15%였는데, 만약 15%면 게임 종료 조건이 만족이 안되길래 임의로 30%로 바꿨다.)
5. 만약 5라운드가 끝나기 전에 확진자가 나라의 총 인구수를 넘으면 게임을 종료한다.
6. 게임이 종료되면 나라들의 총 확진자 수별로 1~5등까지 정렬하고 결과표를 보여준다.

import math
import random


class Country:
    name = ''
    total = 0
    infect = 0


korea = Country()
korea.name = '한국'
korea.total = 1500
korea.infect = 300

china = Country()
china.name = '중국'
china.total = 3000
china.infect = 800

japan = Country()
japan.name = '일본'
japan.total = 2000
japan.infect = 500

us = Country()
us.name = '미국'
us.total = 2500
us.infect = 750

germany = Country()
germany.name = '독일'
germany.total = 2200
germany.infect = 1000

vac1 = ['백신 1', 0.25]
vac2 = ['백신 2', 0.5]
vac3 = ['백신 3', 1.0]

vaclist = [vac1, vac2, vac3]
counlist = [korea, china, japan, us, germany]

checkbreak = 0
infectsum = 0
curesum = 0
curedlist = []


def play():
    print('-------------------------------------------------------------')
    print('                    코로나 종식 게임')
    print('-------------------------------------------------------------')
    print('1. 백신 정보')
    print('2. 감염된 국가 정보')
    print('3. 게임 시작')
    print('4. 게임 종료')
    print('-------------------------------------------------------------')
    playnum = int(input())
    if playnum == 3:
        play3()


def play3():
    print('\n                  게임 시작!\n')
    print('백신 번호(1~3)과 국가 번호(1~5)를 차례로 입력하세요')
    vacnum, counum = input().split()
    vacnum = int(vacnum)
    counum = int(counum)

    for i in range(5):
        print(f'\n                    **Round {i+1}**\n')
        print(
            f'선택된 백신 : {vaclist[vacnum-1][0]}, 치료율 : {vaclist[vacnum-1][1]*100}%')
        print(
            f'선택된 국가 : {counlist[counum-1].name}, 인구수 : {counlist[counum-1].total}명, 감염자수 : {counlist[counum-1].infect}명')

        infect(counum)
        cure(vacnum, counum)

        print('-------------------------------------------------------------')
        print(f'{i+1}차 백신 투여 후 감염된 나라에 대한 정보')
        printstatus()
        checkstatus()

        if checkbreak != 0:
            break

        vacnum = random.randrange(1, 4)
        counum = random.randrange(1, 6)

    print('\n                    **게임 종료!**')
    gameresult()


def infect(counum):
    global infectsum
    for i in range(5):
        if i != counum-1:
            country = counlist[i]
            country.infect = math.floor(country.infect * 1.30)
            infectsum += country.infect * 0.3
        else:
            pass


def cure(vacnum, counum):
    global curesum
    rate = vaclist[vacnum-1][1]
    country = counlist[counum-1]
    country.infect = math.floor(country.infect * (1 - rate))
    curesum += country.infect * rate


def printstatus():
    global curedlist
    for i in range(5):
        if counlist[i].infect == 0 and i not in curedlist:
            curedlist.append(i)

    for i in curedlist:
        print(f'완치된 국가 : {counlist[i].name}')

    print('-------------------------------------------------------------')

    for i in range(5):
        if i not in curedlist:
            print(f'감염국가 : {counlist[i].name}')
            print(f'인구수 : {counlist[i].total}명')
            print(f'감염자수 : {counlist[i].infect}명')


def checkstatus():
    global checkbreak
    for i in range(5):
        country = counlist[i]
        if country.infect >= country.total:
            print(f'\n{country.name}에서 감염자수가 인구수보다 많아졌습니다.')
            print('게임을 종료합니다')
            checkbreak = 1


def gameresult():
    curednames = []
    for i in curedlist:
        curednames.append(counlist[i].name)
    print('-------------------------------------------------------------')
    print('                    최종 결과')
    print('-------------------------------------------------------------')
    print(f'라운드마다 추가로 감염된 감염자 수 : {math.floor(infectsum)}명')
    print(f'백신으로 치료한 감염자 수 : {math.floor(curesum)}명')
    print(f'백신으로 완치된 국가 : {curednames}, {len(curedlist)}개 국가')

    counlist.sort(key=lambda object: object.infect)

    rank = 1
    for country in counlist:
        print(f'\n{rank}위')
        print(f'국가 : {country.name}')
        print(f'인구수 : {country.total}명')
        print(f'감염자수: {country.infect}명')
        rank += 1


play()

결과 코드는 다음과 같다.

파이썬을 조금이나마 할 줄 알게 된 덕분일까 저번보다 훨씬 수월하고 빠르게, 간결하게 코드를 작성했다. 나쁘지 않은 결과라고 생각한다. 그런데 이전에 작성했던 코드가 아주 엉망일 것이라고 생각했는데 생각보다는 그렇게 못봐줄 정도까지는 아니었다. 어쩌면 형편없는게 팩트지만 내 코드라 bias가 좀 들어있을 수도 있다.

 

지난번 코딩테스트를 제출했을 때는 class의 개념조차 모르고 사용해서 코드가 너무 길어지고 더러웠었는데, 이번에는 확실히 class 개념을 이해하고 이를 이용해서 깔끔하게 코드를 작성했다고 생각한다. 아무리 동아리 코딩테스트라지만 이렇게 말도 안되게 class를 사용했는데 어떻게 합격할 수 있었을지 조금 의문은 든다. 심지어 다시보니 틀린 부분들도 눈에 띈다. 어쩌면 class를 제대로 사용하지도 못했는데 def 랑 list로만 문제를 푼 것을 보고 노력이 가상해서 붙여준 것일지도 모르겠다.


이전에 작성했던 코드는 약 240줄 정도인데, 이번 코드는 160줄로 약 80줄 가량을 줄였다. 처음에 class를 선언하고 나라 클래스들을 만드느라 대략 30줄 정도를 할애한 것을 감안하면 사실상 100줄 이상 줄였다고 봐도 될 것이다. class에 sort를 사용해서 순서를 정렬하는 방식은 찾아보면서 처음 확인한 내용인데, 알아두면 아주 유용하겠다. 한 번에 여러 클래스들을 다중 입력받게끔 하고 싶었는데, 이 방법을 못찾은게 조금 아쉽다. 혹시 아는 사람이 있으면 알려주면 좋겠다. 전반적으로 재밌는 경험이었다.

관련글 더보기

댓글 영역