@classmethod 를 왜 쓸까? 에 대해서
- 팩토리 매서드로 사용
- 상속에서 활용
- 정적메서드와 구분
- 클래스 변수 수정
위에 있는 특징들을 설명해주었지만, 나는 인스턴스 함수와의 구분을 하지 못하겠다.
추가적으로 인스턴스 함수와 클래스 함수를 왜 따로 구분해줘야 하는지도 잘 모르겠다.
라고 생각했고 이 둘에 대한 차이점을 찾아보니 이러했다.
class MyClass:
class_variable = "I'm a class variable."
def __init__(self, value):
self.instance_variable = value
def set_instance_variable(self, value):
self.instance_variable = value
@classmethod
def set_class_variable(cls, value):
cls.class_variable = value
# 인스턴스 생성
a = MyClass("a's instance variable")
b = MyClass("b's instance variable")
# 클래스 변수 변경
MyClass.set_class_variable("Changed class variable")
print(a.class_variable) # 출력: Changed class variable
print(b.class_variable) # 출력: Changed class variable
# 인스턴스 변수 변경
a.set_instance_variable("Changed a's instance variable")
print(a.instance_variable) # 출력: Changed a's instance variable
print(b.instance_variable) # 출력: b's instance variable
간략하게 위 코드로 부터 인스턴스 변수, 클래스 변수가 어떻게 변경되고, 그에 대한 차이점을 볼 수 있다.
나는 클래스 함수를 통해 클래스 변수가 어떻게 변경되는지에 대한 이해도가 생겼다.
하지만 여전히 왜 클래스 변수를 변경해줘야 하는지에 대한 의문이 풀리지 않았다.
다시 생각해서, 클래스 변수를 왜 써야 하는가에 대해 고민해보았다.
생각한 결과, 클래스 변수는 자식과 부모 객체 간의 공통된 데이터들을 공유하기 위해서 유용할 것 같다고 생각이 들었다.
class Car:
total_cars = 0 # 이 변수는 Car 클래스와 그 모든 하위 클래스의 인스턴스들에 의해 공유됩니다.
def __init__(self, brand, model):
self.brand = brand
self.model = model
Car.total_cars += 1
@classmethod
def get_total_cars(cls):
return cls.total_cars
class ElectricCar(Car): # ElectricCar는 Car 클래스를 상속받습니다.
def __init__(self, brand, model, battery_size):
super().__init__(brand, model)
self.battery_size = battery_size
# 인스턴스 생성
car1 = Car("Toyota", "Camry")
car2 = Car("Honda", "Civic")
e_car1 = ElectricCar("Tesla", "Model S", 100)
print(Car.get_total_cars()) # 출력: 3, 모든 차량의 수를 출력합니다.
print(ElectricCar.get_total_cars()) # 출력: 3, ElectricCar는 Car의 total_cars를 공유하기 때문에 동일한 값을 출력합니다.
위 코드에서 클래스 변수는 부모와 자식, 상속관계에서 데이터의 공유를 원활하게 해줄 수 있음을 볼 수 있다.
그런데, 여러 예시들을 찾아보던 중 내 머리를 어지럽히는 녀석이 등장했다.
class Person:
count = 0
def __init__(self, name):
self.name = name
Person.count += 1
@classmethod
def how_many(cls):
return cls.count
위 코드를 보면 왜 cls 를 써야 하는지 이유를 다시 묻게된다. 왜냐하면 생성자에서 Person 이라는 객체에 접근이 가능해졌기 떄문이다.
따라서 return cls.count 를 return Person.count 로 바꿔 사용해도 된다는 것이지 않은가???
그렇게 생각한 나는 Person.count로 리팩토링을 하고 난 한참 뒤에 Person 이라는 클래스명이 맘에 들지 않았다.
그래서 Persony 로 변경하였고, 흡족한 나는 그대로 commit을 해버린다. 그리고 발생하는 에러.
그 외에도 Person를 직접 바라보게 작성한다면 자식 객체에게도 의도한 동작이 발생하지 않을 수 있다.
class Person:
count = 0
def __init__(self, name):
self.name = name
Person.count += 1
@classmethod
def how_many(cls):
return cls.count
class Student(Person):
count = 0 # Student 클래스에 대한 별도의 count
def __init__(self, name, grade):
super().__init__(name)
self.grade = grade
Student.count += 1 # 여기에서는 Student의 count만 증가시킵니다.
# 만약 Student 클래스에 how_many 메서드를 오버라이드 하지 않았다면
# cls가 Student를 가리키므로, Person의 how_many를 사용하게 되고,
# 이는 Student의 count를 반환하게 됩니다.
# 인스턴스 생성
p1 = Person("John")
p2 = Person("Doe")
s1 = Student("Alice", "1st Grade")
s2 = Student("Bob", "2nd Grade")
print(Person.how_many()) # 출력: 4
print(Student.how_many()) # 출력: 2
# 만약 @classmethod와 cls를 사용하지 않고, Person.count로 직접 참조했다면
# Student 클래스의 how_many 메서드는 항상 4를 반환하게 됩니다.
위 코드에서 말해주듯, Person를 직접바라보게 작성하는 코드와 cls를 상속해서 작성하는 코드의 차이점을 확인해볼 수 있다.
'프로그래밍 언어 > Python' 카테고리의 다른 글
GIL를 무조건 신뢰하면 안되는 이유( feat.스레드 감수성 ) (0) | 2023.09.15 |
---|---|
두 코드의 차이점을 설명하시요( feat. Python,쓰레드, GIL, 병렬 I/O) (0) | 2023.09.15 |
클래스를 디버깅해보자(feat.메타클래스,클래스 데코레이터) (0) | 2023.09.15 |
디스크립터 __set_name__ 의 활용방법 (0) | 2023.09.14 |
메타클래스와 init_subclass (0) | 2023.09.12 |