Python 속성 조작 함수 (getattr, setattr, hasattr, delattr)

Python 속성 조작 함수 (getattr, setattr, hasattr, delattr)

개요

1
2
3
4
5
6
# 일반적인 속성 접근
config.debug  # 속성명이 확정된 경우

# 동적 속성 접근이 필요한 경우
setting_name = "debug"  # 문자열로 속성명이 주어짐
getattr(config, setting_name)  # 이때 동적 함수가 필요!

핵심 개념 이해

정적 vs 동적 속성 접근

정적 접근 = 컴파일 타임에 결정

  • 코드 작성 시점에 속성명이 확정됨
  • obj.attribute 형태로 직접 접근
  • 빠르고 명확하지만 유연성 부족

동적 접근 = 런타임에 결정

  • 실행 중에 속성명이 문자열로 결정됨
  • getattr(obj, 'attribute') 형태로 접근
  • 약간 느리지만 매우 유연함
1
2
3
4
5
6
7
8
# 정적: 코드에서 속성명이 고정
user.name
user.age

# 동적: 실행 중에 속성명이 결정
fields = ['name', 'age', 'email']
for field in fields:
    value = getattr(user, field)  # 필드명이 변수로 주어짐

4가지 함수의 역할 분담

getattr: 안전한 속성 읽기 (기본값 제공)
setattr: 동적 속성 설정 (런타임에 속성 추가/수정)
hasattr: 속성 존재 확인 (에러 없이 검사)
delattr: 속성 삭제 (동적으로 제거)

1
2
3
4
5
6
7
8
9
10
class Config:
    debug = True

config = Config()

# 각 함수의 핵심 특징
hasattr(config, 'timeout')           # False (존재 확인)
getattr(config, 'timeout', 30)       # 30 (기본값 반환)
setattr(config, 'timeout', 60)       # 새 속성 생성
delattr(config, 'timeout')           # 속성 삭제

실제 활용 사례

설정 파일 처리 - 동적 함수로 코드 간소화

수십 개의 설정 항목을 if문 없이 깔끔하게 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# ❌ 기존 방식: 설정마다 개별 처리
class AppConfig:
    def __init__(self):
        self.debug = False
        self.host = "localhost"
        self.port = 8000
        # ... 50개 설정 항목
    
    def load_from_dict(self, config_dict):
        if 'debug' in config_dict:
            self.debug = config_dict['debug']
        if 'host' in config_dict:
            self.host = config_dict['host']
        if 'port' in config_dict:
            self.port = config_dict['port']
        # ... 50개 설정마다 if문 (코드 중복!)

# ✅ 개선된 방식: 동적 함수로 간소화
class AppConfig:
    def __init__(self):
        self.debug = False
        self.host = "localhost" 
        self.port = 8080
    
    def load_from_dict(self, config_dict):
        for key, value in config_dict.items():
            if hasattr(self, key):  # 기존 설정만 업데이트
                setattr(self, key, value)
    
    def get_setting(self, name, default=None):
        return getattr(self, name, default)

# 사용 예시
config = AppConfig()
config.load_from_dict({
    'debug': True,
    'host': '0.0.0.0',
    'new_setting': 'ignored'  # 기존에 없는 설정은 무시
})

print(config.get_setting('debug'))      # True
print(config.get_setting('timeout', 30)) # 30 (기본값)
  • 50개 설정을 처리하는 코드가 3줄로 단축
  • 새로운 설정 추가 시 코드 수정 불필요
  • 타입 안전성과 유연성을 동시에 확보

동적 데이터 클래스 - 런타임 클래스 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def create_data_class(class_name, fields, validations=None):
    """필드 정보를 기반으로 데이터 클래스를 동적 생성"""
    
    class DynamicDataClass:
        def __init__(self, **kwargs):
            # 필드 초기화
            for field in fields:
                default_value = kwargs.get(field)
                setattr(self, field, default_value)
            
            # 검증 실행
            if validations:
                self.validate()
        
        def validate(self):
            """동적 검증 실행"""
            for field, validator in (validations or {}).items():
                if hasattr(self, field):
                    value = getattr(self, field)
                    if not validator(value):
                        raise ValueError(f"{field} 검증 실패: {value}")
        
        def to_dict(self):
            """모든 필드를 딕셔너리로 변환"""
            result = {}
            for field in fields:
                if hasattr(self, field):
                    result[field] = getattr(self, field)
            return result
        
        def update(self, **kwargs):
            """필드 업데이트"""
            for key, value in kwargs.items():
                if key in fields:
                    setattr(self, key, value)
        
        def __repr__(self):
            field_strs = []
            for field in fields:
                if hasattr(self, field):
                    value = getattr(self, field)
                    field_strs.append(f"{field}={value!r}")
            return f"{class_name}({', '.join(field_strs)})"
    
    DynamicDataClass.__name__ = class_name
    return DynamicDataClass

# 사용 예시: API 응답 모델 동적 생성
User = create_data_class(
    'User', 
    ['id', 'name', 'email', 'age'],
    validations={
        'email': lambda x: '@' in str(x) if x else False,
        'age': lambda x: isinstance(x, int) and x >= 0
    }
)

# 인스턴스 생성 및 사용
user = User(id=1, name='Alice', email='alice@example.com', age=30)
print(user)  # User(id=1, name='Alice', email='alice@example.com', age=30)

user.update(age=31)
print(user.to_dict())  # {'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'age': 31}

성능 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import time

class TestClass:
    value = 42

obj = TestClass()
iterations = 1000000

# 직접 접근
start = time.time()
for _ in range(iterations):
    x = obj.value
direct_time = time.time() - start

# getattr 사용
start = time.time()
for _ in range(iterations):
    x = getattr(obj, 'value')
getattr_time = time.time() - start

print(f"직접 접근: {direct_time:.4f}")
print(f"getattr 사용: {getattr_time:.4f}")
print(f"성능 차이: {getattr_time/direct_time:.1f}배 느림")

# 결과 예시:
# 직접 접근: 0.0521초
# getattr 사용: 0.1032초  
# 성능 차이: 2.0배 느림

결론

  • 속성명이 고정: 직접 접근 (obj.attr)
  • 속성명이 변수: 동적 함수 (getattr(obj, attr_name))
  • 확장성 필요: 동적 함수로 유연한 구조 설계

Python의 동적 속성 조작은 편의를 위한 기능이 아니라 확장 가능한 아키텍처를 위한 핵심 도구다.
적절히 사용하면 코드의 유연성과 재사용성을 크게 향상시킬 수 있다.

This post is licensed under CC BY 4.0 by the author.