본문 바로가기

클래스 심화

클래스 메서드('@classmethod')는 첫 인자로 클래스(cls)를 받아 클래스 상태를 변경하고, 정적 메서드('@staticmethod')는 self/cls 없이 독립적으로 동작합니다. '@property'는 메서드를 속성처럼 접근하게 합니다.

1. 클래스 메서드와 정적(static) 메서드

Python의 클래스에는 일반적인 인스턴스 메서드 외에도 클래스 메서드와 정적(static) 메서드라는 두 가지 특별한 유형의 메서드가 있습니다. 이들 메서드는 @classmethod와 @staticmethod 데코레이터를 사용하여 정의됩니다.

1.1 클래스 메서드 (@classmethod)

클래스 메서드는 클래스에 작용하는 메서드입니다. 클래스 메서드는 첫 번째 인자로 클래스 자체를 받습니다. 일반적으로 이 인자를 cls로 명명합니다. 클래스 메서드는 클래스 상태를 변경하는 데 사용될 수 있습니다.

class MyClass:
    count = 0

    @classmethod
    def increment(cls):
        cls.count += 1

MyClass.increment()
print(MyClass.count)  # 출력: 1

1.2 정적(static) 메서드 (@staticmethod)

정적(static) 메서드는 클래스나 인스턴스와는 독립적으로 작동하는 메서드입니다. 정적 메서드는 self나 cls 같은 특별한 첫 번째 인자를 받지 않습니다. 정적 메서드는 주로 클래스와 연관은 있지만 인스턴스나 클래스 상태에는 접근하지 않는 메서드에 사용됩니다.

class MyClass:
    @staticmethod
    def my_method(x, y):
        return x + y

print(MyClass.my_method(5, 3))  # 출력: 8

아래와 같이 과목에 관련된 클래스를 만들었다고 했을 때 academic_warning에서는 self를 이용한 접근이 되지 않습니다.

class CompletionList:
    def __init__(self):
        self.subject_list = []

    def show(self):
        print(self.subject_list)

    def append(self, subject):
        self.subject_list.append(subject)

    @staticmethod
    def academic_warning(subject):
        '''
        학점이 1.5 미만이면 학사경고 대상입니다.
        정적 메서드이므로 내부에서 클래스 변수나 인스턴스 변수를 수정할 수 없습니다.
        '''
        return subject['grades'] < 1.5

c = CompletionList()

subject1 = {'name': 'Python', 'grades': 2.5}
subject2 = {'name': 'HTML/CSS', 'grades': 3.5}

c.append(subject1)
c.append(subject2)
c.show()

print(c.academic_warning(subject1)) # 2.5는 1.5 이상이므로 False

1.3 self를 반환해 메서드 체이닝 만들기

'타입' 챕터에서 ' Hello '.strip().lower()처럼 메서드를 이어 붙이는 방법을 살펴봤습니다. 그때 체이닝이 되려면 앞 메서드가 다음 메서드를 가진 값을 돌려주어야 한다고 했습니다. 우리가 만든 클래스에서도 같은 규칙이 적용됩니다. 메서드가 self를 반환하면 그 결과에 다시 자기 자신의 메서드를 호출할 수 있습니다.

class Calculator:
    def __init__(self, value):
        self.value = value

    def add(self, other):
        self.value += other
        return self  # 자기 자신을 돌려줍니다.

    def subtract(self, other):
        self.value -= other
        return self

    def multiply(self, other):
        self.value *= other
        return self

    def get_value(self):
        return self.value

calc = Calculator(1)
result = calc.add(2).subtract(1).multiply(3).get_value()
print(result)  # 결과: 6

add, subtract, multiply가 모두 self를 반환하기 때문에 계속 이어 붙일 수 있습니다. 반면 get_value는 int를 반환하므로 그 뒤에는 int가 가진 메서드만 올 수 있습니다.

class Calculator:
    def __init__(self, value):
        self.value = value

    def add(self, other):
        self.value += other
        return self

    def get_value(self):
        return self.value

# get_value 다음에는 int의 메서드가 옵니다.
print(Calculator(1).add(9).get_value().bit_length())

return self를 빠뜨리면 메서드는 None을 반환하고, 그 다음 호출에서 AttributeError가 납니다. 체이닝을 지원하는 클래스를 설계할 때 가장 흔하게 하는 실수이니 주의해주세요.

2. 속성 접근자와 덕 타이핑

2.1 속성 접근자 (Property)

Python에서는 @property 데코레이터를 사용하여 클래스의 메서드를 속성처럼 접근할 수 있게 만들 수 있습니다. 이를 통해 객체의 내부 상태를 보호하고, 특정 속성에 대한 접근을 제어할 수 있습니다.

class Person:
    def __init__(self, first_name, last_name):
        self._first_name = first_name
        self._last_name = last_name

    @property
    def full_name(self):
        return f'{self._first_name}{self._last_name}'

licat = Person('li', 'cat')
print(licat._first_name)
print(licat._last_name)
print(licat.full_name)
# print(licat.full_name())

이 클래스의 인스턴스에서 full_name 속성에 접근하면, _first_name과 _last_name을 합친 문자열을 반환합니다. @property 데코레이터가 없다면, full_name() 메서드를 직접 호출해야 합니다.

2.2 속성 접근자의 값 설정

@property 데코레이터를 사용하면, 속성에 값을 할당할 때 setter 메서드를 사용할 수 있습니다. setter 메서드는 @property 데코레이터와 동일한 이름을 사용하며, @property 데코레이터의 이름 뒤에 .setter를 붙여 정의합니다. 이를 통해 속성에 대한 값을 설정할 때 추가적인 검증을 수행할 수 있습니다.

class Person:
    def __init__(self, name):
        self.name = name
        self._age = 0

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("나이는 음수일 수 없습니다.")
        self._age = value

# 사용 예제
person = Person("홍길동")

# age 속성 설정
person.age = 25
print(f"{person.name}의 나이는 {person.age}세입니다.")

2.3 덕 타이핑 (Duck typing)

"오리처럼 걷고, 오리처럼 꽥꽥대면 그것은 오리다."는 덕 타이핑의 원리를 잘 나타내는 문장입니다.

Duck Typing - 'If it walks like a duck and it quacks like a duck, then it must be a duck’

Duck typing

덕 타이핑은 실제 타입은 상관치 않고 구현된 메서드로 확인하는 방법입니다. 좀 더 어려운 말로 객체의 변수 및 메서드의 집합이 객체의 타입을 결정하는 개념을 의미합니다. 이를 통해 유연한 인터페이스를 구현할 수 있습니다.

class Duck:
    def quack(self):
        print('꽥꽥!')

class Person:
    def quack(self):
        print("안녕하세요!")

def quack(obj):
    obj.quack()

duck = Duck()
person = Person()

quack(duck)  # 출력: 꽥꽥!
quack(person)  # 출력: 안녕하세요!

위 코드에서 quack(obj) 함수는 obj가 실제로 Duck 타입이든 아니든 상관하지 않고, quack 메서드를 호출합니다. 이러한 접근법은 런타임에 메서드나 속성의 존재를 확인하기 때문에 코드가 더욱 유연해집니다.

클래스 심화 - 견고한 파이썬 | 위니버시티