Python @staticmethod, @classmethod 차이 with 클래스 변수, 인스턴스 변수

# 목적

Python을 사용한 업무를 진행할 때, 대부분의 메소드에 @staticmethod와 @classmethod를 사용하고 있는데, 두 어노테이션이 갖는 의미와 차이점, 사용 방법 등을 정확하게 정리하는 것이 이번 포스팅의 목적이다.

 

추가로, 업무하면서 두 어노테이션을 쓰다 인스턴스 변수와 클래스 변수를 생각 안하고 짯던 경험이 있어서, 이슈 상황과 차이점 또한 간단히 정리하고자 한다.


@staticmethod

https://docs.python.org/3/library/functions.html#staticmethod

 

Built-in Functions

The Python interpreter has a number of functions and types built into it that are always available. They are listed here in alphabetical order.,,,, Built-in Functions,,, A, abs(), aiter(), all(), a...

docs.python.org

 

Python 공식 문서를 확인했을 때, @staticmethod Annotion의 목적은 해당 메소드를 정적 메소드로 변경하는 것이다.


정적 메소드는 첫번째 매개변수에 self, cls를 선언할 필요가 없고, 사용할 매개변수만 선언하면 된다.

class C:
    @staticmethod
    def f(arg1, arg2, argN): ...

@classmethod

https://docs.python.org/3/library/functions.html#classmethod

 

Built-in Functions

The Python interpreter has a number of functions and types built into it that are always available. They are listed here in alphabetical order.,,,, Built-in Functions,,, A, abs(), aiter(), all(), a...

docs.python.org

 

정의는 해당 메소드를 클래스 메소드로 변경하는 것이다.
클래스 메소드는 인스턴스 메소드처럼, 첫번째 매개변수로 자신(cls)을 선언해야한다.

파이참에선 self로 사용하면 IDE에서 경고 Linting을 제공한다.

class C:
    @classmethod
    def f(cls, arg1, arg2): ...

# 차이점

두 어노테이션의 정의만 놓고 봤을 때, 명칭 상 정적 메소드와 클래스 메소드라는 차이가 존재한다.
코드 레벨의 차이점은 메소드를 선언할 때 첫번째 매개변수를 자신으로 지정하는지의 유무(cls가 매개변수로 있는지)이다.

사실 이 부분을 처음 봤을 때 그래서 이게 무슨 차이야? 하는 생각이 들었었는데, 실제로 메소드를 사용하는 코드에선 차이가 존재하지 않는다.

 

결국 Python의 클래스 메소드와 정적 메소드 모두, 일반적인 언어의 정적 메소드와 동일한 의미를 갖는다.

Java에서 메소드 앞에 static 키워드를 사용하면, 인스턴스를 선언하지 않고 메소드를 사용하는 것이 가능한데, 이를 정적 메소드라고 부른다.

 

@staticmethod와 @classmethod 모두 인스턴스 생성 없이 메소드를 사용하고자 할 때 사용할 수 있다. 앞선 공식문서의 예시 클래스 C의 메소드를 사용하는 방법은 아래와 같다.

C.f(arg1, arg2)

즉, 두 어노테이션 모두 Python에서 정적 메소드를 만든다는 동일한 목적을 갖고 있고, 메소드를 사용할 때도 차이가 존재하지 않는다.

두 어노테이션의 차이는 해당 메소드의 기능을 구현할 때 발생하는데, 메소드의 기능에 따라 어노테이션을 알맞게 사용해야 한다.

 

간단한 예시를 통해 해당 차이를 확인해보자.

from datetime import datetime
class Clock:
	model_type = "알람 시계"
	
	@staticmethod
	def get_now() :
		return datetime.now()
		
	@classmethod
	def get_model_type(cls):
		return cls.model_type

 

시계라는 클래스가 현재 날짜 및 시간을 알려주는 기능과 자신의 타입을 반환하는 기능이 있다고 가정했을 때, 두 어노테이션의 차이가 발생한다.

결론부터 확인하면, 클래스 변수에 접근해야하는 정적 메소드는 @classmethod 어노테이션을 사용해야하고, 클래스 변수에 접근할 필요가 없는 정적 메소드는 @staticmethod를 사용하면 된다.

즉, @classmethod는 메소드 파라미터에 cls를 필수적으로 선언해야하는데, cls 변수는 클래스를 의미하므로, 클래스 변수에 접근이 가능하다. 또 클래스의 다른 메소드를 사용할 수 있게 된다.

하지만, @staticmethod는 인스턴스 메소드와 @classmethod와 다르게, 클래스에 접근할 수 있는 파라미터를 선언할 수 없고 따라서 클래스 변수에 접근도 불가능하다.

두 메소드를 사용할 땐, 인스턴스 생성 없이 바로 사용이 가능하다.

Clock.get_now()
Clock.get_model_type()

만약 어노테이션을 붙이지 않으면 다음과 같은 차이가 존재한다.

def get_model_type2(self):
	return self.model_type

### 
# 방법 1
C().get_model_type2()

# 방법 2
clock = Clock()
clock.get_model_type2()

두가지로 사용이 가능한데, 방법 1처럼 사용하는 메소드는 인스턴스의 상태와 무관한 메소드일 것이기 가능성이 커서, 유지보수와 협업의 측면에서 정적 메소드로 명시적으로 선언하여 사용하는 것이 좋다고 생각된다.


# 클래스 변수와 인스턴스 변수

두 변수는 말 그대로 변수를 접근할 수 있는 위치에 차이점이 있다. (또는 메모리에 올라가는 시점)

Python에선 클래스 변수는 클래스와 인스턴스 모두 접근이 가능하지만, 인스턴스 변수는 인스턴스에서만 접근이 가능하다.

 

이를 앞선 어노테이션의 개념과 확장해보면, @classmethod를 사용한 메소드에선 인스턴스 변수 접근이 불가능하다. 

사실 당연한 개념이지만, 이게 왜 안되지? 라고 생각했던, 멍청한 자신을 반성하는(이 글을 작성하게 된) 계기가 되었다.
일단 어노테이션 붙이고 메소드를 짰던 습관이 생겼던 터라, 이런 상황을 맞이했었다.

문제가 됐던 상황을 예시로 확인해보면 다음과 같다.

class Clock:
	model_type = "알람시계"
	name: int

	def __init__(self, name):
		self.name = name

	@classmethod
	def get_model_type(cls):
		return cls.model_type

	@classmethod
	def get_name(cls):
		return cls.name

clock = Clock(name)
clock.get_name()

이렇게 코드를 짜고 돌려보면, `AttributeError: type object 'Clock' has no attribute 'name'` 에러를 반환환다.

원인은 앞서 명시한 것 처럼, 정적 메소드인 get_name()에서 인스턴스 변수를 접근할 수 없기 때문이다.
이를 의도에 맞게 수정하려면, @classmethod를 지우고 인스턴스 메소드로 작성해야한다.

 

class Clock:
	model_type = "알람시계"
	name: int

	def __init__(self, name):
		self.name = name

	@classmethod
	def get_model_type(cls):
		return cls.model_type

	def get_name(self): ## 수정한 메소드
		return self.name

clock = Clock(name)
print(clock.get_name())

이렇게 코드를 수정하면, 목적에 맞게 인스턴스 메소드가 되고, 인스턴스 변수에 접근이 가능하다.

 

Python은 엄격하지 않아서, 그냥 사용해도 인스턴스에서 제한 없이 클래스 변수 접근과 정적 메소드 사용이 가능하다.


# 정리

  • @staticmethod와 @classmethod는 Python에서 정적 메소드로 사용하기 위한 어노테이션이다.
  • @staticmethod와 @classmethod의 차이점은 해당 메소드가 클래스 변수의 접근이 할 수 있는지 아닌지에 있다.
  • @classmethod를 선언한 메소드는 인스턴스 변수에 접근할 수 없다.
728x90
반응형