1. 어떤 사용 예시가 있을까?

- 클래스를 만들면 동일하거나 비슷한 속성의 객체를 쉽게 관리할 수 있다.

- 예를 들어 카드의 전투력과 방어력이 변동하고 해당 값으로 승패를 가르는 게임이 있다면 쉽게 사용할 수 있을 것이다.

 

 

2. 카드 게임에 사용할 기본 클래스를 구현해보자

- 아래와 같이 구현해보자

class BattleCard:
    def __init__(self, name, attack, defense):
        self.name = name
        self.attack = attack
        self.defense = defense

    def __repr__(self):
        return f"BattleCard('{self.name}', {self.attack}, {self.defense})"
    
    def __getitem__(self, key):
        if key == 'name':
            return self.name
        elif key == 'attack':
            return self.attack
        elif key == 'defense':
            return self.defense
        else:
            raise KeyError(f"Invalid key: {key}")

# wave_card: BattleCard('훌라댄서 웨이브', 10, 5)
# wave_card['name']: 훌라댄서 웨이브
# wave_card['attack']: 10
# wave_card['defense']: 5
# --------------------------------
# surf_card: BattleCard('먹부림 대장 서프', 4, 9)
# surf_card['name']: 먹부림 대장 서프
# surf_card['attack']: 4
# surf_card['defense']: 9

- 그러면 아래와 같이 데이터를 각 카드의 속성을 확인할 수 있다.

wave_card = BattleCard(name="훌라댄서 웨이브", attack=10, defense=5)
surf_card = BattleCard(name="먹부림 대장 서프", attack=4, defense=9)

print(f"wave_card: {wave_card}")
print(f"wave_card['name']: {wave_card['name']}")
print(f"wave_card['attack']: {wave_card['attack']}")
print(f"wave_card['defense']: {wave_card['defense']}")
print("--------------------------------")
print(f"surf_card: {surf_card}")
print(f"surf_card['name']: {surf_card['name']}")
print(f"surf_card['attack']: {surf_card['attack']}")
print(f"surf_card['defense']: {surf_card['defense']}")

- 그렇다면 카드끼리 싸우는 함수를 통해 한 번의 싸움마다 카드의 승패를 확인할 수 있다.

def battle_result(player1_card, player2_card):
    player1_card_defense_status_after_battle = player1_card['defense'] - player2_card['attack']
    player2_card_defense_status_after_battle = player2_card['defense'] - player1_card['attack']
    
    if player1_card_defense_status_after_battle <= 0 and player2_card_defense_status_after_battle <= 0:
        return "두 카드가 모두 파괴되어 무승부입니다.."
    elif player1_card_defense_status_after_battle <= 0 and player2_card_defense_status_after_battle > 0:
        return f"{player1_card['name']} 패배했습니다."
    elif player1_card_defense_status_after_battle > 0 and player2_card_defense_status_after_battle <= 0:
        return f"{player2_card['name']} 패배했습니다."
    else:
        return "두 카드가 모두 살아있습니다."
        

battle_result(wave_card, surf_card)
# '먹부림 대장 서프 패배했습니다.'

- 하지만 만약 무승부로 끝나서 이어서 대결을 해야한다면??

- 그렇다면 인스턴스의 값을 변경하고 다시 대결을 하는 과정이 필요하다.

- 따라서 인스턴스의 값을 변경시킬 수 있는 특별 메서드를 추가한다.

 

 

3. 값을 변경할 수 있도록 바꾸고 배틀 함수를 변경해보자

- __setitem__을 추가해보자

class BattleCard:
    def __init__(self, name, attack, defense):
        self.name = name
        self.attack = attack
        self.defense = defense

    def __repr__(self):
        return f"BattleCard('{self.name}', {self.attack}, {self.defense})"
    
    def __getitem__(self, key):
        if key == 'name':
            return self.name
        elif key == 'attack':
            return self.attack
        elif key == 'defense':
            return self.defense
        else:
            raise KeyError(f"Invalid key: {key}")
    
    def __setitem__(self, key, value):
        if key == 'name':
            self.name = value
        elif key == 'attack':
            self.attack = value
        elif key == 'defense':
            self.defense = value
        else:
            raise KeyError(f"Invalid key: {key}")

- 그리고 배틀 함수에서 변경된 defence의 값을 적용하고 결과로 출력한다.

- 또한 배틀 후의 결과를 출력하는 함수와 배틀을 하는 함수로 분할해서 함수를 관리한다.

def cards_status_after_battle(player1_card, player2_card):
    player1_card_defense_status_after_battle = player1_card['defense'] - player2_card['attack']
    player2_card_defense_status_after_battle = player2_card['defense'] - player1_card['attack']
    player1_card['defense'] = player1_card_defense_status_after_battle
    player2_card['defense'] = player2_card_defense_status_after_battle
    return player1_card, player2_card
    
def battle_result(player1_card, player2_card):
    player1_card_defense_status_after_battle = player1_card['defense']
    player2_card_defense_status_after_battle = player2_card['defense']
    
    if player1_card_defense_status_after_battle <= 0 and player2_card_defense_status_after_battle <= 0:
        return "두 카드가 모두 파괴되어 무승부입니다.."
    elif player1_card_defense_status_after_battle <= 0 and player2_card_defense_status_after_battle > 0:
        return f"{player1_card['name']} 패배했습니다."
    elif player1_card_defense_status_after_battle > 0 and player2_card_defense_status_after_battle <= 0:
        return f"{player2_card['name']} 패배했습니다."
    else:
        return "두 카드가 모두 살아있습니다."


player1_card, player2_card = cards_status_after_battle(wave_card, surf_card)
print(battle_result(player1_card, player2_card))
print(player1_card)
print(player2_card)

# 먹부림 대장 서프 패배했습니다.
# BattleCard('훌라댄서 웨이브', 10, 1)
# BattleCard('먹부림 대장 서프', 4, -1)

 

'개발 공부 > Python' 카테고리의 다른 글

1. 특별 메서드의 기본적인 사용  (0) 2025.01.09

1. 특별 메서드?

- 클래스에 특정한 동작을 부여하기 위해 사용되는 방법이다.

- 더하기, 빼기, 길이 출력 등 쉽게 코드로 구현할 수 있는 기능을 제공한다.

- 하지만 특별 메서드를 사용하면 추가적인 구현이 없어도 되고, 클래스의 기능 구성이 간결해진다.

 

2. 예시를 통해 알아보자

class NumberList:
    def __init__(self, initial_data=None):
        self.data = initial_data
    
    def __getitem__(self, index):
        return self.data[index]
    
    def __len__(self):
        return len(self.data)
    
    def __repr__(self):
        return f"NumberList({self.data})"


- 위의 클래스를 아래와 같이 실행하면 주석으로 달아둔 결과를 얻을 수 있다.

my_list = NumberList([1, 2, 3, 4, 5])

print(f"3번째 숫자: {my_list[3]}")
print(f"나의 리스트 길이 : {len(my_list)}")
print(f"나의 리스트 문자열: {my_list}")

# 출력 결과
# 3번째 숫자: 4
# 나의 리스트 길이 : 5
# 나의 리스트 문자열: NumberList([1, 2, 3, 4, 5])

1. Dockerfile로 특정 작업을 실행하는 이미지를 만들면 좋은점

- 특정 작업을 실행하는 이미지를 만들어 해당 작업을 어디서든 실행시킬 수 있다.

- 도커컨테이너로 작업하면 실행 환경이 보장되기 때문에 안정적인 배포가 가능하다.

- 이를 통해 CI/CD를 할 수 있다.

 

2. Dockerfile를 만드는 법

- 실습을 위해 폴더를 하나 만들고 아래와 같이 main.py와 Dockerfile이라는 파일을 만들자.

# main.py
# argparse로 특정 인자를 실행할 때 받는 코드

import argparse

parser = argparse.ArgumentParser(description="Process two arguments.")
parser.add_argument("arg1", help="The first argument.")
parser.add_argument("arg2", help="The second argument.")
args = parser.parse_args()

print(f"arg1: {args.arg1}, arg2: {args.arg2}")
# Dockerfile (확장자가 없는 파일입니다.)
FROM python:3.9-slim

# 작업 디렉토리 설정
WORKDIR /app

# main.py 복사
COPY main.py .

# 파이썬 로그가 보이게 버퍼 설정
ENV PYTHONUNBUFFERED=1

# 실행 설정
ENTRYPOINT ["python", "main.py"]

- 평소에 argparse를 사용했다면 main.py에 arg가 2개 들어가야 한다는 것을 알것이다.

- 해당 arguments는 이미지를 실행(run) 할 때 넣으면 된다.

 

3. Dockerfile로 image 만들기

- Dockerfile이 있는 경로에서 아래와 같은 명령어를 실행한다.

# main-with-args라는 이름과 1.0이라는 tag를 가지고 docker image가 생긴다
docker build -t main-with-args:1.0

# build가 성공적으로 완료되면 설정한 이름으로 이미지가 생성됐는지 본다.
docker image list

성공했다면 이렇게 조회된다.

 

4. arguments를 써서 컨테이너를 실행하기

- 단순히 컨텐이너를 실행할 때 arguments를 붙이면 된다.

# test-arguments라는 컨테이너 이름으로 first와 second라는 arguments를 붙여서 실행한다.
docker run --name test-arguments main-with-args:1.0 first second

# 실행이 완료되면 docker logs로 확인하자
docker logs test-arguments

아래와 같은 로그가 나오면 성공이다.

 

5. arguments가 아니라 환경변수 값으로는 안될까?

- arguments가 아니라 환경변수를 인자로 받아서 실행하고 싶은 경우도 있을 것이다.

- 그런경우 아래와 같이 환경 변수를 Dockerfile에서 줄 수 있고 실행해서 확인할 수 있다.

# MY_ARG1, MY_ARG2라는 이름으로 설정된 환경 변수를 가져온다.
import os

print(f"MY_ARG1: {os.environ.get('MY_ARG1')}, MY_ARG2: {os.environ.get('MY_ARG2')}")
# 환경 변수를 설정했으니 main.py에 환경변수를 받는 코드가 있으면 된다.
FROM python:3.9-slim

# 작업 디렉토리 설정
WORKDIR /app

# main.py 복사
COPY main.py .

# 파이썬 로그가 보이게 버퍼 설정
ENV PYTHONUNBUFFERED=1

# 환경 변수 설정
ENV MY_ARG1="first my env"
ENV MY_ARG2="second my env"

# 실행 설정
ENTRYPOINT ["python", "main.py"]

 

6. 위와 동일하게 이미지를 만들고 실행해보자

- 명령어는 동일한 방식으로 사용하면 된다.

- 물론 이번에는 arguments를 받는 경우가 아니므로 컨테이너를 그냥 실행하기만 하면 된다.

# 이미지를 생성한다.
docker build -t main-with-envs:1.0 .

# arguments를 주는 코드가 아니므로 실행만한다.
docker run --name test-envs main-with-envs:1.0

# 로그를 살펴보자!
docker logs test-envs

정상적으로 적용된 것을 알 수 있다.

 

7. 결론

- 상황에 따라 적절하게 이미지를 생성해서 사용하자.

- 주로 환경변수로 작업을 진행할테지만 상황에 따라 arguments를 부여해야하는 경우가 있으니 적절히 사용해보자!

+ Recent posts