24. 클래스 속성과 정적, 클래스 메서드
1. 키워드
- 클래스 속성(Class Attribute)과 인스턴스 속성(Instance Attribute)
- 비공개 클래스 속성(Private Class Attribute)
- 정적 메서드(Static Method)
- 데코레이터(Decorator)
2. 클래스 속성과 정적, 클래스 메서드 사용하기
- 이번에는 클래스에 속해 있는 클래스 속성에 대해 알아보자.
- 그리고 인스턴스를 만들지 않고 클래스로 호출하는 정적 메서드와 클래스 메서드도 사용해 보자.
3. 클래스 속성과 인스턴스 속성 알아보기
- 속성에는 클래스 속성과 인스턴스 속성 두 가지 종류가 있다.
__init__
메서드에서 만들었던 속성은 인스턴스 속성이다.
1) 클래스 속성 사용하기
- 클래스 속성은 다음과 같이 클래스에 바로 속성을 만든다.
- 다음과 같이
Person
클래스에 바로bag
속성을 넣고,put_bag
메서드를 만들고, 인스턴스 두 개를 만든 뒤 각각put_bag
메서드를 사용한다.
class Person:
bag = []
def put_bag(self, stuff):
self.bag.append(stuff)
james = Person()
james.put_bag("책")
maria = Person()
maria.put_bag("열쇠")
print(james.bag) # ['책', '열쇠']
print(maria.bag) # ['책', '열쇠']
james
와maria
인스턴스를 만들고 각자put_bag
메서드로 물건을 넣었는데,james.bag
과maria.bag
을 출력해 보면 넣었던 물건이 합쳐져서 나온다.- 즉, 클래스 속성은 클래스에 속해 있으며 모든 인스턴스에서 공유한다.
put_bag
메서드에서 클래스 속성bag
에 접근할 때self
를 사용했다.- 하지만
self
는 현재 인스턴스를 뜻하므로 클래스 속성을 지칭하기에는 조금 모호하다.
- 그래서 클래스 속성에 접근할 때는 다음과 같이 클래스 이름으로 접근하면 좀 더 코드가 명확해진다.
Person.bag
이라고 하니 클래스Person
에 속한bag
속성이라는 것을 바로 알 수 있다.
- 마찬가지로 클래스 바깥에서도 다음과 같이 클래스 이름으로 클래스 속성에 접근하면 된다.
속성, 메서드 이름을 찾는 순서
- 파이썬에서는 속성, 메서드 이름을 찾을 때 인스턴스, 클래스 순으로 찾는다.
- 그래서 인스턴스 속성이 없으면 클래스 속성을 찾게 되므로
james.bag
,maria.bag
도 문제없이 동작한다. - 겉보기에는 인스턴스 속성을 사용하는 것 같지만 실제로는 클래스 속성이다.
- 인스턴스와 클래스에서
__dict__
속성을 출력해 보면 현재 인스턴스와 클래스의 속성을 딕셔너리로 확인할 수 있다.
>>> james.__dict__
{}
>>> Person.__dict__
mappingproxy({'__module__': '__main__', 'bag': ['책', '열쇠'], 'put_bag': <function Person.put_bag at ...>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None})
james.bag
을 사용했을 때 클래스 속성을 찾는 과정은 다음과 같다.
2) 인스턴스 속성 사용하기
- 가방을 여러 사람이 공유하지 않게 하려면
bag
을 인스턴스 속성으로 만들면 된다.
class Person:
def __init__(self):
self.bag = []
def put_bag(self, stuff):
self.bag.append(stuff)
james = Person()
james.put_bag("책")
maria = Person()
maria.put_bag("열쇠")
print(james.bag) # ['책']
print(maria.bag) # ['열쇠']
james.bag
과maria.bag
을 출력해 보면 각자 넣은 물건만 출력된다.- 즉, 인스턴스 속성은 인스턴스별로 독립되어 있으며 서로 영향을 주지 않는다.
- 이제 클래스 속성과 인스턴스 속성의 차이점을 정리해 보자.
1] 클래스 속성
- 모든 인스턴스가 공유
- 인스턴스 전체가 사용해야 하는 값을 저장할 때 사용
2] 인스턴스 속성
- 인스턴스별로 독립되어 있음
- 각 인스턴스가 값을 따로 저장해야 할 때 사용
3) 비공개 클래스 속성 사용하기
- 클래스 속성도 비공개 속성을 만들 수 있다.
- 클래스 속성을 만들 때
__속성
과 같이__
(밑줄 두 개)로 시작하면 비공개 속성이 된다. - 따라서 클래스 안에서만 접근할 수 있고, 클래스 바깥에서는 접근할 수 없다.
- 즉, 클래스에서 공개하고 싶지 않은 속성이 있다면 비공개 클래스를 사용해야 한다.
class Knight:
__item_limit = 10
def print_item_limit(self):
print(Knight.__item_limit)
x = Knight()
x.print_item_limit() # 10
print(Knight.__item_limit) # AttributeError: type object 'Knight' has no attribute '__item_limit'
- 실행을 해보면 클래스
Knight
의 비공개 클래스 속성__item_limit
은 클래스 안의print_item_limit
메서드에서만 접근할 수 있고, 클래스 바깥에서 접근하면 에러가 발생한다. - 이처럼 비공개 클래스 속성은 클래스 바깥으로 드러내고 싶지 않은 값에 사용한다.
클래스와 메서드의 독스트링 사용하기
- 함수와 마찬가지로 클래스와 메서드도 독스트링을 사용할 수 있다.
- 다음과 같이 클래스와 메서드를 만들 때
:
(콜론) 바로 다음 줄에""" """
(큰따옴표 세 개) 또는''' '''
(작은따옴표 세 개)로 문자열을 입력하면 된다.
class Person:
"""사람 클래스입니다."""
def greeting(self):
"""인사 메서드입니다."""
print("Hello")
print(Person.__doc__) # 사람 클래스입니다.
print(Person.greeting.__doc__) # 인사 메서드입니다.
maria = Person()
print(maria.greeting.__doc__) # 인사 메서드입니다.
- 그리고 클래스의 독스트링은
클래스.__doc__
형식으로 사용하고, 메서드의 독스트링은클래스.메서드.__doc__
또는인스턴스.메서드.__doc__
형식으로 사용한다.
4. 정적 메서드 사용하기
- 이번에는 인스턴스를 통하지 않고 클래스에서 바로 호출할 수 있는 정적 메서드와 클래스 메서드에 대해 알아보자.
- 먼저 정적 메서드는 다음과 같이 메서드 위에
@staticmethod
를 붙이며, 이때 정적 메서드는 매개변수self
를 지정하지 않는다.
@staticmethod
처럼 앞에@
이 붙은 것을 데코레이터라고 하며 메서드(함수)에 추가 기능을 구현할 때 사용한다.
- 간단하게 덧셈과 곱셈을 하는 클래스를 만들어보자.
class Calc:
@staticmethod
def add(a, b):
print(a + b)
@staticmethod
def mul(a, b):
print(a * b)
Calc.add(10, 20) # 30
Calc.mul(10, 20) # 200
Calc
클래스에서@staticmethod
를 붙여서add
메서드와mul
메서드를 만들었다.
- 정적 메서드를 호출할 때는 다음과 같이 클래스에서 바로 메서드를 호출하면 된다.
- 정적 메서드는
self
를 받지 않으므로 인스턴스 속성에는 접근할 수 없다. - 그래서 보통 정적 메서드는 인스턴스 속성, 인스턴스 메서드가 필요없을 때 사용한다.
- 정적 메서드는 메서드의 실행이 외부 상태에 영향을 끼치지 않는 순수 함수(Pure Function)를 만들 때 사용한다.
- 순수 함수는 부수 효과(Side Effect)가 없고 입력 값이 같으면 언제나 같은 출력 값을 반환한다.
- 즉, 정적 메서드는 인스턴스의 상태를 변화시키지 않는 메서드를 만들 때 사용한다.
파이썬 자료형의 인스턴스 메서드와 정적 메서드
- 파이썬의 자료형도 인스턴스 메서드와 정적, 클래스 메서드로 나뉘어져 있다.
- 예를 들어 다음과 같이 세트에 요소를 더할 때는 인스턴스 메서드를 사용하고, 합집합을 구할 때는 정적 메서드를 사용하도록 만들어져 있다.
>>> a = {1, 2, 3, 4}
>>> a.update({5})
>>> a
{1, 2, 3, 4, 5}
>>> set.union({1, 2, 3, 4}, {5})
{1, 2, 3, 4, 5}
- 이처럼 인스턴스의 내용을 변경해야 할 때는
update
와 같이 인스턴스 메서드로 작성하면 되고, 인스턴스 내용과는 상관없이 결과만 구하면 될 때는set.union
과 같이 정적 메서드로 작성하면 된다.
5. 클래스 메서드 사용하기
- 이번에는 정적 메서드와 비슷하지만 약간의 차이점이 있는 클래스 메서드를 사용해 보자.
- 클래스 메서드는 다음과 같이 메서드 위에
@classmethod
를 붙이며, 이때 클래스 메서드는 첫 번째 매개변수에cls
를 지정해야 한다.
- 사람 클래스
Person
을 만들고 인스턴스가 몇 개 만들어졌는지 출력하는 메서드를 만들어보자.
class Person:
count = 0
def __init__(self):
Person.count += 1
@classmethod
def print_count(cls):
print(f"{cls.count}명 생성되었습니다.")
james = Person()
maria = Person()
Person.print_count() # 2명 생성되었습니다.
- 먼저 인스턴스가 만들어질 때마다 숫자를 세어야 하므로
__init__
메서드에서 클래스 속성count
에1
을 더해 준다. - 물론 클래스 속성에 접근한다는 것을 명확하게 하기 위해
Person.count
와 같이 만들어준다.
- 이제
@classmethod
를 붙여서 클래스 메서드를 만든다. - 클래스 메서드는 첫 번째 매개변수가
cls
인데 여기에는 현재 클래스가 들어온다. - 따라서
cls.count
처럼cls
로 클래스 속성count
에 접근할 수 있다.
Person
으로 인스턴스를 두 개 만들었으므로print_count
를 호출해 보면"2명 생성되었습니다."
가 출력된다.- 물론
print_count
는 클래스 메서드이므로Person.print_count()
처럼 클래스로 호출해 준다.
- 클래스 메서드는 정적 메서드처럼 인스턴스 없이 호출할 수 있다는 점은 같다.
- 하지만 클래스 메서드는 메서드 안에서 클래스 속성, 클래스 메서드에 접근해야 할 때 사용한다.
- 특히
cls
를 사용하면 메서드 안에서 현재 클래스의 인스턴스를 만들 수도 있다. - 즉,
cls
는 클래스이므로cls()
는Person()
과 같다.