9.9 매직 메서드 개요
파이썬은 의미를 미리 정의
한 여러 메서드 이름이 있다. 모든 이름은 언더바 2개(__
)로 시작하고 끝난다.
이런 메서드를 던더(dunder, double underscore) 메서드라 부른다.
그래서 메서드 이름을 지을 때 언더바 2개(__
)를 아예 사용하지 않는다면 던더 메서드의 이름과 겹치지 않는다.
미리 정의된 이름을 사용하는 메서드를 매직 메서드
라고 부른다.
다른 메서드와 똑같은 방식으로 호출되지만 특정 조건에 따라 자동으로 호출되기도 한다.
ex) __init__
메서드 : 해당 클래스의 인스턴스가 생성될 때 마다 자동으로 호출되는 매직 메서드
그렇다면 어떤 매직 메서드가 있는지 하나씩 살펴보겠다.
9.10 매직 메서드 상세
앞으로 소개할 각 섹션은 중/고급 파이썬 프로그래머가 되기 위해 유용한 주요 매직 메서드를 상세하게 다루고 있다.
일부는 거의 직접 구현할 일이 없기 때문에 다루지 않는다.
① 파이썬 클래스의 문자열 표현
__format__
, __str__
, __repr__
을 포함한 메서드 몇 가지로 클래스 자체를 표현할 수 있다.
ex) format 함수
# 파이썬에서 아래 코드를 실행
format(6, 'b')
# format 함수가 호출되면서 정수 클래스 int의 __format__ 메서드가 호출 & 포맷 규약으로 'b'가 전달됨
# 메서드는 정수 6의 이진 값을 반환함
==> '110'
② 객체 표현 메서드
메서드 문법 | 설명 |
---|---|
__format__(self, spec) |
format 함수에 객체를 직접 전달했을 때 호출됨 |
__str__(self) |
사용자가 원하는 형태로 객체 데이터를 담은 문자열을 반환. 직접 구현하지 않았다면 __repr__ 호출 |
__repr(self)__ |
__str__ 과 다르게 객체 표준 표현 방식으로 문자열을 반환함 |
__hash__(self) |
hash 함수에서 객체가 매개변수로 전달되었을 때 호출됨 |
__bool__(self) |
bool 함수를 호출하고 나면 항상 호출된다 |
__nonzero__(self) |
파이썬 3.0 부터는 불리언 반환을 하려면 __bool__ 을 구현해야 한다 |
ex)
class Point :
big_prime1 = 1200330304
big_prime2 = 2323039292
def __init__ (self, x = 0, y = 0) :
self.x = x
self.y = y
# 사용자가 원하는 형태로 데이터를 문자열로 반환함
def __str__(self) :
return str(self.x) + ',' + str(self.y)
# 객체의 표준 출력 방식을 정의했음
def __repr__(self) :
return 'Point(' + str(self.x) + ', ' + str(self.y) + ')'
# Point라는 객체에서 해시코드를 만드는 방식을 정의함
# 해당 메서드는 hash 메서드에 Point 객체의 인스턴스를 매개변수로 전달했을 때 호출된다.
def __hash__(self) :
return (self.x * self.big_prime1 + self.y) % self.big_prime2
# Poinst 객체에서 true/false가 출력되는 기준을 정의함
# 해당 메서드는 bool 메서드에 Point 객체의 인스턴스를 매개변수로 전달했을 때 호출된다.
def __bool__(self) :
return self.x == self.y
>>> pt = Point(3, 4)
>>> pt # Point 객체에서 정의한 __repr__ 호출됨
Point(3, 4)
>>> print(pt) # Point 객체에서 정의한 __str__ 호출됨
3,4
>>> hash(pt) # Point 객체에서 정의한 __hash__ 호출됨
1277951624
>>> bool(pt) # Point 객체에서 정의한 __bool__ 호출됨
False
③ 비교 메서드
비교 메서드는 클래스 객체를 ==, !=, >, <, >=, <= 를 사용해서 비교할 수 있도록 해준다.
- 파이썬 비교 연산자의 특징
1) 클래스의 객체들을 정렬하고 싶다면 <
연산자를 정의해야 한다
2) 컬렉션이 서로 다른 클래스의 객체들을 동시에 갖고 있을 때 비교하고 싶다면 대칭(symmetry)
을 이용한다
==> 대칭 규칙이 있기 때문에 모든 메서드를 구현할 필요 없이 자동으로 여러 연산자를 갖게 된다
(ex. 파이썬은 A > B는 B < A와 동일하다는 걸 추론할 수 있다)
class Dog :
def __init__(self, n) :
self.n = n
# 동일함 테스트
def __eq__(self, other) :
''' ==를 구현한다.
이에 자동으로 !=도 구현된다. '''
return self.n == other.n
# lt = less than
# lt를 구현함으로써 gt가 자동으로 구현됨
def __lt__(self, other) :
''' <를 구현한다.
이에 자동으로 >도 구현된다. '''
return self.n < other.n
# le = less that or equal to
# le를 구현함으로써 ge가 자동으로 구현됨
def __le__(self, other) :
''' <=를 구현한다.
이에 자동으로 >=도 구현된다'''
return self.n <= other.n
ex) Dog 클래스와 int형 정수를 같이 정렬하는 경우
Dog 클래스
와 int형 정수
를 서로 정렬하게 하려면 다음과 같은 비교가 가능해야 한다.
Dog < Dog # 같은 객체 간의 비교
Dog < int # int가 더 큰 경우
int < Dog # Dog이 더 큰 경우
위 3가지를 정의할 수 있어야 한다.
어떻게 정의하면 될까. 아래의 코드와 함께 살펴보자.
class Dog :
def __init__(self, d) :
self.d = d
# Dog과 Dog 간의 비교
# 또는 Dog이 int보다 큰 경우
def __gt__(self, other) :
if type(other) == Dog :
return self.d > other.d
else :
return self.d > other
# Dog과 Dog 간의 비교
# 또는 int가 Dog보다 큰 경우
def __lt__ (self, other) :
if type(other) == Dog :
return self.d < other.d
else :
return self.d < other
# 문자 표현
def __repr__(self) :
return "Dog(" + str(self.d) + ")"
d1, d5, d10 = Dog(1), Dog(5), Dog(10)
a_list = [50, d10, 100, d1, -20, d5, 3]
a_list.sort()
[-20, Dog(1), 3, Dog(5), Dog(10), 50, 100] # 같은 객체 끼리 비교 & int형 정수와 Dog 객체의 값 비교
④ 산술 연산자 메서드
메서드 문법 | 설명 |
---|---|
__add__(self, other) |
덧셈. 클래스의 인스턴스가 + 좌측에 있을 때 호출됨. other는 + 우측에 위치한 참조 |
__sub__(self, other) |
뺄셈. 클래스의 인스턴스가 - 좌측에 있을 때 호출됨. other는 - 우측에 위치한 참조 |
__mul__(self, other) |
곱셈. 클래스의 인스턴스가 * 좌측에 있을 때 호출됨. other는 * 우측에 위치한 참조 |
__floordiv__(self, other) |
정수 나눗셈. 소수점을 버린 몫 반환. 클래스의 인스턴스가 // 좌측에 있을 때 호출됨. other는 // 우측에 위치한 참조 |
__truediv__(self, other) |
일반 나눗셈. 소수점을 버리지 않은 몫 반환. 클래스의 인스턴스가 / 좌측에 있을 때 호출됨. other는 / 우측에 위치한 참조 |
__divmod__(self, other) |
몫과 나머지 2개의 값을 가진 튜플을 반환 |
__pow__(self, other) |
제곱 ex) 2**4 = 16 |
+ 연산자
를 호출했다면 자동으로 __add__
가 호출되는 거고/ 연산자
를 호출했다면 자동으로 __truediv__
가 호출되는 거다.
이런 기본 연산자들을 객체에서도 사용하고 싶다면 위에서 얘기한 메소드들을 직접 구현해줘야 한다.
ex) Point 클래스
class Point :
def __init__ (self, x, y) :
self.x = x
self.y = y
def __add__(self, other) :
'''Point 객체끼리 + 연산자를 호출했을 때
동작하는 방식을 정의'''
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other) :
'''Point 객체끼리 - 연산자를 호출했을 때
동작하는 방식을 정의'''
return Point(self.x - other.x, self.y - other.y)
def __mul__(self, n) :
'''Point 객체와 정수 연산에서 * 연산자를 호출했을 때
동작하는 방식을 정의'''
return Point(self.x * n, self.y * n)
def __repr__(self) :
return "Point(" + str(self.x) + ", " + str(self.y) + ")"
>>> pt1 + pt2
Point(13, 17)
>>> pt1 - pt2
Point(7, 13)
⑤ 단항 산술 연산자
ex) my_pt = Point(3, 4)를 실행했다.
이때, +my_pt, -my_pt, math.abs(my_pt), ~my_pt, bool(my_pt),
math.round(my_pt, 3), math.floor(my_pt), math.ceil(my_pt), math.trunc(my_pt) 을 실행할 때
아래의 매직 메서드들을 호출하면서 실행된다.
메서드 문법 | 설명 |
---|---|
__pos__(self) |
+ 가 붙는 경우. 거의 대부분은 원래 상태 그대로 반환하는 걸로 정의됨 |
__neg__(self) |
- 가 붙는 경우 |
__abs__(self) |
절댓값. abs 함수에 의해 호출된다 |
__invert__(self) |
비트 반전. 비트값 1은 0으로 & 0은 1로 변환함 |
__bool__(self) |
bool() 뿐만 아니라 not이나 조건문에 응답하는 제어 구조와 같은 논리 연산자를 사용하는 경우 |
__round__(self, n) |
반올림 함수 |
__floor(self)__ |
내림 함수. 객체의 값보다 큰 가장 작은 정수를 반환. math.floor 함수에 의해 호출됨 |
__ceil(self)__ |
올림함수. 객체의 값보다 작은 가장 큰 정수를 반환. math.ceil 함수에 의해 호출됨 |
__trunc__(self) |
소수점 버림 함수 |
ex) Point 클래스에 __neg__(self)
정의
class Point :
def __neg__(self) :
return Point(-self.x, -self.y) # 해당 return문을 통해 새로운 Point 객체가 생성된다는 걸 알 수 있다.
def __trunc__(self) :
return Point(self.x.__trunc__(), self.y.__trunc__())
pt1 = Point(3.3, -4.5)
pt2 = -pt1 # Point(-3.3, 4.5)가 저장됨
pt3 = math.trunc(pt1) # Point(3.0, -4.0)이 저장됨
⑥ 리플렉션(역방향) 메서드
pt1 * 5 # 좌측 피연산자가 pt1이다.
# 그래서, 연산을 위해서 pt1에서 __mul__이 정의되어 있어야 한다.
5 * pt1 # pt1이 좌측에 있지 않고 우측에 있다.
# 이럴때 정의해야 하는게 __mul__의 리플렉션 메서드 __rmul__이다.
위와 같이 객체
가 우측에서 계산
되는 경우가 존재할 수 있다. 이를 위해서 정의하는게 리플렉션 연산자 메서드이다.
내용은 ④에서 다뤘던 내용과 동일하다.
다만, 좌측 피연산자가 ④의 연산자를 정의하지 않았거나 NotImplemented
를 반환한다면 수행된다는 점에서 차이가 있다.
| 문법 | 설명 |
| __radd__(self, other)
| 우측 덧셈 연산자 |
| __rsub__(self, other)
| 우측 뺄셈 연산자 |
| __rmul__(self, other)
| 우측 스칼라 곱셈 연산자 |
| __rfloordiv__(self, other)
| 우측 정수 나눗셈 연산자(//) |
| __rtruediv__(self, other)
| 우측 나눗셈 연산자(/) |
| __rmod__(self, other)
| 우측 나머지 나눗셈 연산자(%) |
| __rdivmod__(self, other)
| 우측 몫/나머지 반환 함수(divmode) |
| __rpow__(self, other)
| 우측 제곱 연산자 |
⑦ 교체 연산자 메서드
모든 클래스에 +=
, -=
, *=
, /=
등등의 산술 & 대입 연산자 기능을 제공하는 매직 메서드가 있다.
여기서 쓰이는 i는 in-place의 약자이다.
문법 | 설명 |
---|---|
__iadd__(self, other) |
+= 연산을 구현. 교체한 연산이 성공적으로 구현되려면 self를 반환해야 한다 |
__isub__(self, other) |
-= 연산을 구현. 이제부터 나머지 내용은 동일 |
__imul__(self, other) |
*= 연산을 구현 |
__idiv__(self, other) |
/= 연산을 구현 |
__igrounddiv__(self, other) |
//= 연산을 구현 |
__imod__(self, other) |
%= 연산을 구현 |
__ilshift__(self, other) |
비트 좌측 시프트를 수행하는 <<= 연산을 구현 |
__irshift__(self, other) |
비트 우측 시프트를 수행하는 >>= 연산을 구현 |
__iand__(self, other) |
바이너리 AND를 수행하는 &= 연산을 구현 |
__ior__(self, other) |
바이너리 OR를 수행하는` |
__ixor__(self, other) |
바이너리 exclusive-OR를 수행하는 ^= 연산을 구현 |
__ipow__(self, other) |
제곱 연산자 수행하는 **= 연산을 구현 |
ex)
a = MyClass(10)
b = a
a += 1
print(a, b) # a와 b는 같을까?
- MyClass 클래스가
__add__
를 지원 &__iadd__
는 지원 안 하는 경우
⇒ 파이썬은 대입 연산
을 지원한다. 다만, 메모리의 값을 교체하는 게 아니라 새로운 객체를 생성
해서 변수에 대입한다.
즉, a와 b는 a += 1
연산을 실행함으로써 서로 다른 객체를 가리키게 된다.
ex2) __iadd__
, __imul__
메서드 구현 예시
def __iadd__(self, other) :
self.x += other.x
self.y += other.y
return self
def __imul__(self, other) :
self.x *= other.x
self.y *= other.y
return self
⑧ 변환 메서드
문법 | 설명 |
---|---|
__int__(self) |
int 변환 함수 사용 시 호출. 정수 객체를 반환해야 함 |
__float__(self) |
float 변환 함수 사용 시 호출. 부동소수점 객체를 반환해야 함 |
__complex__(self) |
복소수(complex) 변환 함수 사용 시 호출. 복소수-숫자 객체를 반환해야 함 |
__index__(self) |
객체가 컬렉션의 색인 or 슬라이싱 연산의 범위로 주어진 경우 실제 인덱스 정수 숫자를 반환해야 한다 |
__bool__(self) |
이미 다룬 내용 |
ex) Point 클래스의 변환 메서드 정의
class Point :
def __init__(self, x = 0, y = 0) :
self.x = x
self.y = y
def __int__ (self) : # int 변환함수를 사용할 때 x,y의 정수값의 합을 반환하도록 함
return int(self.x) + int(self.y)
def __float__(self) : # float 변환함수를 사용할 때 x,y의 부동소수점 값의 합을 반환하도록 함
return float(self.x) + float(self.y)
>>> p = Point(100, 20)
>>> int(p)
120
>>> float(p)
120.0
⑨ __iter__
와 __next__
구현하기
- 용어 정리
1) 이터러블(iterable) : 여러 항목을 한 번에 한 항목씩 접근해 첫 항목부터 끝 항목까지 관통할 수 있는 객체를 의미
객체가 iterable이 되려면 __iter__
메서드는 반드시 객체를 반환해야 한다
2) 이터레이터(iterator) :
- __iter__
가 반드시 반환해야 하는 객체. 컬렉션 객체를 관통하는데 사용됨
- __next__
메서드가 반환해야 하는 객체. __next__
는 아무 작업을 하지 않더라도 구현되어야 한다.
이 메서드들은 파이썬의 for문
에서 클래스 인스턴스
를 사용하려면 반드시 필요하다.
ex) 4차원의 Point 객체가 있다고 하자
이터레이터가 4개의 좌표값을 한 번에 하나씩 가져올 수 있다면 다음과 같이 사용할 수 있다.
이렇게 사용할 수 있다는 건 __iter__
메서드는 self를 반환하고 __next__
메서드를 자체적으로 구현했을 것이다
my_pt = Point()
for i in my_pt :
print(i)
컨테이너의 복잡성과 유연성 수준에 따라 여러 방법으로 구현할 수 있다.
1) 대상 내에 저장된 컬렉션 객체의 __iter__
를 호출 ⇒ 가장 쉬운 방법 & 다른 누군가가 순회 작업을 제어해야 함
2) 컬렉션 클래스 자체적으로 __iter__
, __next__
를 모두 구현 ⇒ 한 번에 한 루프 이상 순회할 수 없음
3) __iter__
메서드가 컬렉션 클래스를 관통하는 이터레이션을 지원하는 목적으로 만들어진 자체 이터레이터 객체를 반환 ⇒ 가장 강력한 방법
9.11 다중 인수 타입 지원
ex)
def __mul__(self, other) :
if type(other) == Point : # Point 객체와 곱해지는 값이 Point 객체라면
# 두 객체의 x, y 값을 각각 곱한 새로운 객체를 반환한다.
return Point(self.x * other.x, self.y * other.y)
elif type(other) == int or type(other) == float : # 실수 또는 정수 값과 곱해진다면
# 스칼라 곱셈을 한 새로운 객체를 반환한다
return Point(self.x * other, self.y * other)
else : # 이도 저도 아니면 NotImplemented를 반환한다.
# 그러면 파이썬에서 우측 피연산자의 __rmul__ 메서드가 있는지 확인할 것이다.
return NotImplemented
똑같은 내용을 isinstance
메서드를 이용해서 작성할 수 있다.
def __mul__(self, other) :
if isinstance(other, Point) : # Point 객체와 곱해지는 값이 Point 객체라면
# 두 객체의 x, y 값을 각각 곱한 새로운 객체를 반환한다.
return Point(self.x * other.x, self.y * other.y)
elif isinstance(other, (int, float)) : # 실수 또는 정수 값과 곱해진다면
# 스칼라 곱셈을 한 새로운 객체를 반환한다
return Point(self.x * other, self.y * other)
else : # 이도 저도 아니면 NotImplemented를 반환한다.
# 그러면 파이썬에서 우측 피연산자의 __rmul__ 메서드가 있는지 확인할 것이다.
# 있다면 그걸 이용해서 곱셈을 진행하면 되고
# 없다면 클래스에서 __ruml__ 메소드를 작성해줘야 한다.
return NotImplemented
9.12 동적 속성 설정 및 조회
파이썬 객체는 수많은 속성(인스턴스 변수, 메서드, 매개변수 등등) 을 가질 수 있다.
일반적으로 이런 속성들은 하드 코딩되어 있어서 이름이 정해져 있다.
하지만, 때때로 동적으로 속성을 설정하는 것이 유용할 때가 있다.
프로그램 실행 시점에 특정 조건에 따라 속성 이름을 결정하는 것이다.
ex)
>>> class Dog :
... pass
>>> d = Dog()
>>> setattr(d, 'breed', 'Great Dane') # breed라는 속성을 추가 & 속성 값은 'Great Dane'으로 설정
>>> getattr(d, 'breed') # 객체 d의 breed 속성의 값을 반환한다
'Great Dane'
'Python' 카테고리의 다른 글
[파이썬 코드 업] 9-1장. 클래스와 매직 메서드 (0) | 2023.05.06 |
---|---|
[파이썬 코드 업] 4-3장. 데코레이터 (0) | 2023.05.06 |
[파이썬 코드 업] 4-2장. (0) | 2023.05.06 |
[파이썬 코드 업] 4-1장. 프로그래밍 지름길 (0) | 2023.05.05 |
[파이썬 스킬 업] 3장. 리스트 기능 (0) | 2023.04.27 |