10.5

DanA·2022년 11월 17일
0

Vector 버전 #3: 동적 속성 접근

>>> v = Vector(range(10)
>>> v.x
0.0
>>> v.y, v.z, v.t
(1.0, 2.0, 3.0)

__getattr__() 특별 메서드를 이용하여 깔끔하게 구현하기!

: 속성을 찾지 못하면 인터프리터는 __getattr__() 메서드 호출
예: my_obj.x 표현식이 주어지면,
1. 파이썬은 my_obj 객체에 x 속성이 있는지 검사
2. 속성이 없으면, 이 객체의 클래스(my_obj.__class__)에서 더 검색
=> 이게 인스턴스의 속성이 아닌 클래스 속성의 속성에서 검색을 하는 것 같음.
3. 상속 그래프를 따라 올라감
4. 그래도 x속성을 찾지 못하면 self와 속성명을 문자열(예를 들면 x)로 전달해서 my_obj의 클래스에 정의된 __getattr__() 메서드를 호출함

예제 10-8. vector_v3.py의 일부: vector_v2.py의 Vector 클래스에 추가된 __getattr__() 메서드

shortcut_names = 'xyzt' # 클래스 속성

def __getattr__(self, name):
	cls = type(self) # 1.
    if len(name) == 1: # 2.
		pos = cls.shortcut_names.find(name) # 3.
        if 0 <= pos < len(self._components): # 4.
        	return self._components[pos]
    msg = '{.__name__!r} object has no attribtute {!r}' # 5.
    raise AttributeErrur(msg.format(cls, name)
  1. 나중에 사용하기 위해 Vector 클래스를 가져온다.
  2. name이 한 글자면 shortcut_names 중 하나일 수 있다.
  3. 한 글자 name의 위치를 찾는다. str.find()는 'yz'의 위치도 찾을 수 있으므로, 위에서 name의 길이가 1인지 확인한 것이다.
  4. position이 범위 안에 있으면 배열 항목을 반환한다.
  5. 두 개의 검사 과정에 실패하면 표준 메세지와 함께 AttributeError가 발생한다.

예제 10-9 부적절한 동작.

>>> v = Vector(range(5))
>>> v
Vector([0.0, 1.0, 2.0, 3.0, 4.0])
>>> v.x # 1. 
>>> v.x = 10 # 2.
>>> v.x # 3.
10
>>> v
Vector([0.0, 1.0, 2.0, 3.0, 4.0]) # 4.
  1. v.x를 이용해서 v[0]에 접근
    => 여기서는 __getattr__()메서드를 통해 접근함
  2. v.x에 새로운 값을 할당한다. 이 연산에서 원래는 오류가 발생했어야 한다.
  3. v.x 값을 읽으면 새로운 값이 10이 나온다.
    => 오잉 ? ? ??
    => 객체 vx라는 인스턴스 속성이 추가된 것임!! 그래서 더 이상 v.x값을 가져오기 위해 __getattr__()을 호출하지 않음!

    __getattr__() 메서드의 호출 원리: 속성을 찾지 못하면 인터프리터는 __getattr__() 메서드 호출!

즉, 바로 v.x를 가져오는 것임!!
4. 그래서 self._components가 변경된 것은 아니므로 v를 호출하면 벡터 요소는 변경되지 않고 나오는 것임!

def __repr__(self):
        components = reprlib.repr(self._components) # 제한된 길이로 출력
        components = components[components.find('['):-1] # 문자열 중 앞에 나오는 "array('d'," 를 제거
        return 'Vector({})'.format(components)

=> 이런 불일치 문제를 해결하기 위해 Vector 클래스에서 속성값을 설정하는 부분의 논리를 수정해야 한다!
9장의 마지막 Vector2d 예제에서 xy 객체의 속성에 값을 할당할 때 AttributeError가 발생했었다. Vector에서도 이런 문제를 피하기 위해 소문자 하나로 된 속성명에 값을 할당할 때 동일한 예외를 발생시키려 한다. 그렇게 하기 위해 __setattr__() 메서드를 구현해보자!

예제 10-10. 불일치 문제의 해결법: Vector 클래스에 추가된 __setattr__() 메서드

def __setattr__(self, name, value):
	cls = type(self)
    if len(name) == 1: # 1.
    	if name in cls.shortcut_names: # 2.
        	error = 'readonly attribute {attr_name!r}'
        elif name.islower(): # 3.
        	error = "can't set attributes 'a' to 'z' in {cls_name!r}
		else:
        	error = '' # 4.
        if error: 5.
        	msg = error.format(cls_name=cls.__name__, attr_name=name)
            raise AttributeError(msg)
    super().__setattr__(name, value) # 6. 에러가 발생하지 않을 때는 표준 동작을 위해 슈퍼클래스의 __setattr__() 메서드를 호출

TIP!
super() 함수는 슈퍼클래스의 메서드에 동적으로 접근할 수 있는 방법을 제공하며, 파이썬과 같이 다중 상속을 지원하는 동적 언어에서 필수적인 기능이다. [예제 10-10]에서 보는 것처럼 super()는 서브클래스가 처리할 작업의 일부를 슈퍼클래스로 위임하기 위해 사용한다. => 12.2절 다중 상속과 메서드 결정 순서에서 자세히 설명!

! 여기서 구현한 걸론 모든 속성의 설정을 막는 것이 아니라 지원되는 읽기 전용 속성인 x, y, z, t와의 혼동을 피하기 위해 단일 소문자 속성의 설정만 막는 것이다!

! 중요한 비결: 객체 동작의 불일치를 피하기 위해선 __getattr__()을 구현할 때 __setattr__()도 함께 구현해야 한다!

! 벡터 요소의 변경을 허용하고 싶은 경우, __setitem__() 메서드를 구현해라!

profile
단아와라라

0개의 댓글