반응형

파이썬 OOP에서 클래스 변수와 인스턴스 변수는 중요한 개념이다. 둘의 개념과 사용시 주의해야 할 점을 소개한다.

 

1. 클래스 변수 (Class Variable)

  • 정의: 클래스 변수는 클래스 자체에 속하는 변수로, 클래스의 모든 인스턴스에서 공유된다.
  • 특징: 클래스 변수를 변경하면 모든 인스턴스에서 그 변경 사항을 공유한다.
  • 사용방법: 클래스 내에서 self가 아닌 클래스명으로 접근할 수 있다.

클래스 변수는 클래스 선언부 아래에 정의된다.

"{클래스명}.{클래스변수}  = 값" 형식으로 클래스 변수를 수정하면, 다른 인스턴스들에서도 그 수정된 값을 보게 된다.

(반면, "{인스턴스명}.{클래스변수} = 값" 형식으로 클래스 변수를 수정하고자 시도하면 다른 인스턴스들에서 그 수정된 값을 볼 수 없다. 이유는 아래 3.2. 인스턴스에서 클래스 변수 덮어쓰기(Shadowing 문제)에서 자세히 다룬다.)

class MyClass:
    class_variable = 0  # 클래스 변수

    def __init__(self, value): # 클래스 생성자
        self.instance_variable = value  # 인스턴스 변수

# 클래스 변수 접근
print(MyClass.class_variable)  # 0

# 인스턴스를 생성하고 인스턴스 변수 접근
obj1 = MyClass(10)
print(obj1.instance_variable)  # 10

# 클래스 변수 수정
obj2 = MyClass(20)
MyClass.class_variable = 5
print(obj1.class_variable)  # 5
print(obj2.class_variable)  # 5

아래는 클래스 변수가 list인 경우이다.

각 인스턴스 객체는 클래스 변수인 list의 주소를 참조하고 있기 때문에, list 주소에 접근 후 마치 클래스 변수인 것처럼 list를 조작할 수 있다.

인스턴스 입장에서 list 클래스 변수를 수정할 때는 "{인스턴스명}.{클래스변수} = 값" 형식을 사용하지 않고 append()를 쓰므로, 다른 인스턴스들에서도 수정된 클래스 변수 값을 확인할 수 있다.

class MyClass:
    shared_list = []  # 클래스 변수

# 인스턴스 생성
a = MyClass()
b = MyClass()

# 인스턴스에서 리스트에 값 추가
a.shared_list.append(1)

# 인스턴스 변수 및 클래스 변수에서 확인
print(a.shared_list)  # [1]
print(b.shared_list)  # [1]
print(MyClass.shared_list)  # [1]

# 클래스에서 리스트에 값 추가
MyClass.shared_list.append(2)

# 인스턴스 변수 및 클래스 변수에서 확인
print(a.shared_list)  # [1, 2]
print(b.shared_list)  # [1, 2]
print(MyClass.shared_list)  # [1, 2]

list, dict, set, 사용자 정의 클래스 객체와 같은 mutable(가변) 객체들을 클래스 변수로 삼을 때는 값을 변경했을 때, 모든 다른 인스턴스들에서도 해당 값을 확인할 수 있다.

mutable과 immutable에 대한 내용은 다음 링크를 참고: https://comgu.tistory.com/entry/Python-Immutable불변-vs-Mutable가변

 

2. 인스턴스 변수 (Instance Variable)

  • 정의: 인스턴스 변수는 객체(인스턴스)별로 개별적으로 존재하는 변수다.
  • 특징: 각 인스턴스마다 고유한 값을 가질 수 있으며, 다른 인스턴스에는 영향을 미치지 않는다.
  • 사용 방법: 인스턴스 변수는 self를 통해 접근한다.
class MyClass:
    def __init__(self, value):
        self.instance_variable = value  # 인스턴스 변수

# 두 개의 인스턴스 생성
obj1 = MyClass(10)
obj2 = MyClass(20)

# 인스턴스 변수 값 확인
print(obj1.instance_variable)  # 10
print(obj2.instance_variable)  # 20

 

3. 주의사항

  1. 클래스 변수에 접근: {클래스명}.{클래스변수} 형식으로 접근하거나, {인스턴스명}.{클래스변수}를 통해 접근할 수 있다. 하지만 일반적으로 클래스 변수는 {클래스명}.{클래스변수} 형식으로 직접 접근/변경하는 것이 좋다.
    • 클래스명으로 접근하면 변수의 소속을 명확히 보여준다는 장점이 있다.
    • 인스턴스를 통해 클래스 변수에 접근하는 것은 가독성을 떨어뜨린다.
      • {클래스명}.{클래스변수} 형식은 클래스 변수라는 것을 직관적으로 의미 파악이 가능하다.
      • 반면  {인스턴스명}.{클래스변수}는 클래스 변수인지 인스턴스 변수인지 구분하기가 어렵다.
  2. 인스턴스에서 클래스 변수 덮어쓰기(Shadowing 문제): 인스턴스에서 클래스 변수와 동일한 이름의 변수를 설정하면, 새로운 인스턴스 변수가 생성된다. 이 경우 클래스 변수는 영향을 받지 않는다.

아래 코드를 보자.

Car 클래스의 brand는 클래스 변수이다. 

그런데 Car 클래스의 인스턴스(car1 또는 car2)에서 change_brand()를 호출하면, 동일한 이름인 brand를 변수명으로 갖는 인스턴스 변수가 새롭게 생성된다.

이때 클래스 변수 brand는 더 이상 해당 인스턴스에서 {인스턴스명}.{클래스변수} 방식으로 접근할 수 없게 된다. 즉 인스턴스 변수 brand가 클래스 변수 brand를 가리므로(shadow하므로), self.brand 는 인스턴스 변수로서만 동작하게 된다.

즉, self.brand = ...와 같이 인스턴스 변수를 명시적으로 설정하면 클래스 변수와의 공유 상태가 끊어진다.

class Car:
    brand = None  # 클래스 변수

    def change_brand(self, brand):
        self.brand = brand  # 인스턴스 변수 생성

# 인스턴스 생성 후 메서드 호출
car1 = Car()
car2 = Car()

car1.change_brand("Toyota")
car2.change_brand("Honda")

print(car1.brand)  # 출력: Toyota -> 신규 생성된 인스턴스 변수 값 출력
print(car2.brand)  # 출력: Honda  -> 신규 생성된 인스턴스 변수 값 출력
print(Car.brand)   # 출력: None   -> 클래스 변수의 값은 불변


파이썬에서는 인스턴스 속성을 참조하려고 할 때 다음 순서로 탐색한다:

  1. 인스턴스의 __dict__에서 해당 속성 이름을 찾는다.
  2. 만약 인스턴스에서 속성을 찾지 못하면 클래스에서 해당 속성을 찾는다.
  3. 클래스에서도 찾지 못하면 상위 클래스(있는 경우)로 탐색한다.

self.brand = brand를 실행하면 brand라는 이름이 인스턴스의 네임스페이스(__dict__)에 추가된다.

이후로는 self.brand를 참조할 때 인스턴스 변수가 우선적으로 반환되어, 이로 인해 클래스 변수 brand는 가려지게 된다.

-> 이것이 Shadowing의 동작 메커니즘이다.

 

따라서.. Good Practice는:

클래스 변수와 인스턴스 변수를 구분하는 것이다. 같은 이름을 사용할 경우 혼란이 생길 수 있다.

클래스 변수와 인스턴스 변수를 명확히 구분하거나, 이름을 다르게 지정해 충돌을 방지하는 것이 좋다.

반응형

+ Recent posts