본문 바로가기
Code/Python

Python 클래스 톺아보기_2편(속성 접근 제어와 관리, 상속, 믹스인)

by hyelog 2025. 3. 16.

Python 클래스 속성 접근 제어와 관리, 상속, 믹스인

오늘은 지난 1편에 이어 Python 클래스 속성 접근 제어와 관리, 상속, 믹스인에 대해 알아보려고 한다.

클래스의 기초와 매직메서드를 정리해둔 1편을 먼저 보는것을 권장한다!

1. 클래스 속성 접근 제어

python에서는 Java나 C++과 같은 언어처럼 엄격한 접근 제어자(private, protected 등)가 없지만, 네이밍 컨벤션과 속성 디스크립터를 통해 속성에 대한 접근을 제어할 수 있다.

1-1. Public (공개 속성)

일반적으로 클래스 내부에서 정의된 속성은 기본적으로 공개(public)이다. 즉, 어디서든 접근이 가능하다.

class Person:
    def __init__(self, name, age):
        self.name = name  # 공개 속성
        self.age = age    # 공개 속성

p = Person("Alice", 30)
print(p.name)  # Alice
print(p.age)   # 30

1-2. Protected (보호된 속성)

접두사로 _ (단일 밑줄)를 사용하면 보호(protected) 속성이다. 이는 암묵적으로 "이 속성은 내부적으로만 사용하라"는 의미이며, 강제적인 제한이 되지는 않는다.

class Person:
    def __init__(self, name, age):
        self._name = name  # 보호된 속성
        self._age = age    # 보호된 속성

1-3. Private (비공개 속성)

접두사 __ (이중 밑줄)를 사용하면 비공개(private) 속성이 되어 클래스 외부에서는 직접 접근할 수 없다. 이는 "네임 맹글링(name mangling)"을 활용하여 속성명을 변경하는 방식이다.

여기서 네임 맹글링(name mangling)이란, 클래스 내에서 이름이 두 개의 언더스코어(__)로 시작하고 최대 한 개의 언더스코어로 끝나는 속성이나 메서드가 있을 때 발생한다.

간단히 말해, Python은 이런 이름을 _클래스이름__속성이름 형태로 자동 변환한다.

네임 맹글링의 주요 목적은:

  1. 클래스 상속 시 이름 충돌 방지: 서브클래스에서 같은 이름의 속성을 정의해도 부모 클래스의 속성과 충돌하지 않는다
  2. 의도적인 데이터 은닉: Python에서 진정한 의미의 private 접근 제어자는 없지만, 네임 맹글링은 간접적으로 비슷한 효과를 제공한다
class Person:
    def __init__(self, name, age):
        self.__name = name  # 비공개 속성
        self.__age = age    # 비공개 속성

p = Person("Alice", 30)
# print(p.__name)  # AttributeError 발생
print(p._Person__name)  # Alice (name mangling을 우회하여 접근 가능)

2. 속성 관리 (Property,Getter, Setter)

비공개 속성에 접근하기 위해 Getter 및 Setter 메서드를 사용할 수 있으며, property 데코레이터를 활용하여 보다 간결하게 구현할 수도 있다.

2-1. 프로퍼티(Property)

property는 클래스 속성의 값을 조회하거나 설정할 때 특정 로직을 실행할 수 있도록 도와주는 기능이다. @property 라는 데코레이터를 사용한다.

✅ 동작원리

  • @property를 사용하면 메서드를 마치 속성처럼 접근
  • @속성명.setter를 사용하면 속성 값을 변경할 때 추가적인 검증 로직 실행

2-2. 게터 (Getter)

Getter는 속성 값을 안전하게 가져올 때 사용됩니다. 주로 속성 값을 반환하는 역할을 한다.

✅ 활용방안

  • 속성 값을 읽을 때 추가적인 연산이나 변환을 수행할 경우 사용
  • 비공개 속성을 외부에서 읽을 수 있도록 허용하면서도 직접 수정은 막을 때 사용

2-3. 세터 (Setter)

Setter는 속성 값을 설정할 때 특정 검증이나 로직을 수행할 때 사용된다.

✅ 활용방안

  • 속성 값을 설정할 때 데이터 유효성을 검사할 경우 사용
  • 특정 값 변경 시 추가적인 부작용(로그 기록, 이벤트 발생 등)을 수행할 경우 사용
class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, new_name):
        if isinstance(new_name, str):
            self.__name = new_name
        else:
            raise ValueError("이름은 문자열이어야 합니다.")

p = Person("Alice", 30)
print(p.name)  # Getter
p.name = "Bob"  # Setter
print(p.name)  # Bob

3. 클래스 속성과 인스턴스 속성

클래스 속성은 모든 인스턴스가 공유하는 속성이고, 인스턴스 속성은 각 객체마다 개별적으로 관리된다.

3-1. 클래스 속성

  • 클래스 속성은 클래스 자체에 속한 변수이다.
  • 클래스 내부에서 메서드 밖에 정의되며, 해당 클래스의 모든 인스턴스가 공유한다.
  • 클래스 속성은 클래스 이름을 통해 직접 접근할 수 있고, 인스턴스를 통해서도 접근이 가능하다.

✅ 클래스 속성 활용

  • 상수 정의: 모든 인스턴스가 공유해야 하는 값을 정의할 때 유용
  • 기본값 설정: 모든 인스턴스의 기본값으로 사용
  • 카운터 구현: 클래스의 인스턴스 개수를 추적할 때 활용
  • 캐싱: 모든 인스턴스가 공유하는 데이터를 저장할 때 사용

3-2. 인스턴스 속성

  • 인스턴스 속성은 각 객체(인스턴스)에 속한 변수이다.
  • 일반적으로 __init__ 메서드 내에서 self.속성명으로 정의된다.
  • 각 인스턴스마다 독립적인 값을 가지며, 인스턴스 이름을 통해서만 접근할 수 있다.

✅ 속성 검색 순서

파이썬은 속성을 찾을 때 다음 순서로 검색한다:

  1. 인스턴스 속성을 먼저 확인한다.
  2. 인스턴스 속성에 없으면 클래스 속성을 찾는다.
  3. 클래스 속성에도 없으면 부모 클래스의 속성을 찾는다.
class Car:
    brand = "Hyundai"  # 클래스 속성

    def __init__(self, model):
        self.model = model  # 인스턴스 속성

car1 = Car("Sonata")
car2 = Car("Elantra")
print(car1.brand, car1.model)  # Hyundai Sonata
print(car2.brand, car2.model)  # Hyundai Elantra

4. 상속과 메서드 오버라이딩

4-1. 상속

파이썬에서 상속(Inheritance)이란 기존 클래스(부모 클래스)의 속성과 메서드를 새로운 클래스(자식 클래스)가 물려받아 사용하는 개념이다. 이를 통해 코드의 재사용성을 높이고, 계층적 관계를 구현할 수 있다.

class Animal:
    def speak(self):
        return "소리를 낸다"

class Dog(Animal):
    def speak(self):
        return "멍멍"

class Cat(Animal):
    def speak(self):
        return "야옹"

dog = Dog()
cat = Cat()
print(dog.speak())  # 멍멍
print(cat.speak())  # 야옹
  • 자식 클래스는 부모 클래스를 괄호 안에 명시하여 상속받는다.
  • 자식 클래스는 부모 클래스의 모든 속성과 메서드를 물려받는다.
  • 자식 클래스에서 정의되지 않은 속성이나 메서드는 부모 클래스에서 찾는다

4-2. 메서드 오버라이딩 (Method Overriding)

메서드 오버라이딩이란 부모 클래스에서 정의된 메서드를 자식 클래스에서 같은 이름으로 다시 정의(재정의)하는 것을 의미한다. 이를 통해 부모 클래스의 동작을 변경하거나 확장할 수 있다.

만약 부모 클래스의 기능을 유지하면서 추가적인 기능을 더하고 싶다면 super()를 활용하면 된다.

class Parent:
    def show_info(self):
        print("부모 클래스의 정보입니다.")

class Child(Parent):
    def show_info(self):  # 부모 클래스의 메서드를 재정의
        print("자식 클래스에서 재정의한 정보입니다.")

# 사용 예시
child = Child()
child.show_info()  # "자식 클래스에서 재정의한 정보입니다." 출력

class Parent:
    def show_info(self):
        print("부모 클래스의 정보입니다.")

class Child(Parent):
    def show_info(self):
        super().show_info()  # 부모 클래스의 메서드 호출
        print("추가적으로 자식 클래스의 정보도 포함됩니다.")

# 사용 예시
child = Child()
child.show_info() # "부모 클래스의 정보입니다.\\n추가적으로 자식 클래스의 정보도 포함됩니다."

5. 믹스인 (Mixin)

믹스인(Mixin)은 클래스에 추가 기능을 제공하기 위해 설계된 특별한 형태의 클래스이다.

클래스의 기능을 확장하는 방법 중 하나로, 독립적인 기능을 가진 클래스를 만들어 여러 클래스에서 재사용할 수 있도록 한다.

즉, 상속을 활용하되 부모 클래스의 역할을 최소화하고, 특정 기능만 추가하는 방식이다.

믹스인은 단독으로 사용되지 않으며, 기능을 제공하는 용도로만 사용되기 때문에 보통 다중 상속과 함께 사용된다.

  • 일반적으로 Mixin 클래스는 인스턴스 변수를 직접 정의하지 않고 메서드만 포함한다.
  • 다른 클래스에 기능을 추가하기 위한 용도로 사용된다.
  • Mixin 클래스 자체로는 인스턴스를 생성하지 않는다.
  • 다중 상속을 활용해 다른 클래스에 기능을 추가하는 방식으로 사용된다.
class LogMixin:
    def log(self, message):
        print(f"[LOG]: {message}")

class User(LogMixin):
    def __init__(self, name):
        self.name = name
        self.log(f"{self.name} 유저가 생성되었습니다.")

u = User("Alice")
# 출력: [LOG]: Alice 유저가 생성되었습니다.

댓글