Python 객체와 책임 분리

클래스·인스턴스·상속을 명단 예제로 설명하고 검증 규칙을 테스트하기

이번 문서에서 만드는 것

3주에는 2주의 dict 명단을 객체로 옮기고 상태와 규칙을 어디에 둘지 결정합니다. 함수·list·dict·예외를 먼저 익혀야 합니다. 클래스 수를 늘리는 것이 목표가 아니라 잘못된 상태를 만드는 경로를 줄이는 것이 목표입니다.

클래스와 인스턴스

클래스는 객체를 만드는 정의이고 인스턴스는 실제 회원 한 명입니다. self는 메서드를 호출한 인스턴스입니다. 아래 코드를 members.py에 작성합니다.

Python
class Member:    def __init__(self, member_id, name):        if not isinstance(member_id, int) or isinstance(member_id, bool) or member_id <= 0:            raise ValueError("ID는 양의 정수여야 합니다.")        self.member_id = member_id        self.rename(name)
    def rename(self, name):        if not isinstance(name, str) or not name.strip():            raise ValueError("이름은 비울 수 없습니다.")        self._name = name.strip()
    @property    def name(self):        return self._name
    def label(self):        return f"{self.member_id}: {self.name}"

class Mentor(Member):    def __init__(self, member_id, name, specialty):        super().__init__(member_id, name)        self.specialty = specialty
    def label(self):        return f"{super().label()} / 멘토({self.specialty})"

if __name__ == "__main__":    roster = [Member(1, "가람"), Mentor(2, "나래", "Python")]    for member in roster:        print(member.label())

python members.py를 실행하면 회원과 멘토의 서로 다른 라벨이 출력됩니다. 같은 label() 호출에 객체별 동작이 적용되는 것이 다형성입니다. super()는 부모 클래스의 초기화와 동작을 재사용합니다.

_name의 밑줄은 내부 사용을 나타내는 관례이지 보안 장치가 아닙니다. 외부에서는 rename()을 사용하도록 약속하고 테스트로 검증 규칙을 지킵니다. @property에는 setter를 정의하지 않았으므로 member.name = ...은 허용되지 않습니다.

상속이 필요한지 먼저 판단하기

멘토가 일반 회원처럼 사용되어도 자연스러울 때 상속을 고려합니다. 하지만 운영 중 역할이 자주 바뀐다면 Member에 역할 정보를 조합하는 편이 단순할 수 있습니다. 역할 하나마다 하위 클래스를 만드는 것은 이번 실습의 요구가 아닙니다.

또한 모든 회원이 공유하면 안 되는 list를 클래스 본문에 두지 않습니다. 다음은 실패 원인을 보여 주는 반례입니다.

Python
# 반례: 두 객체가 같은 목록을 공유합니다.class BadTeam:    members = []
# 수정: 팀을 만들 때마다 새 목록을 생성합니다.class Team:    def __init__(self):        self.members = []

규칙을 테스트로 고정하기

test_members.py를 다음으로 교체하고 python -m unittest -v를 실행합니다. 앞 주의 파일을 보존하려면 별도 주차 폴더를 사용합니다.

Python
import unittestfrom members import Member, Mentor
class MemberTests(unittest.TestCase):    def test_rename_keeps_valid_name_on_failure(self):        member = Member(1, "가람")        with self.assertRaises(ValueError):            member.rename(" ")        self.assertEqual(member.name, "가람")
    def test_instances_are_independent(self):        first, second = Member(1, "가람"), Member(2, "나래")        first.rename("다온")        self.assertEqual(second.name, "나래")
    def test_polymorphism(self):        member = Mentor(3, "라온", "Django")        self.assertIsInstance(member, Member)        self.assertIn("멘토", member.label())

세션 진행과 제출물

권장 90분: dict와 객체 비교 15분, 코드 실행·설명 25분, 규칙 추가 30분, 짝 리뷰 20분입니다.

실습 과제는 이름 최대 길이를 40자로 제한하는 것입니다. 생성과 이름 변경 양쪽에서 같은 규칙을 적용하고 40자·41자·공백 입력을 검사합니다. 모든 검증을 Controller 같은 웹 계층에만 둘 때 생길 문제를 토론합니다.

제출물은 members.py, 테스트, “상속 대신 역할 필드를 선택할 상황” 한 문단입니다. 완료 기준은 메서드 호출 전후 상태를 설명하고 실패한 변경이 기존 정상 값을 훼손하지 않는 것을 확인하는 것입니다.

Django로 이어지는 부분

Django 모델도 클래스지만 일반 Python 객체와 동일하지는 않습니다. DB 필드와 저장·조회 규칙이 추가됩니다. 지금 작성한 클래스를 그대로 모델에 복사하기보다, 다음 주에는 요청 처리와 저장소의 경계를 먼저 배웁니다.

연결된 PBL 미션과 VOD

주차·미션참고 VOD 범위
3주 · Python 객체지향Python 첫걸음 10장

강좌 안내: 참고 강좌 1. 이 문서는 영상 전체를 옮긴 전사 자료가 아닙니다. 세부 내용은 위 공식 문서에서 확인합니다.