>>> 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)
- 나중에 사용하기 위해 Vector 클래스를 가져온다.
- name이 한 글자면 shortcut_names 중 하나일 수 있다.
- 한 글자 name의 위치를 찾는다.
str.find()
는 'yz'의 위치도 찾을 수 있으므로, 위에서 name의 길이가 1인지 확인한 것이다.- position이 범위 안에 있으면 배열 항목을 반환한다.
- 두 개의 검사 과정에 실패하면 표준 메세지와 함께 AttributeError가 발생한다.
>>> 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.
v.x
를 이용해서v[0]
에 접근
=> 여기서는__getattr__()
메서드를 통해 접근함v.x
에 새로운 값을 할당한다. 이 연산에서 원래는 오류가 발생했어야 한다.v.x
값을 읽으면 새로운 값이 10이 나온다.
=> 오잉 ? ? ??
=> 객체v
에x
라는 인스턴스 속성이 추가된 것임!! 그래서 더 이상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
예제에서 x
나 y
객체의 속성에 값을 할당할 때 AttributeError
가 발생했었다. Vector
에서도 이런 문제를 피하기 위해 소문자 하나로 된 속성명에 값을 할당할 때 동일한 예외를 발생시키려 한다. 그렇게 하기 위해 __setattr__()
메서드를 구현해보자!
__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__()
메서드를 구현해라!