파이썬 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. 주의사항
- 클래스 변수에 접근: {클래스명}.{클래스변수} 형식으로 접근하거나, {인스턴스명}.{클래스변수}를 통해 접근할 수 있다. 하지만 일반적으로 클래스 변수는 {클래스명}.{클래스변수} 형식으로 직접 접근/변경하는 것이 좋다.
- 클래스명으로 접근하면 변수의 소속을 명확히 보여준다는 장점이 있다.
- 인스턴스를 통해 클래스 변수에 접근하는 것은 가독성을 떨어뜨린다.
- {클래스명}.{클래스변수} 형식은 클래스 변수라는 것을 직관적으로 의미 파악이 가능하다.
- 반면 {인스턴스명}.{클래스변수}는 클래스 변수인지 인스턴스 변수인지 구분하기가 어렵다.
- 인스턴스에서 클래스 변수 덮어쓰기(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 -> 클래스 변수의 값은 불변
파이썬에서는 인스턴스 속성을 참조하려고 할 때 다음 순서로 탐색한다:
- 인스턴스의 __dict__에서 해당 속성 이름을 찾는다.
- 만약 인스턴스에서 속성을 찾지 못하면 클래스에서 해당 속성을 찾는다.
- 클래스에서도 찾지 못하면 상위 클래스(있는 경우)로 탐색한다.
self.brand = brand를 실행하면 brand라는 이름이 인스턴스의 네임스페이스(__dict__)에 추가된다.
이후로는 self.brand를 참조할 때 인스턴스 변수가 우선적으로 반환되어, 이로 인해 클래스 변수 brand는 가려지게 된다.
-> 이것이 Shadowing의 동작 메커니즘이다.
따라서.. Good Practice는:
클래스 변수와 인스턴스 변수를 구분하는 것이다. 같은 이름을 사용할 경우 혼란이 생길 수 있다.
클래스 변수와 인스턴스 변수를 명확히 구분하거나, 이름을 다르게 지정해 충돌을 방지하는 것이 좋다.
'Python' 카테고리의 다른 글
[Python] Asterisk(*)가 pack과 unpack을 수행하는 방식 (2) | 2024.12.06 |
---|---|
[Python] Static Method (@staticmethod) (0) | 2024.12.05 |
[Python] Iteration의 원리(Iterator Protocol) (1) | 2024.12.04 |
[Python] dict에 대한 iteration 정리(feat. lambda 함수) (0) | 2024.12.03 |
[Python] Local 변수 vs Global 변수, global 키워드 사용법 (3) | 2024.12.02 |