DINO NET

(23.02.01) 실습 본문

program()/파이썬

(23.02.01) 실습

2023. 2. 2. 00:59

본 게시물은 코드잇 python 토픽에 나온 실습을 사용하였습니다.

 

실습 1 - 게임 캐릭터 만들기

조건:

  • 인스턴스 변수(타입)
    • name(문자열): 캐릭터의 이름
    • hp(숫자형): 캐릭터의 체력
    • power(숫자형): 캐릭터의 공격력
  • 인스턴스 메소드
    • __init__: 사용할 모든 인스턴스 변수를 설정한다.
    • is_alive: 게임 캐릭터의 체력이 0보다 큰지(살았는지 죽었는지) 확인한다.
      • 0 초과이면 True를, 0 이하라면 False를 리턴한다.
    • get_attacked: 게임 캐릭터의 체력이 0보다 큰 상태라면 파라미터로 받은 공격력만큼 체력을 깎는다.
      • 조건:
        • is_alive 메소드를 사용해서 인스턴스가 살아있을 때만 체력을 깎는다. 이미 캐릭터가 죽었으면 죽었다는 메시지를 출력한다.
        • 남은 체력보다 공격력이 더 크면 체력(hp)을 0으로 설정한다.
    • attack: 파라미터로 받은 다른 캐릭터의 체력을 자신의 공격력만큼 깎는다.
      • 조건:
        • is_alive 메소드를 이용해서 살아있는 인스턴스만 공격을 할 수 있도록 한다.
        • get_attacked 메소드를 사용한다.
    • __str__: 게임 캐릭터의 의미있는 정보를 포함한 문자열을 리턴한다.

출력예시

Ww영훈전사wW님은 이미 죽었습니다.
Ww영훈전사wW님의 hp는 0만큼 남았습니다.
Xx지웅최고xX님의 hp는 70만큼 남았습니다.

 

실습 결과

class GameCharacter:
    # 게임 캐릭터 클래스
    def __init__(self, name, hp, power):
        # 게임 캐릭터는 속성으로 이름, hp, 공격력을 갖는다
        self.name = name
        self.hp = hp
        self.power = power

    def is_alive(self):
        # 게임 캐릭터가 살아있는지(체력이 0이 넘는지) 확인하는 메소드
        return self.hp > 0 

    def get_attacked(self, damage):
        """
        게임 캐릭터가 살아있으면 공격한 캐릭터의 공격력만큼 체력을 깎는 메소드
        조건:    
            1. 이미 캐릭터가 죽었으면 죽었다는 메시지를 출력한다
            2. 남은 체력보다 공격력이 더 크면 체력은 0이 된다.
        """
        if self.is_alive():
            self.hp = 0 if self.hp <= damage else self.hp - damage
        else:
            print("{}님은 이미 죽었습니다.".format(self.name))

    def attack(self, other_character):
        # 게임 캐릭터가 살아있으면 파라미터로 받은 다른 캐릭터의 체력을 자신의 공격력만큼 깎는다
        other_character.get_attacked(self.power)

    def __str__(self):
        # 게임 캐릭터의 의미있는 정보를 포함한 문자열을 리턴한다
        return "{}님의 hp는 {}만큼 남았습니다.".format(self.name, self.hp)

# 게임 캐릭터 인스턴스 생성                        
character_1 = GameCharacter("Ww영훈전사wW", 200, 30)
character_2 = GameCharacter("Xx지웅최고xX", 100, 50)

# 게임 캐릭터 인스턴스들 서로 공격
character_1.attack(character_2)
character_2.attack(character_1)
character_2.attack(character_1)
character_2.attack(character_1)
character_2.attack(character_1)
character_2.attack(character_1)

# 게임 캐릭터 인스턴스 출력
print(character_1)
print(character_2)

 

설명

 

객체 character

속성 (__init__에서 정의) -> name, hp, power
행동 (method로 정의) -> is_alive, attack, get_attacked
def __init__(self, name, hp, power):
#속성 정의
    self.name = name
    self.hp = hp
    self.power = power

 

1. is_alive(self): 살아있는가?

살아있다 == (self.hp > 0) / 죽었다 == (self.hp <= 0)

boolean을 이용하여 self.hp가 0을 초과하는지 그 이하인지 판별하면 됨.

def is_alive(self):
	return self.hp > 0
	#self.hp > 0 이면 True 출력 (살아있음)
	#self.hp < 0 이거나 self.hp == 0 이면 False 출력 (죽었음)

 

2. attack(self, other_character)과 get_attacked(self, damage)

공격한다 == other_character.hp - self.power

공격을 받다 == self.hp - damage

-> 위 실습에서는 attack메소드만 부르므로 attack내부에서 get_attacked을 사용.

def attack(self, other_character):
	other_character.get_attacked(self.power)
    #get_attacked의 주체가 other_character이므로 damage == self.power

 

※조건(get_attacked 기준)※

1. is_alive 메소드를 사용해서 인스턴스가 살아있을 때만 체력을 깎는다. 이미 캐릭터가 죽었으면 죽었다는 메시지를 출력한다. -> self.hp <= 0 일 때, print("{}님이 죽었습니다.".format(self.name))

2. 남은 체력보다 공격력이 더 크면 체력(hp)을 0으로 설정한다. -> self.hp <= damage 일 때, self.hp = 0

-> self.hp 값이 중요하므로 is_alive를 사용하여 if else로 조건에 맞춰 나누기

-> 살아있는 경우 self.hp와 damage값을 비교하여 self.hp의 값을 설정 (ternary 구문 사용)

 

* ternary expression

true_value if condition else false_value

 

 

def get_attacked(self, damage):
	if self.is_alive():
    #살아있음
    	self.hp = 0 if self.hp <= damage else self.hp - damage
        #ternary expression
	else:
    #죽었음
    	print("{}님은 이미 죽었습니다.".format(self.name))

 

__str__이용하여 유의미한 정보 출력 -> character의 hp 출력

def __str__(self):
	return "{}님의 hp는 {}만큼 남았습니다.".format(self.name, self.hp)
    # self.name + "님의 hp는 " + self.hp + "만큼 남았습니다."

 

 

실습 2 - 블로그 유저 만들기

조건:

post 클래스

class Post:
    # 게시글 클래스
    def __init__(self, date, content):
        # 게시글은 속성으로 작성 날짜와 내용을 갖는다
        self.date = date
        self.content = content
    
    def __str__(self):
        # 게시글의 정보를 문자열로 리턴하는 메소드
        return "게시일: {}\n내용: {}".format(self.date, self.content)
  • 인스턴스 변수(타입)
    • name(문자열): 블로그 사용자의 이름
    • posts(리스트): 블로그 게시글들을 담을 리스트
  • 메소드
    • __init__: 인스턴스 변수가 설정되는 메소드
    • add_post: 블로그 사용자의 블로그 게시글 리스트에 새로운 게시글 인스턴스를 추가하는 메소드
    • show_all_posts: 블로그 사용자가 올린 모든 게시글을 출력하는 메소드
    • __str__: 블로그 사용자의 간단한 인사와 이름을 문자열로 리턴하는 메소드

출력예시

안녕하세요 성태호입니다.

작성 날짜: 2019년 8월 30일
내용: 
오늘은 내 생일이었다.
많은 사람들이 축하해줬다.
행복했다.

작성 날짜: 2019년 8월 31일
내용: 
재밌는 코딩 교육 사이트를 찾았다.
코드잇이란 곳인데 최고다.
같이 공부하실 분들은 www.codeit.kr로 오세요!

실습 결과

class Post:
    # 게시글 클래스
    def __init__(self, date, content):
        # 게시글은 속성으로 작성 날짜와 내용을 갖는다
        self.date = date
        self.content = content

    def __str__(self):
        # 게시글의 정보를 문자열로 리턴하는 메소드
        return "작성 날짜: {}\n내용: {}".format(self.date, self.content)
    
    
class BlogUser:
    # 블로그 유저 클래스
    def __init__(self, name):
        """
        블로그 유저는 속성으로 이름, 게시글들을 갖는다
        posts는 빈 배열로 초기화한다
        """
        self.name = name
        self.post = []

    def add_post(self, date, content):
        # 새로운 게시글 추가
        new_post = Post(date, content)
        self.post.append(new_post)

    def show_all_posts(self):
        # 블로그 유저의 모든 게시글 출력
        for post in self.posts:
            print(post)

    def __str__(self):
        # 간단한 인사와 이름을 문자열로 리턴
        return "안녕하세요 {}입니다.\n".format(self.name)
    
    

# 블로그 유저 인스턴스 생성
blog_user_1 = BlogUser("성태호")

# 블로그 유저 인스턴스 출력(인사, 이름)
print(blog_user_1)

# 블로그 유저 게시글 2개 추가
blog_user_1.add_post("2019년 8월 30일", """
오늘은 내 생일이었다.
많은 사람들이 축하해줬다.
행복했다.
""")

blog_user_1.add_post("2019년 8월 31일", """
재밌는 코딩 교육 사이트를 찾았다.
코드잇이란 곳인데 최고다.
같이 공부하실 분들은 www.codeit.kr로 오세요!
""")

# 블로그 유저의 모든 게시글 출력
blog_user_1.show_all_posts()

 

설명

 

객체 Post

속성(__init__) -> date, content

 

객체 BlogUser

속성(__init__) -> name, posts
행동(method) -> add_post, show_all_posts
def __init__(self, name):
	self.name = name 
    self.post = []
    #포스트 리스트, 각 인스턴스마다 할당됨

 

1. add_post(self, date, content)

이 블로그에서 공통적으로 post에는 date라는 값과 content라는 값이 할당됨

이는 Post 클래스를 사용하여 게시물 인스턴스를 만들어내면 됨!

그리고 만들어진 게시물 객체를 블로그 유저의 포스트 리스트에 추가하면 된다.

def add_post(self, date, content):
#새로운 포스트 객체 생성
	new_post = Post(date, content)
    self.post.append(new_post)
    #블로그 유저 내 post리스트에 추가

 

2. show_all_posts(self)

post 리스트에 저장되어있는 게시물들을 내보내기.

for문을 이용하여 리스트에 저장되어있는 모든 값들을 이끌어내면 된다.

※ 각 인스턴스들은 Post란 클래스로 만들어졌으므로 pirnt를 이용하면 자동적으로 "안녕하세요 {}입니다.\n".format(self.name) 값 리턴

def show_all_posts(self):
	for post in self.post:
    	print(post)
        #print를 이용하여 __str__이 return 될 수 있도록 함

 

3. __str__

간단한 인사와 이름을 문자열로 리턴하길 원하므로

def __str__(self):
	return "안녕하세요 {}입니다.\n".format(self.name)

 

실습3 - 시계만들기

* 뒤 수업 듣기 전

 

필요 기능

  1. 현재 시간을 설정할 수 있다.
  2. 현재 시간을 변경할 수 있다.
  3. 현재 시간에 1초씩 더할 수 있다.
속성: 시간, 분, 초
행동: 시간 설정, 시간에 1초씩 더함
class Clock:
    #시계 클래스
    def __init__(self, hour, minute, sec):
        self.hour = hour
        self.minute = minute
        self.sec = sec
    
    def tick(self):
        #1초씩 더하는 메소드
        #60 sec = 1minute
        #60 minute = 1 hour
        #24 hour -> 0 시간으로 초기화
        self.sec += 1
        
        if self.sec == 60:
            self.minute += 1
            self.sec = 0
            
            if self.minute == 60:
                self.hour += 1
                self.minute = 0
                
                if self.hour == 24:
                    self.hour = 0

    def set(self, hour, minute, sec):
        self.hour = hour
        self.minute = minute
        self.sec = sec
    
    def  __str__(self):
        return str(self.hour).zfill(2) + ":" + str(self.minute).zfill(2) + ":" + str(self.sec).zfill(2)
        
    
# 1시 30분 48초인 시계 인스턴스 생성
clock = Clock(1, 30, 48)
    
# 13초를 늘린다
for i in range(13):
    clock.tick()
    
# 시계의 현재 시간 출력
print(clock)
    
# 2시 3분 58초로 시계 세팅
clock.set(2, 3, 58)
    
# 5초를 늘린다
for i in range(5):
    clock.tick()
    
# 시계의 현재 시간 출력
print(clock)
    
# 23시 59분 57초로 세팅
clock.set(23, 59, 57)
    
# 5초를 늘린다
for i in range(5):
    clock.tick()
    
# 시계의 현재 시간 출력
print(clock)

피드백:

tick 메소드가 복잡함

zfill을 이용해 표현할 때, 세번이나 중복됨

 

Counter 클래스를 도입하여 위 코드를 단순화 할 수 있다.

class Counter:
    """
    시계 클래스의 시,분,초를 각각 나타내는데 사용될 카운터 클래스
    """

    def __init__(self, limit):
        """
        인스턴스 변수 limit(최댓값), value(현재까지 카운트한 값)을 설정한다.
        인스턴스를 생성할 때 인스턴스 변수 limit만 파라미터로 받고, value는 초깃값 0으로 설정한다.
        """    
        self.limit = limit
        self.value = 0


    def set(self, new_value):
        """
        파라미터가 0 이상, 최댓값 미만이면 value에 설정한다.
        아닐 경우 value에 0을 설정한다.
        """
        if 0 <= new_value < self.limit:
            self.value = new_value
        else:
            self.value = 0


    def tick(self):
        """
        value를 1 증가시킨다.
        카운터의 값 value가 limit에 도달하면 value를 0으로 바꾼 뒤 True를 리턴한다.
        value가 limit보다 작은 경우 False를 리턴한다.
        """
        self.value += 1

        if self.value == self.limit:
            self.value = 0
            return True
        return False


    def __str__(self):
        """
        value를 최소 두 자릿수 이상의 문자열로 리턴한다. 
        일단 str 함수로 숫자형 변수인 value를 문자열로 변환하고 zfill을 호출한다. 
        """
        return str(self.value).zfill(2)
    

class Clock:
    """
    시계 클래스
    """
    HOURS = 24 # 시 최댓값
    MINUTES = 60 # 분 최댓값
    SECONDS = 60 # 초 최댓값

    def __init__(self, hour, minute, second):
        """
        각각 시, 분, 초를 나타내는 카운터 인스턴스 3개(hour, minute, second)를 정의한다.
        현재 시간을 파라미터 hour시, minute분, second초로 지정한다.
        """
        
        self.hour = Counter(Clock.HOURS)
        self.minute = Counter(Clock.MINUTES)
        self.second = Counter(Clock.SECONDS)
        
        self.set(hour, minute, second)
        #self.hour.set(hour)
        #self.minute.set(minute)
        #self.second.set(second)
        

    def set(self, hour, minute, second):
        """현재 시간을 파라미터 hour시, minute분, second초로 설정한다."""
        self.hour.set(hour)
        self.minute.set(minute)
        self.second.set(second)


    def tick(self):
        """
        초 카운터의 값을 1만큼 증가시킨다.
        초 카운터를 증가시킬 때, 분 또는 시가 바뀌어야하는 경우도 처리한다.
        """
        if self.second.tick():
            if self.minute.tick():
                self.hour.tick()
        
        

    def __str__(self):
        """
        현재 시간을 시:분:초 형식으로 리턴한다. 시, 분, 초는 두 자리 형식이다.
        예시: "03:11:02"
        """
        return str(self.hour) + ":" + str(self.minute) + ":" + str(self.second) 
        

# 초가 60이 넘을 때 분이 늘어나는지 확인하기
print("시간을 1시 30분 48초로 설정합니다")
clock = Clock(1, 30, 48)
print(clock)

# 13초를 늘린다
print("13초가 흘렀습니다")
for i in range(13):
    clock.tick()
print(clock)

# 분이 60이 넘을 때 시간이 늘어나는지 확인
print("시간을 2시 59분 58초로 설정합니다")
clock.set(2, 59, 58)
print(clock)

# 5초를 늘린다
print("5초가 흘렀습니다")
for i in range(5):
    clock.tick()
print(clock)

# 시간이 24가 넘을 때 00:00:00으로 넘어가는 지 확인
print("시간을 23시 59분 57초로 설정합니다")
clock.set(23, 59, 57)
print(clock)

# 5초를 늘린다
print("5초가 흘렀습니다")
for i in range(5):
    clock.tick()
print(clock)

 

Counter 클래스

시계의 매커니즘은 다음과 같다.

1. 시간 설정이 가능
2. 시간 변경이 가능
3. 1초씩 더함 -> 60 sec = 1 min / 60 min = 1h / 24h -> 0h로 초기화
4. 시간을 표시할 때 2자리 숫자로 표현

Counter 클래스는 3번특성 때문에 혼자서 만들었을 때 복잡해진 tick에서 반복되는 부분을 한번에 모았다.

 

hour, minute, second는 각각 최대값이 존재하므로 최대값에 접근 하였을 때 초기화 되는 매커니즘!

그에 더해 기본적인 시계의 매커니즘을 구현.

 

class Counter:
	
    def __init__(self, limit):
    	self.limit = limit
        self.value = 0
        #limit값은 다 다르므로 파라미터 값으로 받아줌
        #value값은 0으로 설정
        
	def set(self, new_value):
    	value = new_value if 0 <= new_value < limit else 0
        #값을 사용자 마음대로 설정하는 메소드
        #new_value 값이 0과 limit 사이일 때만 value값에 저장
        
    def tick(self):
    	self.value += 1
        
        if self.value == self.limit:
        	self.value = 0
            #limit값에 도달하였으므로 0으로 값 초기화
            return True
        return False
        #True, False 값을 이용하여 조건문에 활용이 가능하다.
    
    def __str__(self):
    	return str(self.value).zfill(2)
        #2자리 숫자로 표현하므로 zfill을 이용

 

Clock 클래스

본격적으로 시계의 본체가 될 클래스

counter 클래스를 적절히 이용하면 쉽게 만들 수 있다.

이번에도 역시 시계가 취해야할 행동을 생각하며 만들기

 

* 시계에서 시, 분, 초 각각의 limit값이 명확하므로 Clock 클래스 내부에 정의를 해놓자

class Clock:
	
    HOURS = 24
    MINUTES = 60
    SECONDS = 60
    #Clock 내부에서 사용될 각각의 limit값

 

1. __init__(self, hour, minute, second)

시계에 정의될 self.hour, self.minute, self.second값들은 limit값이 정해져있으므로 Counter 클래스를 이용한 인스턴스가 될 것이다. 인스턴스가 된 각각의 값에 파라미터 값 그대로를 대입하면 오류가 발생하므로 Counter 클래스의 set 메소드를 이용하여 각각의 값을 할당할 수 있다.

def __init__(self, hour, minute, second):
	
    self.hour = Counter(Clock.HOURS) #Clock 내부에서 정의된 상수이므로
    self.minute = Counter(Clock.MINUTES)
    self.second = Counter(Clock.SECONDS)
    
    self.hour.set(hour)
    self.minute.set(minute)
    self.second.set(second)
    #인스턴스들이므로 set 메소드를 이용하여 값 할당

 

2. set(self, hour, minute, second)

처음 Clock 클래스에 할당된 값들을 새로 받은 값으로 치환하는 메소드

이 역시 인스턴스들의 set 메소드를 이용하여 할당할 수 있다.

def set(self, hour, minute, second):
	
    self.hour.set(hour)
    self.minute.set(minute)
    self.second.set(second)

 

*Clock 클래스 내부의 set 메소드는 __init__ 메소드에서 처음 값을 할당할 때와 똑같은 방식이다.

그러므로 중복을 피하고자 set 메소드를 이용하여 간단하게 표현이 가능하다.

def __init__(self, hour, minute, second):
	
    self.hour = Counter(Clock.HOURS)
    self.minute = Counter(Clock.MINUTES)
    self.second = Counter(Clock.SECONDS)
    
    self.set(hour, minute, second)
    #self.hour.set(hour)
    #self.minute.set(minute)
    #self.second.set(second)
※주의점
self.hour.set(hour) 는 Counter 클래스로 만들어진 인스턴스 hour의 set 메소드를 이용한 것이고
self.set(hour, minute, second)는 Clock 클래스로 만들어진 인스턴스의 set 메소드를 이용한 것이다.
그러므로 두 문장은 같은 set이라도 다른 set이다.

 

3. tick(self)

tick은 1초를 더해주는 메소드이므로 파라미터를 받을 필요가 없다.

tick에서 가장 중요한 조건은 60 sec = 1 min / 60 min = 1h / 24h -> 0h 이므로 각각의 hour, minute, second 인스턴스가 가지고 있는 tick 메소드를 활용하여 조건문을 만들 수 있다.

def tick(self):
	
    if self.second.tick():
    	if self.minute.tick():
        	self.hour.tick()
    #if는 조건 = True 일 때 발동
    #초에서부터가 단위가 바뀌므로 self.second.tick()을 먼저 사용
    #self.second.tick()을 부를 시 무조건 second의 값이 1 올라가고 False나 True값 반환
    #-> second의 값이 1 올라가는 것이 먼저이므로 if구문이 발동되지 않더라도 second += 1 상태임 
    #True값 반환 = limit 도달이므로 self.minute.tick()으로 minute값을 1 올린다.
    #self.minute.tick() 이하동문

 

4. __str__(self)

print문을 사용하면 자동으로 나오는 메소드.

def __str__(self):

	return "{}:{}:{}".format(self.hour, self.minute, self.second)
    #인스턴스 속 __str__ 에서 이미 str로 타입 변환과 zfill을 이용한 처리를 이미 끝냈으므로
    #format을 이용하여 return만 하면 된다.