엄범


어떤 클래스에 추가 기능이 필요한 경우, 파이썬에서는 (뿐만 아니라 대부분의 언어에서)

1. Composition(Wrapper) : backing property를 이용. (+ operator overloading)

2. Extern   : 상속받아서 추가 기능 구현

두 가지 방법을 생각할 수 있다.

코틀린과 같은 언어는 여기서 확장 함수를 사용하는 방법도 생각해 볼 수 있지만, 파이썬은 이를 지원하지 않는다.


보통 상속이 더 구현하기 까다롭고 복잡한 경우가 많다. 상속하다 보면 부모 클래스 쪽의 구현사항에서 이것 저것 신경써야 하는 경우도 많고... 예를들면 __slots__같은 거라던가...


그래서 이 둘을 어떻게 구분하는게 좋냐면, 

구현 측면에서도 그렇고 객체 지향 설계 측면에서도 그렇고

그냥 일반적인 기능 추가(단순히 추가 메서드가 필요하다던가...)는 Composition으로 처리하는게 더 깔끔하고

객체 상속 계층을 따져보면 상속을 받을 수 밖에 없거나, 타입 문제 때문에 하위 클래스로 만들어 다형성을 이용해야 하는 경우 등의 케이스는 Extern을 사용하는게 깔끔하다.


그러니까 둘 다 가능한 경우는 보통 Composition로 처리하는게 좋은 듯.

상속은 단순 기능 추가 보다는 좀 더 의미론적으로 접근하는게 맞는 것 같고...


np.ndarray의 경우

```python

import numpy as np


# sol 1.

class OperatorOverloading:

    def __init__(self):

        self.board = np.zeros((8, 8), dtype=int)


    def __getitem__(self, point):

        i, j = point

        return self.board[i][j]


    def myFunc(self):

        return "my function!"


A = OperatorOverloading()

print(A[1,2])

print(A.myFunc())


# sol 2.

class Extern(np.ndarray):

    """

    https://docs.scipy.org/doc/numpy/user/basics.subclassing.html

    type casting하는 방식 말고, 좀 더 복잡하지만 __new__ 등을 overloading해서 완전 서브클래스로 사용하는 방법도 있다.

    """

    def myFunc(self):

        return "my function!"


_B = np.zeros((8, 8), dtype=int)

B = _B.view(Extern)

print(B[1][2])

print(B.myFunc())


# 확장 함수는 지원하지 않음.

C = np.zeros((8, 8), dtype=int)

C.myFunc = lambda x: "my function!"

print(C.myFunc())    # AttributeError: 'numpy.ndarray' object has no attribute 'myFunc'

```


socket.socket의 경우

```python
class LogSocket(socket.socket):
    def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, fileno=None):
        socket.socket.__init__(self, family, type, proto, fileno)
        self.next_retry_time = 0
        self.retry_factor = 2
        self.retry_period = 1
        self.retry_time_max = 30

    def recv(self, bufsize = 4096):
        cmd_raw = super.recv(bufsize)
        cmd = cmd_raw.decode()
        return cmd

    def exponential_backoff_connect(self, address):
        while True:
            result = self._exponential_backoff_connect(address)
            if result in (True, False):
                return result

    def _exponential_backoff_connect(self, address):
        now = time.time()

        if now > self.next_retry_time:
            try:
                print("Try...")
                self.connect(address)
                return True
            except OSError:
                self.next_retry_time = now + self.retry_period
                self.retry_period *= self.retry_factor
                if self.retry_period > self.retry_time_max:
                    print("retry_time_max! {}".format(self.retry_time_max))
                    return False
                print("Fail : wait {}".format(self.retry_period))
        return None
```

```python

class SocketWrapper:

    __slots__ = ("_sock")


    def __init__(self, sock):

        self._sock = sock

    

    def recv(self, bufsize = 4096):

        cmd_raw = self._sock.recv(bufsize)

        cmd = cmd_raw.decode()

        return cmd

```