[Python] Special Function
Python은 class 구조로 대부분 이뤄져있으며, 각 class는 __init__ 과 같은 special function으로 구성된다.
class Pair:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Pair({0.x!r}, {0.y!r})'.format(self)
def __str__(self):
return '({0.x}, {0.y})'.format(self)
p = Pair(3, 4)
p
| Pair(3, 4) |
__init__()
__init__: 기존 class의 constructor 역할을 한다.
__repr__()
__repr__: class의 객체를 출력했을 때 호출되는 함수이다.
위 코드에서 'Pair({0.x!r}, {0.y!r})'.format(self) 가 의미하는 것은, 중괄호 안에 0은 format()의 첫 번째 인자, 1이 들어가면 두 번째 인자를 가리킨다. 위 코드의 0은 self가 된다. !r은 만약 내부 멤버가 숫자가 아니라 class일 경우에 각 class에 대해서도 __repr__ 함수를 사용하라는 의미다.
__str__()
__str__: 객체를 출력할 경우 해당 함수가 스트링으로 치환되어 호출된다.
만약 __str__ 함수가 없다면 __repr__ 함수가 호출된다. 그리고 둘 다 없다고 하면 객체의 주소가 출력된다.
__repr__()는 eval(repr(x) == x와 같은 텍스트를 만드는 것이 표준이다.
p1 = eval(repr(p)) type(p) == type(p1)
위 값이 만족을 해야지 정확한 class가 되는 것이다.
__enter__()
Python에서 with 키워드를 사용할 때 수행되는 special function이다.
from socket import socket, AF_INET, SOCK_STREAM
class LazyConnection:
def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
self.address = address
self.family = AF_INET
self.type = SOCK_STREAM
self.sock = None
def __enter__(self):
if self.sock is not None:
raise RuntimeError('Already connected')
self.sock = socket(self.family, self.type)
self.sock.connect(self.address)
return self.sock
def __exit__(self, exc_ty, exc_val, tb):
self.sock.close()
self.sock = None
if __name__ == '__main__':
from functools import partial
c = LazyConnection(('www.python.org', 80))
with c as s:
s.send(b'GET /index.html HTTP/1.0\r\n')
s.send(b'Host: www.python.org\r\n')
s.send(b'\r\n')
resp = b''.join(iter(partial(s.recv, 8192), b''))
print('Got %d bytes' % len(resp))
| Got 1238 bytes |
위 코드를 분석해보면 __init__ 함수의 인자로 ('www.python.org', 80) tuple를 전달한다. 그리고 with 구문을 수행할 때 __enter__() 함수를 수행한다.
__exit__()
with 구문을 종료할 때 수행되는 special function이다.
만약에 file이라고 하면 file을 열고, 푸는 행동을 __enter__와 __exit__을 통해서 기술해주면 된다.
__dict__()
기본적으로 인스턴스마다 내부 멤버를 저장할 목적으로 dictionary를 구성한다. 이는 메모리 낭비의 원인이 된다. 간단한 class 구조를 통해서 설명해보면 아래와 같다.
class Date:
__slots__ = ['year', 'month', 'day']
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
d1 = Date(2020, 6, 1)
dir(d1)
| [‘__class__’, ‘__delattr__’, ‘__dir__’, … ‘__day__’, ‘__month__’, ‘__year__’] |

Python에선 __dict__ 객체를 통해 변수들을 가지고 있다. 아마 예상으로 RB tree 구조를 통해 멤버 변수를 가지고 있을 것이다. Python은 매우 유연한 언어의 특성을 가진다. 실제로 runtime 사에 동적으로 멤버변수가 추가될 수 있다.
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
d1 = Date(2020, 6, 1)
d1.name = "kim"
d1.__dict__
| {‘year’: 2020, ‘month’: 6, ‘day’: 1, ‘name’: ‘kim’} |

위 그림처럼 runtime에 실시간으로 멤버가 추가되거나 삭제될 수 있다. RB tree를 통해 실시간으로 빠르게 찾을 수 있지만 많은 메모리를 차지하는 단점이 존재한다.
__slots__()
기존 __dict__에 저장된 멤버들이 메모리를 많이 차지하기 때문에 이를 위해서 __slots__이 존재하는데, 이는 실시간으로 멤버를 추가하고 제거할 수는 없지만 대신 메모리를 절약 할 수 있다.
class Date:
__slots__ = ['year', 'month', 'day']
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
d1 = Date(2020, 6, 1)
dir(d1)
| [‘__class__’, ‘__delattr__’, ‘__dir__’, … ‘__day__’, ‘__month__’, ‘__year__’] |