클래스 심화
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 메서드를 호출합니다. 이러한 접근법은 런타임에 메서드나 속성의 존재를 확인하기 때문에 코드가 더욱 유연해집니다.