35.1 클래스 속성과 인스턴스 속성 알아보기

Unit 35. 클래스 속성과 정적, 클래스 메서드 사용하기

지금까지 간단하게 클래스를 만들고 속성과 메서드를 사용해봤습니다. 이번에는 클래스에 속해 있는 클래스 속성에 대해 알아보겠습니다. 그리고 인스턴스를 만들지 않고 클래스로 호출하는 정적 메서드와 클래스 메서드도 사용해보겠습니다.

35.1 클래스 속성과 인스턴스 속성 알아보기

'34.2 속성 사용하기'에서 클래스의 속성을 사용해봤는데, 사실 속성에는 클래스 속성과 인스턴스 속성 두 가지 종류가 있습니다. __init__ 메서드에서 만들었던 속성은 인스턴스 속성입니다.

35.1.1  클래스 속성 사용하기

그럼 이번에는 클래스 속성을 사용해보겠습니다. 클래스 속성은 다음과 같이 클래스에 바로 속성을 만듭니다.

class 클래스이름:
    속성 = 

이제 간단하게 사람 클래스에 클래스 속성으로 가방 속성을 넣고 사용해보겠습니다. 다음과 같이 Person 클래스에 바로 bag 속성을 넣고, put_bag 메서드를 만듭니다. 그리고 인스턴스 두 개를 만든 뒤 각각 put_bag 메서드를 사용합니다.

class_class_attribute.py

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)

실행 결과

['책', '열쇠']
['책', '열쇠']

가방에 물건을 넣는 간단한 동작을 만들었습니다. 그런데 결과가 좀 이상하죠? jamesmaria 인스턴스를 만들고 각자 put_bag 메서드로 물건을 넣었는데, james.bagmaria.bag을 출력해보면 넣었던 물건이 합쳐져서 나옵니다. 즉, 클래스 속성은 클래스에 속해 있으며 모든 인스턴스에서 공유합니다.

그림 35-1 클래스 속성

put_bag 메서드에서 클래스 속성 bag에 접근할 때 self를 사용했습니다. 사실 self는 현재 인스턴스를 뜻하므로 클래스 속성을 지칭하기에는 조금 모호합니다.

class Person:
    bag = []
 
    def put_bag(self, stuff):
        self.bag.append(stuff)

그래서 클래스 속성에 접근할 때는 다음과 같이 클래스 이름으로 접근하면 좀 더 코드가 명확해집니다.

  • 클래스.속성
class Person:
    bag = []
 
    def put_bag(self, stuff):
        Person.bag.append(stuff)    # 클래스 이름으로 클래스 속성에 접근

Person.bag이라고 하니 클래스 Person에 속한 bag 속성이라는 것을 바로 알 수 있습니다. 마찬가지로 클래스 바깥에서도 다음과 같이 클래스 이름으로 클래스 속성에 접근하면 됩니다.

print(Person.bag)
참고 | 속성, 메서드 이름을 찾는 순서

파이썬에서는 속성, 메서드 이름을 찾을 때 인스턴스, 클래스 순으로 찾습니다. 그래서 인스턴스 속성이 없으면 클래스 속성을 찾게 되므로 james.bag, maria.bag도 문제 없이 동작합니다. 겉보기에는 인스턴스 속성을 사용하는 것 같지만 실제로는 클래스 속성입니다.

인스턴스와 클래스에서 __dict__ 속성을 출력해보면 현재 인스턴스와 클래스의 속성을 딕셔너리로 확인할 수 있습니다.

인스턴스.__dict__

클래스.__dict__

>>> james.__dict__
{}
>>> Person.__dict__
mappingproxy({'__module__': '__main__', 'bag': ['책', '열쇠'], 'put_bag': <function Person.put_bag at 0x028A32B8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None})

</코드>

james.bag을 사용했을 때 클래스 속성을 찾는 과정은 다음과 같습니다.

그림 35-2 클래스 속성을 찾는 과정

35.1.2  인스턴스 속성 사용하기

그럼 가방을 여러 사람이 공유하지 않으려면 어떻게 해야 할까요? 그냥 bag을 인스턴스 속성으로 만들면 됩니다.

class_instance_attribute.py

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.bagmaria.bag을 출력해보면 각자 넣은 물건만 출력됩니다. 즉, 인스턴스 속성은 인스턴스별로 독립되어 있으며 서로 영향을 주지 않습니다.

이제 클래스 속성과 인스턴스 속성의 차이점을 정리해보겠습니다.

  • 클래스 속성: 모든 인스턴스가 공유. 인스턴스 전체가 사용해야 하는 값을 저장할 때 사용
  • 인스턴스 속성: 인스턴스별로 독립되어 있음. 각 인스턴스가 값을 따로 저장해야 할 때 사용

35.1.3  비공개 클래스 속성 사용하기

클래스 속성도 비공개 속성을 만들 수 있습니다. 클래스 속성을 만들 때 __속성과 같이 __(밑줄 두 개)로 시작하면 비공개 속성이 됩니다. 따라서 클래스 안에서만 접근할 수 있고, 클래스 바깥에서는 접근할 수 없습니다.

class 클래스이름:
    __속성 =     # 비공개 클래스 속성

즉, 클래스에서 공개하고 싶지 않은 속성이 있다면 비공개 클래스를 사용해야 합니다. 예를 들어 기사 게임 캐릭터는 아이템을 최대 10개까지만 보유할 수 있다고 하죠.

class_private_class_attribute_error.py

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)    # 클래스 바깥에서는 접근할 수 없음


실행 결과

10
Traceback (most recent call last):
  File "C:\project\class_private_class_attribute_error.py ", line 11, in <module>
    print(Knight.__item_limit)    # 클래스 바깥에서는 접근할 수 없음
AttributeError: type object 'Knight' has no attribute '__item_limit' 

실행을 해보면 클래스 Knight의 비공개 클래스 속성 __item_limit는 클래스 안의 print_item_limit 메서드에서만 접근할 수 있고, 클래스 바깥에서 접근하면 에러가 발생합니다. 아이템의 보유 제한이 10개인데, 이 클래스를 사용하는 사람이 마음대로 __item_limit = 1000으로 수정하면 곤란하겠죠?

이처럼 비공개 클래스 속성은 클래스 바깥으로 드러내고 싶지 않은 값에 사용합니다.

참고 | 클래스와 메서드의 독스트링 사용하기

함수와 마찬가지로 클래스와 메서드도 독스트링을 사용할 수 있습니다. 다음과 같이 클래스와 메서드를 만들 때 :(콜론) 바로 다음 줄에 """ """(큰따옴표 세 개) 또는 ''' '''(작은따옴표 세 개)로 문자열을 입력하면 됩니다. 그리고 클래스의 독스트링은 클래스.__doc__ 형식으로 사용하고, 메서드의 독스트링은 클래스.메서드.__doc__ 또는 인스턴스.메서드.__doc__ 형식으로 사용합니다.

class Person:
    '''사람 클래스입니다.'''
    
    def greeting(self):
        '''인사 메서드입니다.'''
        print('Hello')
 
print(Person.__doc__)             # 사람 클래스입니다.
print(Person.greeting.__doc__)    # 인사 메서드입니다.
 
maria = Person()
print(maria.greeting.__doc__)     # 인사 메서드입니다.