Programming,  Python

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

Leave a Reply

Your email address will not be published. Required fields are marked *