프로그래밍 언어/Python

@classmethod 는 왜 쓸까?

JMDev 2023. 9. 12. 14:43

@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를 상속해서 작성하는 코드의 차이점을 확인해볼 수 있다.