파이썬 클래스 기초부터 실전 OOP까지 완벽 정리

읽기 예상 시간: 8분

파이썬 클래스의 기초 개념을 붕어빵 틀 비유로 아주 쉽게 이해하고 객체지향 프로그래밍에 확실히 입문할 수 있어요. self, __init__, 인스턴스 변수와 클래스 변수 등 처음 배울 때 헷갈리기 쉬운 핵심 뼈대를 명확하게 잡아드릴게요. 나아가 PEP 8 네이밍 규칙과 타입 힌트를 적용해 요즘 실무에서 선호하는 모던 파이썬 코드 작성법을 배우고, 상속과 재고 관리 시스템 예제로 실무 감각까지 완벽하게 익혀봐요.

목차

왜 클래스가 필요한가요? 붕어빵 틀로 이해하기

코딩을 처음 배우다 보면 객체지향 프로그래밍이라는 단어를 정말 자주 듣게 되죠? 이름만 들으면 왠지 철학적이고 엄청 어렵게 느껴지지만, 사실 아이디어 자체는 아주 단순하고 실용적이에요. 우리가 왜 이 개념을 배워야 하는지 구체적인 상황을 상상해볼까요? 여러분이 동물 병원 관리 프로그램을 만든다고 가정하고, 강아지 10마리의 이름과 나이를 코드로 관리한다고 생각해봐요.

클래스를 쓰지 않고 변수로만 이 정보를 관리하려고 하면 금방 한계에 부딪히는 걸 볼 수 있어요. 강아지가 늘어날 때마다 무한정으로 변수를 만들어내야 하거든요.

python
no_class_vs_class.py
# 클래스 없이 변수로만 강아지 정보를 관리하는 방식이에요.
# 강아지가 100마리가 된다면 이런 변수를 200개나 만들어야 해요!
dog1_name = "Luna"  # 첫 번째 강아지의 이름 변수 생성
dog1_age = 3        # 첫 번째 강아지의 나이 변수 생성
dog2_name = "Rex"   # 두 번째 강아지의 이름 변수 생성
dog2_age = 5        # 두 번째 강아지의 나이 변수 생성

# 클래스를 사용해 강아지라는 '틀'을 만들고 정보를 관리하는 방식이에요.
class Dog:
    def __init__(self, name, age):  # 강아지를 만들 때 이름과 나이를 받아요.
        self.name = name            # 받은 이름을 자기 자신(self)의 이름으로 저장해요.
        self.age = age              # 받은 나이를 자기 자신(self)의 나이로 저장해요.

# 이제 강아지가 아무리 늘어나도 아주 깔끔하게 찍어낼 수 있어요.
luna = Dog("Luna", 3)  # 이름은 Luna, 나이는 3인 강아지 객체를 하나 만들어요.
rex = Dog("Rex", 5)    # 이름은 Rex, 나이는 5인 강아지 객체를 하나 더 만들어요.

데이터가 2~3개일 때는 변수로 관리해도 괜찮아요. 하지만 실제 서비스에서는 수백, 수천 마리의 강아지 정보를 다뤄야 해요. 데이터가 늘어날수록 클래스의 장점은 아주 명확해져요. 관련된 데이터를 하나의 덩어리로 묶어서 깔끔하게 관리할 수 있기 때문이에요.

이때 클래스와 인스턴스의 관계를 흔히 붕어빵에 비유해요. 이 비유가 가장 직관적이고 이해하기 쉽거든요.

  • 클래스: 붕어빵 틀이에요. 어떤 모양으로 빵을 구울지, 안에 어떤 재료를 넣을 수 있는지 미리 정해두는 철판 설계도죠. 틀 자체는 먹을 수 없어요.
  • 인스턴스(객체): 그 틀로 찍어낸 실제 붕어빵이에요. 우리가 실제로 만지고 다루는 데이터 그 자체랍니다.

같은 철판 틀(클래스)로 빵을 찍어내도, 어떤 붕어빵(인스턴스)에는 달콤한 팥을 듬뿍 넣고, 어떤 것에는 부드러운 슈크림을 넣을 수 있어요. 심지어 피자 소스를 넣을 수도 있죠. 즉, 각 인스턴스는 부모 틀의 형태는 공유하지만 자기만의 독립적인 데이터를 안전하게 보관한다는 뜻이에요. 이게 바로 객체지향의 첫걸음이에요!

붕어빵 틀과 다양한 속이 든 붕어빵으로 파이썬 클래스와 인스턴스의 관계를 비유한 실사 이미지

클래스 기본 구조와 파이썬 표준 작성법

클래스를 직접 만들 때 반드시 알고 넘어가야 할 문법적 구조가 있어요. 바로 생성자(__init__)와 self예요. 생성자는 붕어빵을 철판에서 막 꺼낼 때 무조건 한 번 실행되는 초기화 함수예요. self는 만들어진 붕어빵, 즉 인스턴스 자기 자신을 가리키는 필수 키워드죠.

코드를 작성할 때는 내 맘대로 이름을 짓기보다 파이썬 코딩 스타일의 공식 표준인 파이썬 기초 문서(PEP 8)에 따라 작성하는 게 좋아요. 수많은 개발자가 함께 일하는 실무에서는 이런 약속을 지키는 게 정말 중요하거든요. 클래스 이름은 단어 첫 글자를 대문자로 쓰는 PascalCase(예: DogInfo)로 적고, 함수나 변수 이름은 소문자와 밑줄을 쓰는 snake_case(예: make_sound)로 작성하는 게 원칙이에요.

또한 요즘 트렌드를 볼 수 있는 파이썬 클래스 관련 공식 문서를 살펴보면, 타입 힌트(Type Hint)가 거의 기본으로 적용되어 있어요. 변수에 문자열이 들어올지 숫자가 들어올지 미리 명시해주는 아주 훌륭한 기능이에요. 이 모든 실무 규칙들을 꾹꾹 눌러 담아 제대로 된 기본 클래스를 만들어 볼게요.

python
modern_class.py
# PEP 8 규칙에 따라 클래스 이름은 첫 글자를 대문자로(Dog) 깔끔하게 작성했어요.
class Dog:
    # 클래스 변수: 이 클래스로 만든 모든 강아지가 공통으로 공유하는 데이터예요.
    # 모든 강아지의 종은 동일하니까 이렇게 한 번만 선언해요.
    species: str = "Canis familiaris" 

    # 생성자 메서드: 인스턴스를 만들 때 무조건 처음 실행돼요.
    # name은 문자열(str), age는 정수(int)가 들어와야 한다고 타입 힌트를 적었어요.
    # -> None은 이 함수가 아무것도 반환하지 않는다는 뜻이에요.
    def __init__(self, name: str, age: int) -> None:
        # 인스턴스 변수: 각 강아지마다 다르게 가지는 독립적인 데이터예요.
        self.name: str = name  # 자기 자신(self)의 name 속성에 전달받은 name을 쏙 저장해요.
        self.age: int = age    # 자기 자신(self)의 age 속성에 전달받은 age를 쏙 저장해요.

    # 인스턴스 메서드: PEP 8 규칙에 따라 소문자로(introduce) 작성했어요.
    def introduce(self) -> str:
        # 자기 자신의 이름과 나이를 조합해 예쁜 문자열로 반환해요.
        return f"멍멍! 제 이름은 {self.name}이고, 나이는 {self.age}살이에요."

# Dog 클래스(틀)를 이용해 luna라는 인스턴스(붕어빵)를 만들어요.
luna = Dog("Luna", 3)
print(luna.introduce())  # 출력: 멍멍! 제 이름은 Luna이고, 나이는 3살이에요.

📌 Note

타입 힌트를 꼼꼼히 적어두면 코드를 읽는 동료도 편하고, VSCode나 PyCharm 같은 에디터가 어떤 데이터가 필요한지 알아서 추천해주기 때문에 오타나 타입 관련 실수를 획기적으로 줄일 수 있어요.

홀로그램 강아지 설계도를 분석하는 프로그래머를 통해 클래스 기본 구조와 타입 힌트를 표현한 실사 이미지

목적에 따라 골라 쓰는 3가지 메서드

클래스 안에서 정의하는 함수를 우리는 특별히 메서드(Method)라고 불러요. 일반 함수와 비슷해 보이지만 클래스에 속해 있다는 점이 달라요. 파이썬에서는 용도와 목적에 따라 총 3가지 종류의 메서드를 골라 쓸 수 있어요. 이 차이를 아는 게 실력의 척도가 되기도 해요.

  • 인스턴스 메서드: 여러분이 가장 흔하게 쓰는 기본 메서드예요. 무조건 첫 번째 매개변수로 self를 받아서 각 인스턴스 고유의 데이터를 읽거나 수정해요.
  • 클래스 메서드: 머리 위에 @classmethod라는 특별한 장식(데코레이터)을 달아줘요. 첫 번째 매개변수로 클래스 자신을 의미하는 cls를 받아요. 주로 새로운 인스턴스를 찍어내는 다양한 방법을 제공할 때 유용하게 쓰여요.
  • 정적 메서드: @staticmethod라는 장식을 붙여요. selfcls를 전혀 받지 않고, 클래스 안에서 단순히 독립적인 도구 상자(유틸리티) 역할을 할 때 써요. 외부 데이터를 변환하는 계산기 같은 역할이죠.

말씀드린 세 가지 메서드를 반지름으로 원의 넓이를 구하는 수학 클래스로 한 번에 비교해 볼게요. 코드를 보면 훨씬 직관적으로 다가올 거예요.

python
circle_methods.py
# 원을 다루는 Circle 클래스를 정의해요.
class Circle:
    # 모든 원이 공평하게 공유하는 파이(pi) 값을 클래스 변수로 설정해요.
    pi: float = 3.14159

    # 반지름(radius)을 받아 인스턴스를 초기화하는 생성자예요.
    def __init__(self, radius: float) -> None:
        self.radius = radius  # 인스턴스 변수에 반지름을 안전하게 저장해요.

    # 1. 인스턴스 메서드: self를 통해 자기 자신의 반지름(radius)을 가져와 넓이를 계산해요.
    def area(self) -> float:
        # 원의 넓이 = 파이 * 반지름의 제곱
        return Circle.pi * (self.radius ** 2)

    # 2. 클래스 메서드: @classmethod를 붙이고 첫 인자로 cls를 받아요.
    @classmethod
    def from_diameter(cls, diameter: float) -> 'Circle':
        # 지름(diameter)을 2로 나눠서 반지름으로 만든 뒤, 클래스(cls)를 호출해 새 원을 만들어요.
        return cls(diameter / 2) 

    # 3. 정적 메서드: @staticmethod를 붙이고 self나 cls 없이 일반 함수처럼 작동해요.
    @staticmethod
    def cm_to_mm(cm: float) -> float:
        # 원의 내부 데이터와 상관없이 단순히 센티미터를 밀리미터로 바꾸는 계산만 수행해요.
        return cm * 10

# 지름이 10인 원을 만들고 싶을 때 클래스 메서드를 쓰면 생성 의도가 아주 명확해져요.
c = Circle.from_diameter(10)
print(f"넓이: {c.area()}") # 출력: 넓이: 78.53975

메서드 종류를 상황에 맞게 꼼꼼하게 나누면 코드를 읽는 사람이 “아, 이 함수는 굳이 인스턴스 데이터에 접근하지 않는 단순 계산용이구나” 하고 의도를 쉽게 파악할 수 있어요. 코드의 가독성이 확 올라가는 마법이죠.

💡 Tip

클래스 메서드를 사용해 다양한 방식으로 인스턴스를 생성하는 기법을 실무에서는 ‘팩토리 메서드 패턴’의 일종으로 흔히 사용해요. 생성자(__init__) 하나만으로는 표현하기 힘든 복잡한 생성 과정을 깔끔하게 정리할 수 있답니다.

투명 보드에 원의 넓이와 공식을 그리는 모습을 통해 세 가지 파이썬 메서드의 쓰임새를 비유한 실사 이미지

상속 — 부모의 기능을 물려받고 확장하기

프로젝트를 진행하며 코드를 계속 짜다 보면 아주 비슷한 기능이 여기저기 겹치는 귀찮은 상황이 생겨요. 예를 들어 강아지 클래스와 고양이 클래스를 따로 만든다고 생각해봐요. 둘 다 동물이니까 ‘이름’과 ‘나이’를 공통으로 가지고, 밥을 먹거나 ‘소리’를 낸다는 행동의 공통점이 있죠.

이럴 때 똑같은 코드를 매번 복사해서 붙여넣기 하지 않고, 공통된 코드를 ‘부모 클래스’로 크게 묶은 다음 자식 클래스들이 이를 그대로 물려받게 하는 멋진 기술을 상속이라고 해요. 게다가 물려받은 기능이 맘에 들지 않으면 자식의 입맛에 맞게 살짝 수정해서 덮어쓸 수도 있는데, 이것을 유식한 말로 메서드 오버라이딩이라고 부릅니다.

python
inheritance.py
# 공통 기능을 모아둔 듬직한 부모 클래스 Animal을 정의해요.
class Animal:
    # 동물의 기본 속성인 이름과 나이를 초기화해요.
    def __init__(self, name: str, age: int) -> None:
        self.name = name  # 이름을 저장해요.
        self.age = age    # 나이를 저장해요.

    # 동물이 소리를 내는 기본 메서드예요. 아직 어떤 동물인지 모르니 점만 찍어둘게요.
    def make_sound(self) -> str:
        return "..."

# Animal 클래스를 상속받는 자식 클래스 Cat을 만들어요. 상속받을 땐 괄호 안에 부모 이름을 쏙 적어요.
class Cat(Animal):
    # 고양이는 이름, 나이뿐만 아니라 품종(breed)이라는 고유 속성이 더 필요해요.
    def __init__(self, name: str, age: int, breed: str) -> None:
        # super()를 써서 부모의 __init__을 먼저 호출해 이름과 나이를 세팅해요.
        super().__init__(name, age)
        # 그런 다음 고양이만의 고유 속성인 품종을 추가로 저장해요.
        self.breed = breed 

    # 부모에게 물려받은 make_sound 메서드를 밋밋하지 않게 고양이식으로 덮어써요(오버라이딩).
    def make_sound(self) -> str:
        return "야옹~ Meow!"

# Cat 클래스로 luna 인스턴스를 만들어 소리를 들어볼까요?
luna = Cat("Luna", 2, "페르시안")
print(luna.make_sound()) # 출력: 야옹~ Meow!

여기서 가장 중요한 핵심 키워드는 바로 super()예요. 자식 클래스에서 새로운 초기화 작업(__init__)을 커스텀할 때는 반드시 super().__init__()을 불러서 부모가 하던 기본 세팅(이름, 나이 저장)을 무사히 마쳐야 해요. 이걸 빼먹으면 부모의 데이터가 제대로 생성되지 않아서 큰 버그가 생기거든요.

⚠️ Warning

상속을 너무 깊게, 여러 단계로 엮어버리면 나중에는 이 메서드가 대체 어느 부모에게서 왔는지 추적하기가 불가능에 가까워져요. 실무에서는 상속의 깊이를 최대 1~2단계 정도로 얕게 유지하는 것을 강력히 권장합니다.

거울에 비친 야생 조상의 모습을 바라보는 고양이를 통해 객체지향 상속의 개념을 시각화한 실사 이미지

실전 응용! 실무에서는 클래스를 어떻게 사용할까요?

지금까지 강아지, 고양이, 붕어빵 비유로 클래스를 배웠어요. 개념을 머릿속에 그리기엔 이보다 좋은 게 없지만, 막상 회사에 가서 업무로 코드를 짤 때는 분위기가 사뭇 달라요. 실무에서는 데이터(상태)와 그 데이터를 안전하게 조작하는 함수(행위)를 튼튼한 한 덩어리로 묶어서 보호해야 할 때 클래스를 적극적으로 도입해요.

실제 서비스 프로젝트에서 파이썬 OOP 원리를 가장 찰떡같이 보여주는 예시가 바로 쇼핑몰의 재고 관리 시스템이에요. 단순히 재고 숫자를 변수로 두면 누군가 실수로 마이너스 값으로 바꿔버릴 위험이 커요. 상품 객체를 만들고 정해진 규칙대로만 재고를 더하거나 빼도록 클래스로 단단히 묶어볼게요.

python
inventory_system.py
# 상품의 정보를 담고 재고를 철저히 관리하는 Product 클래스예요.
class Product:
    # 상품 이름과 초기 재고 수량을 받아 안전하게 초기화해요.
    def __init__(self, name: str, stock: int) -> None:
        self.name = name    # 상품 이름을 저장해요.
        self.stock = stock  # 현재 재고 수량을 저장해요.

    # 재고를 추가하는 메서드예요. 입고될 수량(amount)을 받아요.
    def add_stock(self, amount: int) -> None:
        if amount <= 0:
            print("입고 수량은 1개 이상이어야 해요!")
            return
        # 기존 재고에 정상적으로 입력받은 수량을 더해요.
        self.stock += amount
        print(f"📦 {self.name} {amount}개 입고 완료. (현재 재고: {self.stock})")

    # 재고를 차감하는 메서드예요. 고객이 구매할 수량(amount)을 받아요.
    def sell(self, amount: int) -> None:
        # 재고가 판매 수량보다 적으면 에러 메시지를 띄우고 판매를 단호하게 막아요.
        if self.stock < amount:
            print(f"🚨 재고가 부족해요! 남은 수량을 확인해주세요. (현재 재고: {self.stock})")
            return
        # 재고가 충분하면 판매 수량만큼만 정확하게 재고를 줄여요.
        self.stock -= amount
        print(f"💳 {self.name} {amount}개 판매 완료. (현재 재고: {self.stock})")

재고 관리 시스템 실행 프로세스

위에서 만든 클래스가 실제로 어떻게 동작하며 데이터를 보호하는지 순서대로 테스트해 볼까요?

1
초기 상품 인스턴스 등록

먼저 노트북이라는 상품 객체를 만들고 초기 재고를 10개로 넉넉하게 설정해 둡니다. (laptop = Product("노트북", 10))

2
정상적인 상품 판매 처리

고객이 노트북을 3개 구매합니다. laptop.sell(3)을 호출하면 남은 재고는 7개가 되어 깔끔하게 처리됩니다.

3
재고 초과 방어 로직 확인

누군가 대량 주문으로 남은 수량보다 많은 10개를 구매하려 합니다. laptop.sell(10)을 호출해도 클래스 내부 로직에 의해 재고가 마이너스가 되지 않고 안전하게 경고 메시지를 띄우며 방어해 냅니다.

이렇게 코드를 견고하게 짜면 외부에서 누군가 실수로 laptop.stock = -5처럼 말도 안 되는 재고 값을 강제로 덮어쓰는 대참사를 논리적으로 방지할 수 있어요. selladd_stock이라는 우리가 미리 허락해 둔 행위를 통해서만 재고를 조작하도록 강력한 규칙을 세울 수 있는 거죠.

깔끔한 물류 창고에서 노트북 재고를 태블릿으로 관리하는 모습을 통해 실무적인 클래스 활용을 표현한 실사 이미지

초보자가 가장 자주 하는 실수 BEST 3

처음 클래스 문법을 익히고 코드를 작성하다 보면 누구나 한 번쯤은 겪고 지나가는 아주 흔한 에러들이 있어요. 모니터에 빨간 글씨가 가득 떠서 당황스럽겠지만, 원리만 알면 1초 만에 허탈하게 고칠 수 있는 문제들이니 아래의 3가지 케이스를 잘 눈여겨봐 두세요.

python
common_mistakes.py
# 실수 1: 자식 클래스에서 상속받을 때 super()를 통째로 빼먹는 경우
class Dog(Animal):
    def __init__(self, name: str, age: int, breed: str):
        # 치명적 실수: 여기서 super().__init__(name, age)를 호출하지 않았어요.
        self.breed = breed
        # 이 상태에서 부모가 만들어주는 self.name을 쓰려고 하면 AttributeError가 빵 터져요!

# 실수 2: 인스턴스 메서드를 만들 때 self를 깜빡하는 경우
class Cat:
    # 앗, 첫 번째 매개변수로 지정석인 self를 적지 않았어요.
    def make_sound():  
        return "야옹"
    # 인스턴스를 만들고 cat.make_sound()를 호출하면 TypeError가 발생해요!
    # 파이썬은 항상 보이지 않게 첫 번째 인자로 인스턴스 자신을 던지기 때문이에요.

# 실수 3: 클래스 변수와 인스턴스 변수의 역할을 완전히 헷갈리는 경우
class Counter:
    count = 0  # 이건 클래스 변수라 이 클래스로 만든 모든 인스턴스가 1개의 데이터를 공유해요.
    
    def __init__(self):
        # 만약 각자 따로 놀아야 하는 독립적인 카운트를 원했다면 아래처럼 인스턴스 변수로 만들었어야 해요.
        self.my_count = 0 

이 세 가지는 정말 지겹도록 만나는 에러예요. 코드가 내 맘대로 돌지 않고 에러가 났을 때 이 세 가지 포인트만 먼저 점검해 봐도 대부분의 문제를 아주 빠르게 해결할 수 있을 거예요.

❗ 중요

파이썬이 뱉어내는 에러 메시지의 마지막 줄을 읽는 습관을 들이세요. TypeError에 "takes 0 positional arguments but 1 was given"이라는 말이 보이면 100% self를 빼먹은 거랍니다.

빨간 경고 빛이 비치는 모니터를 보며 코딩 실수를 분석하는 집중한 개발자의 모습을 담은 실사 이미지

자주 묻는 질문

Q. 붕어빵 비유로는 쉽게 이해했는데, 실제 실무 프로젝트에서는 클래스를 도대체 언제 사용해야 하나요?

데이터를 그저 단순하게 변수에 저장하는 걸 넘어서, 그 데이터(상태)와 데이터를 지지고 볶는 함수(행위)가 논리적으로 아주 끈끈하게 연결되어 있을 때 사용해요. 예를 들어 로그인한 유저의 복잡한 세션 정보를 담고 만료 여부를 깐깐하게 체크할 때나, 데이터베이스 연결을 굳건히 유지하면서 쿼리를 날릴 때, 그리고 방금 우리가 살펴본 재고 관리 시스템처럼 데이터를 외부에 함부로 노출하지 않고 안전하게 보호하면서 변경해야 할 때 클래스를 주력으로 도입합니다.

Q. 클래스나 메서드의 이름을 지을 때 반드시 지켜야 할 파이썬만의 표준 규칙이 있나요?

네, 파이썬 생태계에는 PEP 8이라는 아주 유명한 공식 스타일 가이드가 있어요. 이 규칙에 따르면 클래스 이름은 단어의 첫 글자를 대문자로 따박따박 적는 PascalCase(예: UserInfo, DatabaseConnection)를 써야 해요. 반면에 클래스 내부의 메서드나 일반 변수 이름은 모두 소문자로 쓰되 단어와 단어 사이를 밑줄(_)로 친절하게 연결하는 snake_case(예: calculate_total, is_valid)를 사용하는 것이 전 세계 파이썬 개발자들의 암묵적인 룰이에요.

Q. 최근 파이썬 코드를 보면 타입 힌트(Type Hint)를 아주 적극적으로 사용하는 것 같은데, 이유가 무엇인가요?

파이썬은 원래 데이터 타입을 굳이 명시하지 않아도 알아서 눈치껏 실행되는 아주 유연한 언어예요. 하지만 프로젝트 규모가 커지고 협업하는 동료가 늘어나면, 이 함수에 도대체 어떤 데이터가 들어가고 뭐가 튀어나오는지 코드만 봐서는 파악하기가 너무 힘들어져요. 타입 힌트를 쓰면 VSCode나 PyCharm 같은 똑똑한 에디터가 자동 완성을 기가 막히게 도와주고, 동료가 내 코드를 읽을 때 나의 의도를 단번에 파악할 수 있어요. 무엇보다 코드를 실제로 돌려보기 전에 아차 싶은 타입 에러를 에디터에서 미리 잡아주기 때문에, 장애를 막기 위해 실무에서는 거의 필수로 사용하고 있는 추세랍니다.

Q. 메서드를 정의할 때 실수로 self를 빠뜨리면 구체적으로 어떤 끔찍한 문제가 생기나요?

파이썬의 독특한 동작 방식 때문인데요, 우리가 luna.bark()처럼 평범하게 인스턴스 메서드를 호출할 때, 눈에 보이지는 않지만 파이썬 내부적으로는 luna라는 객체 자신을 첫 번째 인자로 쓱 밀어 넣어서 전달해요. 그런데 막상 메서드를 정의할 때 self를 안 적어두면, 파이썬은 "어? 나는 인자 하나를 분명히 보냈는데 받을 곳(매개변수)이 하나도 없네?" 하면서 즉시 TypeError를 화를 내며 뱉어내고 프로그램이 그 자리에서 뻗어버려요. 그래서 인스턴스 메서드를 만들 때는 첫 자리가 무조건 self의 전용 지정석이라고 외우고 계셔야 해요.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기