[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__’] |