Skip to content

Latest commit

 

History

History
304 lines (245 loc) · 11.1 KB

211115.md

File metadata and controls

304 lines (245 loc) · 11.1 KB

Day 43

한 권으로 개발자가 원하던 파이썬 심화 A to Z

함수 체인 구성

함수나 메소드를 연속 실행해서 로직을 처리할 수 있는데 이렇게 연속 처리하는 것을 체인이라고 한다.
종료되는 함수에는 체인을 종료하는 로직을 넣어줘야 한다.

import functools as ft

def add(x, y):
    return ft.partial(mul, x + y)

def mul(x, y):
    return ft.partial(sub, x * y)

def sub(x, y):
    return ft.partial(div, x - y)

def div(x, y):
    return x / y

add(5, 6)(4)(5)(5) # 7.8
# 처음 호출하는 함수만 2개의 인자를 받고, 나머지는 부분함수라서 하나의 인자만 받는다.

메소드 체인 구성

하나의 객체로 다양한 메소드를 조합해서 특정 결과를 만들 때 메소드를 연속으로 호출해서 체인을 구성할 수 있다.

(100).__add__(5).__mul__(4).__sub__(5).__truediv__(3) # 138.333333333334
# 스페셜 메소드의 연속 호출

메소드 체인을 수행할 클래스를 작성하되, 내부에 정의된 모든 함수는 self를 반환한다.

class cal:
    def __init__(self, value=0):
        self.value = value

    def add_num(self, value):
        self.value += value
        return self

    def mul_num(self, value):
        self.value *= value
        return self

    def sub_num(self, value):
        self.value -= value
        return self

    def div_num(self, value):
        self.value /= value
        return self

a = cal(100)
b = a.add_num(5).mul_num(4).sub_num(5).div_num(3)
b.__dict__ # {'value': 138.333333333334}

같은 이름 메소드 오버로딩

객체 지향은 매개변수가 같은 이름으로 다른 메소드를 재정의하는 오버로딩을 지원한다.
파이썬은 실제 함수나 메소드를 이름으로만 관리하여 오버로딩을 작성할 수 없으나, 오버로딩을 처리할 수 있는 모듈을 제공한다.

class overload:
    def __init__(self, fget1=None, fget2=None, fget3=None):
        self.fget1 = fget1 # 3개의 같은 메소드를 저장하게 객체의 속성 3개 정의
        self.fget2 = fget2
        self.fget3 = fget3

    def __get__(self, other, owner): # 디스크립터 클래스를 만들어서 이름으로 조회하면 객체를 반환
        self._other = other
        return self

    def add(self, func): # 두 번째와 세 번째 함수를 등록하는 함수
        if not self.fget2:
            self.fget2 = func
        else:
            if not self.fget3:
                self.fget3 = func
        return self

    def __call__(self, *args): # 들어온 인자에 따라 저장된 함수를 실행
        if len(args) == 0: # 인자 없이 호출
            print(self.fget1(self._other, *args))
        elif len(args) == 1: # 인자가 하나일 때
            print(self.fget2(self._other, *args))
        elif len(args) == 2: # 인자가 둘일 때
            print(self.fget3(self._other, *args))
        else:
            print('Check again')


# 위에서 정의한 overload를 사용하는 클래스 정의
class Overload:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @overload # 처음 함수는 overload 클래스를 데코레이터로 처리해서 등록한다.
    def add(self):
        return self.x + self.y

    @add.add # 1개의 매개변수를 받는 같은 함수를 추가 등록한다.
    def add(self, z):
        return self.x + self.y + z

    @add.add # 2개의 매개변수를 받는 같은 함수를 추가 등록한다.
    def add(self, a, b):
        return self.x + self.y + a + b


Overload.__dict__ # 클래스 이름공간을 확인하면 add가 하나만 등록되어 있고, add는 overload의 객체임을 알 수 있다.

o = Overload(5, 5)
o.add # Overload 클래스로 객체를 만들고 add 속성을 확인하면 overload 객체가 조회된다.

o.add.__dict__ # overload 객체의 이름공간을 확인하면 3개의 함수가 저장된 것을 볼 수 있다.
'''
{'fget1': <function __main__.Overload.add(self)>,
'fget2': <function __main__.Overload.add(self, z)>,
'fget3': <function __main__.Overload.add(self, a, b)>,
'_other': <__main__.Overload at ~~~}

_other는 클래스로 객체를 만든 정보를 관리하는 속성이다.
'''

o.add # 10
o.add(1) # 11

수학 연산자

표현식을 작성할 때 수학 연산자를 사용하지만, 실제 실행될 때는 이에 대응하는 클래스 내의 스페셜 메소드로 처리된다.

import operator as op

count = 0
for i in dir(int): # 정수 클래스 내의 속성과 메소드 확인
    if i in dir(op): # 연산자 모듈의 스페셜 메소드 확인
        if i not in ['__doc__', '__index__']:
            count += 1
            print(i, end=', ')
            if count % 5 == 0:
                print()

출력 결과를 통해 정수는 산술 연산, 비교 연산, 관계 연산 등을 처리하는 것을 알 수 있다.

# 덧셈, 뺄셈, 곱셈
10 + 2, 10 - 2, 10 * 2 # (12, 8, 20)
(10).__add__(2), (10).__sub__(2), (10).__mul__(2) # (12, 8, 20)

# 나눗셈
10 // 2, 10 / 2 # (5, 5.0)
(10).__floordiv__(2), (10).__truediv__(2) # (5, 5.0)

# 나머지, 제곱
10 % 2, 10 ** 2 # (0, 100)
(10).__mod__(2), (10).__pow__(2) # (0, 100)

# 단항 연산자
-1, -(-1), abs(-1) # (-1, 1, 1), abs는 절댓값
(1).__neg__(), (-1).__pos__(), (-1).__abs__() # (-1, 1, 1)

# 비교 연산자
10 > 2, 10 < 2 # (True, False)
(10).__gt__(2), (10).__lt__(2) # (True, False)

10 == 2, 10 >= 2, 10 <= 2, 10 != 2 # (False, True, False, True)
(10).__eq__(2), (10).__ge__(2), (10).__le__(2), (10).__ne__(2) # (False, True, False, True)

# 논리 연산자
# and
(10 > 5) and (10 > 2), (10 > 5) & (10 > 2) # (True, True)
(10 > 5).__and__(10 > 2) # True

# or
(10 > 5) or (10 > 2), (10 > 5) | (10 > 2) # (True, True)
(10 > 5).__or__(10 > 2) # True

# xor
(10 < 5) ^ (10 > 2) # True
(10 < 5).__xor__(10 > 2) # True

# 시프트 연산
32 << 2, 32 >> 2 # (128, 8)
(32).__lshift__(2), (32).__rshift__(2) # (128, 8)

# ~
int(bin(32), base=2), int(bin(~32), base=2) # (32, -33)
~32, (32).__invert__() # (-33, -33)

객체 접근 연산자

클래스로 이름공간에 접근할 때는 메타 클래스에 있는 접근 연산자를 사용하고, 객체가 이름공간에 접근할 때는 클래스에 있는 접근 연산자를 사용한다.
즉, 접근 연산은 생성 관계가 성립하는 클래스의 객체 접근 연산을 사용해서 처리한다.

class Attribute:
    pass
a = Attribute()

# 속성 추가
setattr(a, 'name', 'add attr')
a.__dict__ # {'name': 'add attr'}

# 속성 조회
getattr(a, 'name') # 'add attr'

# 속성 삭제
if hasattr(a, 'name'): # 객체 내에 속성이 있는지 확인
    delattr(a, 'name') # 속성 삭제

객체를 만들고 객체 접근 연산에 점 연산을 사용하면 클래스 내에 스페셜 메소드 __getattribute__를 호출한다.

class ObjectAttr:
    classAttr = 'ObjectAttr
    def __init__(self, value):
        self.objattr = value

    def __getattribute__(self, name):
        print('__getattribute__')
        return super().__getattribute__(name)

    def __getattr__(self, name): # 객체에서 속성이나 메소드를 조회했을 때, 찾을 수 없으면 예외 처리하는 스페셜 메소드 정의
        return 'No attr'

# 클래스로 객체를 생성할 때 객체가 점 연산을 사용하므로 __getattribute__ 메소드 내부의 print문을 실행한다.
obj = ObjectAttr('instance attr') # __getattribute__가 몇 번 출력됨
obj.__dict__ # 점 연산을 한번 실행했으므로 __getattribute__가 1번 출력된다.

클래스 속성에 접근할 때는 클래스에 정의된 __getattribute__ 메소드가 아닌, 메타 클래스 type에 있는 __getattribute__를 호출해서 사용한다.

ObjectAttr.classAttr # 'ObjectAttr', 메타 클래스의 스페셜 메소드를 사용해서 __getattribute__가 출력되지 않는다.

obj.classAttr # 객체에서 클래스 속성에 접근할 때는 클래스에 있는 스페셜 메소드를 사용해서 __getattribute__가 출력된다.

객체 이름공간에 없는 속성을 접근할 때 __getattribute__로 처리한 후에 검색하지 못하면 __getattr__을 자동으로 호출해 예외 대신 반환값을 처리한다.

obj.name # __getattribute__와 'No attr'이 출력된다.

# 클래스에 없는 속성에 접근하면 메타 클래스 내에 __getattr__이 추가로 정의되지 않아 예외가 발생한다.
ObjectAttr.name # 클래스 속성만 검색하므로 정의한 것이 없어서 예외 발생

색인 연산자

import collections as cols
import operator as op

class Seq(cols.UserList):
    def __getitem__(self, i): # 색인 연산 조회를 처리하는 스페셜 메소드 재정의
        print('Seq getitem')
        return self.data[i]

    def __setitem__(self, i, item): # 색인 연산 갱신을 처리하는 스페셜 메소드 재정의
        print('Seq setitem')
        if i == len(self.data): # 인덱스 범위를 하나 벗어난 경우에는 추가하도록 처리
            self.data.append(item)
        else: # 인덱스 범위 내는 값을 갱신
            self.data[i] = item

l = Seq()
l.__dict__ # {'data': []}

l[0] = 100 # Seq setitem
op.setitem(l, 1, 200) # Seq setitem 출력, 함수를 처리하는 곳에 setitem 함수를 처리하면 클래스 내부의 스페셜 메소드가 실행되어 원소를 추가한다.

l.__dict__ # {'data': [100, 200]}

l[0] # Seq getitem과 100 출력, 색인 조회 연산도 클래스 내의 스페셜 메소드를 호출한다.
op.getitem(l, 1) # Seq getitem과 200 출력, 색인 함수로 호출해도 클래스 내의 스페셜 메소드를 호출한다.

딕셔너리 색인 검색

class Udict(cols.UserDict):
    def __getitem__(self, key): # 딕셔너리 키 값 조회
        print('Udict getitem')
        return self.data[key]

    def __setitem__(self, key, item): # 딕셔너리 키를 갱신하거나 추가
        print('Udict setitem')
        self.data[key] = item

ud = Udict()
ud.__dict__ # {'data': {}}
ud['nokey'] # Udict getitem을 출력하고 예외 발생

딕셔너리 클래스를 상속하고 구현했기에 키 검색 에러를 방지하는 get, setdefault 메소드를 그대로 사용할 수 있다.

ud.get('nokey', 'defaults') # Udict getitem과 'defaults'를 출력
ud.setdefault('nokey', 'defaults') # Udict getitem을 2번, 'defaults'를 1번 출력, setdefault는 키가 없으면 삽입한다.
ud.__dict__ # {'data': {'nokey': 'defaults'}}

색인 연산에도 예외 발생을 조절하기 위한 스페셜 메소드 __missing__을 제공한다.
__getitem__에서 검색이 실패한 경우, 자동으로 __missing__ 메소드를 실행한다.

class Udict_(cols.UserDict):
    def __missing__(self, key):
        print('Udict missing')

    def __setitem__(self, key, item):
        print('Udict setitem')
        self.__dict__[key] = item

ud_ = Udict_()
ud_['miss'] # Udict misssing 출력, 없는 키로 조회해도 예외가 발생하지 않고 __missing__ 메소드가 자동으로 호출되어 처리한다.