[python] Class 속성과 인스턴스 속성
Python의 class의 속성과 객체의 속성은 별개입니다.
선요약
class의 __init__
즉, 생성자에서 할당하는 것이 아닌,
class 자체에 선언된 속성은 instance.attribute
가 아니라 instance.__class__.attribute
에 존재합니다.
다만 instance.attribute
에 무언가를 할당한 상태가 아니라면, instance.attribute
를 통해 접근이 가능하긴 합니다.
class로 생성할 객체마다 변하기 쉬운 기본 속성을 주고 싶다면, 클래스 속성이 아닌 인스턴스 생성자에 기본값을 설정합시다.
class 하나를 만들어봅시다.
class Cat:
race = 'Domestic Shot hair'
def __init__(self, name, age):
self.name = name
self.age = age
def change_race(self, race):
self.race = race
클래스의 기본 속성으로 도메스틱 숏 헤어라는 종 분류가 들어갑니다.
생성자에서 이름과 나이를 설정할 수 있으며,
메서드를 통해 race
를 변경 할 수 있는 것
처럼 만들어놨습니다.
위와 같은 코드는 만들면 안 됩니다.
change_race 메서드는 과연 어떻게 동작할까?
oia_cat = Cat('oai', 2)
huh_cat = Cat('huh', 3)
oia_ca.change_race('american shot hair')
과연 위의 코드는 어떤 동작을 의미할까요?
- Cat 클래스의 클래스 속성인 race 값이 변하여, huh_cat의 race도 바뀌었다.
- oia_cat 객체(인스턴스)의 race 값만 변한다. huh_cat은 그대로다.
- 둘 다 아니다.
정답은 3번
입니다.
겉으로 드러나는 부분만 보면 2번
이 될 수도 있지만, 파고 들면 아예 다릅니다.
class 속성은 모든 인스턴스가 공유한다.
class의 속성은 class의 인스턴스 전체가 공유하는 변수입니다.
# 위의 코드에서 이어진다.
print(oia_ca.race)
print(huh_cat.race)
# ======
american shot hair
domestic shot hair
보다시피 Cat 클래스의 두 객체의 race는 다릅니다.
앞서 말씀드렸다시피, 한 클래스의 모든 객체가 공유해야 하는 값이므로 서로 달라서는 안 됩니다.
단순히 값이 같은 수준(==)
이 아닌, 하나의 메모리에 존재하는 똑같은 객체 (is)
가 되어야 합니다.
어째서 같은 클래스인데 클래스 속성이 다른가?
chage_race 메서드는 클래스 속성인 race의 값을 바꾼 것이 아닌, 인스턴스 속성 스페이스에 race 값을 생성했기 때문입니다.
클래스 속성과 객체 속성은 다른 스페이스에 선언되어 있습니다.
클래스 속성에 보다 명시적으로 접근하기 위해서는,
{class_instance}.__class__.{attribute}
로 접근 할 수 있습니다.
(당연하지만, getter를 만드는 게 최고입니다.)
같은 변수명으로 객체 속성을 선언하지 않는 이상, {class_instance}.attribute
는
{class_instance}.__class__.attribute
를 불러옵니다.
코드로 설명하면 다음과 같습니다.
oia_cat = Cat('oia', 2)
huh_cat = Cat('huh', 3)
print(oia_cat.race is oia_cat.__class__.race) # True
print(oia_cat.race is huh_cat.race) # True
print(oia_cat.__class__.race is huh_cat.__class__.race) # True
oia_cat.change_race('american shot hair')
print(oia_cat.race is oia_cat.__class__.race) # False
print(oia_cat.race) # american shot hair
print(oia_cat.__class__.race) # domestic shot hair
print(huh_cat.race) # domestic shot hair
print(oia_cat.__class__.race is huh_cat.__class__.race) # True
oia cat
의 change_race
메서드로 클래스 속성과 이름을 공유하는 객체 속성을 생성한 뒤에는,
객체 속성을 먼저 호출하게 바뀝니다.
이 때문에 클래스 속성과 객체 속성을 혼동하는 경우가 잦은 것 같습니다.
클래스 속성은 모든 인스턴스가 공유해야만 하는 값, 하나가 바뀌면 모든 값이 바뀌는 값, 또는 상수가 들어가야 합니다.
Cat 클래스를 예로 든다면, 모든 고양이 객체가 변함 없이 공유하는 평균 수명
, 학명
정도를 넣을 수 있겠죠.
(물론 세부 품종마다 다를 수는 있으니, 단순 예시입니다!)
위의 클래스를 조금 더 올바르게 수정한다면 다음과 같이 다듬을 수 있을 것 같습니다.
class Cat:
avg_life_span = 15
scientific_name = 'Felis catus'
def __init__(self, name, age, race = 'Domestic Shot hair'):
self.name = name
self.age = age
self.race = race
def change_race(self, race):
self.race = race
클래스의 속성은 객체 전체가 공유하는 값으로 바꾸고,
race(품종)의 경우, 기본값은 Domestic shot hair로 하되, 클래스 생성 시 값이 들어올 경우 해당 값으로 할당합니다.
그렇다면 클래스 속성은 어떻게 바꾸지?
@classmethod
데코레이터를 활용하여 클래스 속성을 다룰 수 있는 메서드를 만들어야 합니다.
class Cat:
avg_life_span = 15
scientific_name = 'Felis catus'
def __init__(self, name, age, race = 'Domestic Shot hair'):
self.name = name
self.age = age
self.race = race
@classmethod
def change_avg_life_span(cls, new_value):
cls.avg_life_span = new_value
@classmethod
로 선언된 메서드의 경우, 인스턴스를 나타내는 self 대신 클래스 참조를 나타내는 cls가 첫 번째 인자로 들어갑니다.
이 방법으로는 class.__class__
내에 존재하는 클래스 속성을 바꿀 수 있습니다.
근데 그럼 이것도 되는 거 아니냐?
oia_cat.__class__.avg_life_span = 50
얌전히 setter 만들어 씁시다.