Python

[python] Class 속성과 인스턴스 속성

inmonim 개발개발 2025. 1. 26. 15:38

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')

 

과연 위의 코드는 어떤 동작을 의미할까요?

  1. Cat 클래스의 클래스 속성인 race 값이 변하여, huh_cat의 race도 바뀌었다.
  2. oia_cat 객체(인스턴스)의 race 값만 변한다. huh_cat은 그대로다.
  3. 둘 다 아니다.

정답은 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 catchange_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 만들어 씁시다.