9.1. 객체 표현
- repr(): 객체를 개발자가 보고자 하는 형태로 표현한 문자열로 반환한다.
- str(): 객체를 사용자가 보고자 하는 형태로 표현한 문자열로 반환한다.
- bytes(): 객체를 바이트 시퀀스로 표현한다.
- format(), str.format(): 특별 포맷 코드를 이용해서 객체를 표현하는 문자열을 반환한다.
9.2. 벡터 클래스의 부활
from array import array
import math
class Vector2d:
typecode = 'd' # typecode는 Vector2d와 bytes 간의 변환에 사용하는 클래스 속성
def __init__(self, x, y):
self.x = float(x) # __init__() 안에서 x와 y를 flolat으로 변환하면 부적절한 인수로 Vector2d 객체를 생성하는 경우 조기에 에러를 잡는 데 도움이 됨.
self.y = float(y)
def __iter__(self):
return (i for i in (self.x, self.y)) # __iter__()를 구현하면 Vector2d를 반복할 수 있게 된다. 이 메서드는 제너레이터 표현식을 이용해 요소를 차례대로 하나씩 생성한다.
def __repr__(self):
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self) # __repr__()은 {!r}을 각 요소에 repr()를 호출해서 반환된 문자열로 치환해 문자열을 만든다.
def __str__(self):
return str(tuple(self)) # 반복형 Vector2d에서 튜플을 만들어 순서쌍으로 출력한다.
def __bytes__(self):
return (bytes[ord(self.typecode)]) + bytes(array(self.typecode, self)) # bytes를 생성하기 위해 typecode를 bytes로 변환한다. 그리고 이것을 객체를 반복해서 생성한 배열에서 변환된 bytes와 연결한다.
def __eq__(self, other):
return tuple(self) == tuple(other) # 모든 속성을 비교하기 위해 피연산자로부터 튜플을 생성한다.
def __abs__(self):
return math.hypot(self.x, self.y) # x와 y로 만들어진 직삼각형 사변의 길이.
def __bool__(self):
return bool(abs(self)) # abs(self)를 이용해 사변 길이를 계산하고 불리언 형으로 변환한다. 따라서 0.0은 False이고 그 외의 값은 True이다.
# 9.3. 대안 생성자
- bytes를 Vector2d로 변환하는 메서드도 추가해보자.
@classmethod
def frombytes(cls, octets): # self 매개변수가 없는 대신 클래스 자신이 cls 매개변수로 전달된다.
typecode = chr(octets[0]) # 첫 번째 바이트에서 typecode를 읽는다.
memv = memoryview(octets[1:]).cast(typecode) # octets 이진 시퀀스로부터 memoryview를 생성하고, typecode를 이용해서 형을 변환한다.
return cls(*memv) # cast()가 반환한 memoryview를 언패킹해서 생성자에 필요한 인수로 전달한다.
v1 = Vector2d(3,4)
v1.frombytes(bytes(v1))
# Vector2d(3.0, 4.0)
9.4. @classmethod와 @staticmethod
- @classmethod 데커레이터는 객체가 아닌 클래스에 연산을 수행하는 메서드를 정의한다.
- @classmethod는 메서드가 호출되는 방식을 변경해서, 클래스 자체를 첫 번째 인수로 받게 반들며, 위 코드의 frombytes() 같은 대안 생성자를 구현하기 위해 주로 사용된다.
- frombytes() 메서드의 마지막 행에서 cls(*memv)는 객체를 생성하기 위해 cls 인수를 이용해서 실제로 클래스의 생성자를 호출한다.
- @staticmethod 데커레이터는 메서드가 특별한 첫 번째 인수를 받지 않도록 메서드를 변경한다. 본질적으로 정적 메서드는 모듈 대신 클래스 본체 안에 정의된 평범한 함수일 뿐이다.
class Demo:
@classmethod
def klassmeth(*args):
return args # classmeth()는 모든 위치 인수를 보여준다.
@staticmethod
def statmeth(*args):
return args # statmeth() 도 모든 위치 인수를 보여준다.
Demo.klassmeth() # 호출 방법에 무관하게 Demo.klassmeth()는 Demo 클래스를 첫 번째 인수로 받는다.
# (__main__.Demo,)
Demo.klassmeth('spam')
# (__main__.Demo, 'spam')
Demo.statmeth() # Demo.statmeth()는 평범한 함수처럼 동작한다.
# ()
Demo.statmeth('spam')
# ('spam',)
9.5. 포맷된 출력
- format(), str.format() 메서드는 실제 포맷 작업을 __format__(format_spec) 메서드에 위임한다. format_spec은 다음 두 가지 방법 중 하나를 통해 지정한다.
1. format(my_obj, format_spec)의 두 번째 인수
2. str.format()에 사용된 포맷 문자열 안에 {}로 구분한 대체 필드 안에서 콜론 뒤의 문자
9.6. 해시 가능한 Vector2d
- Vector2d를 해시 가능하게 만들려면 __hash__() 메서드를 구현해야 한다. Vector2d 객체를 불변형으로 만들어야 한다.
class Vector2d:
typecode = 'd' # typecode는 Vector2d와 bytes 간의 변환에 사용하는 클래스 속성
def __init__(self, x, y):
self.__x = float(x) # __init__() 안에서 x와 y를 flolat으로 변환하면 부적절한 인수로 Vector2d 객체를 생성하는 경우 조기에 에러를 잡는 데 도움이 됨.
self.__y = float(y) # 두 개의 언더바로 속성을 비공개로 만든다.
@property
def x(self):# 자신이 노출시키는 공개 속서염ㅇ을 따라 게터 메서드의 이름을 지정한다.
return self._x
@property
def y(self):
return self._y
# .....
- x와 y의 속성을 읽기만 하는 다른 메서드들은 비공개 속성 대신 self.x와 self.y를 통해 공개 프로퍼티를 읽으므로 변경하지 않아도 된다.
- 이제 구현할 __hash__() 메서드는 int형을 반환해야 한다.
def __hash__(self):
return hash(self.x) ^ hash(self.y) # XOR 연산자 ^ 사용
v1 = Vector2d(3, 4)
v2 = Vector2d(3.1, 4.2)
print(hash(v1), hash(v2))
print(set([v1, v2]))
# 7 384307168202284039
# {Vector2d(3.1, 4.2), Vector2d(3.0, 4.0)}
9.7. 파이썬에서의 비공개 속성과 보호된 속성
- 속성명을 두 개의 언더바로 시작하고, 언더바 없이, 또는 하나의 언어바로 끝나도록 정의하면, 파이썬은 언어바와 클래스명을 변수명 앞에 붙여 객체의 __dict__에 저장한다. 이러한 기능을 이름 장식(name mangling)이라고 한다.
v1 = Vector2d(3, 4)
v1.__dict__
# {'_Vector2d__x': 3.0, '_Vector2d__y': 4.0}
v1._Vector2d__x
# 3.0
- 이름 장식은 안전을 제공하지만, 보안 기능은 아니다. 실수로 접근하는 것을 막도록 설계되었을 뿐이다.
9.8. __slots__ 클래스 속성으로 공간 절약하기
- 파이썬은 객체 속성을 각 객체 안의 __dict__라는 딕셔너리형 속성에 저장한다.
- 만약 속성이 몇 개 없는 수백만 개의 객체를 다룬다면 __slots__ 클래스 속성을 이용해서 메모리 사용량을 줄일 수 있다. __slots__ 속성은 파이썬 인터프리터가 객체 속성을 딕셔너리 대신 튜플을 저장하게 만든다.
class Vector2d:
__slots__ = ('__x', '__y')
typecode = 'd' # typecode는 Vector2d와 bytes 간의 변환에 사용하는 클래스 속성
# ....
- __slots__ 를 정의할 때 불변형인 튜플을 사용하면 __slots__ 정의를 변경할 수 없음을 알려줄 수 있다.
- __slots__를 클래스에 정의함으로써 '이 속성들이 이 클래스 객체가 가지는 속성'임을 인터프리터에 알려준다.파이썬 인터프리터는 이 속성들을 각 객체의 튜플형 구조체에 저장함으로써, __dict__ 속성을 각 객체마다 유지하지 않아도 되어 메모리 사용량을 줄여준다.
9.8.1. __slots__를 사용할 때 주의할 점
- 인터프리터는 상속된 __slots__ 속성을 무시하므로 각 클래스마다 __slots__ 속성을 다시 정의해야 한다.
- __dict__를 __slots__에 추가하지 않는 한 객체는 __slots__에 나열된 속성만 가질 수 있다.
- __weakref__를 __slots__에 추가하지 않으면 객체가 약한 참조의 대상이 될 수 없다.
9.9. 클래스 속성 오버라이드
- 클래스의 속성을 변경하려면 클래스 정의에서 직접 바꿔야 하며, 객체를 통해 변경하면 안 된다.
'Python' 카테고리의 다른 글
[전문가를 위한 파이썬] 11장 인터페이스: 프로토콜에서 ABC까지 (0) | 2024.08.31 |
---|---|
[전문가를 위한 파이썬] 10장 시퀀스 해킹, 해시, 슬라이스 (0) | 2024.07.21 |
[전문가를 위한 파이썬] 8장 객체 참조, 가변성, 재활용 내용 정리 (0) | 2024.06.18 |
[전문가를 위한 파이썬] 7장 함수 데커레이터와 클로저 내용 정리 (1) | 2024.06.10 |
[전문가를 위한 파이썬] 6장 일급 함수 디자인 패턴 내용 정리 (0) | 2024.06.02 |