Skip to content

Latest commit

 

History

History
347 lines (291 loc) · 10.5 KB

211111.md

File metadata and controls

347 lines (291 loc) · 10.5 KB

Day 40

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

람다 함수

람다 함수도 하나의 객체이므로 LambdaType 클래스에 의해 생성된다.
람다 함수는 별도의 반환문이 없어 표현식이 결과를 그대로 반환한다.

(lambda x,y : x+y)(5, 5) # 10

lam = lambda x,y : x+y # 이름이 없어 다시 사용하려면 변수에 저장해야 한다.

람다 함수는 익명 함수로 이름이 없는 함수이기 때문에 __name__ 속성을 확인하면 이름이 없다는 의미로 <lambda>를 표시한다.
이름이 없다는 것은 이름공간에 바로 등록할 수 없다는 뜻이기도 하다.

람다 표현식에서 함수 호출은 표현식이라 올 수 있지만, pass는 문장이기 때문에 사용할 수 없다.

(lambda x,y : print(x,y))(5, 5) # 5 5
(lambda x,y : pass)(5, 5) # 예외 발생

람다 함수를 이용한 합성 함수 처리

l = [1, 2, 3, 4]

func = lambda x : x*x
(lambda func,obj : [func(x) for x in obj])(func, l) # [1, 4, 9, 16]

c = lambda func : func # 람다 함수를 전달 받아 람다 함수 반환
c = c(func)
c(10) # 100

b = (lambda func : lambda *args,**kwargs : func(*args,**kwargs))
# 외부 람다 함수는 함수를 전달 받고, 반환되는 내부 람다 함수는 인자를 받고 전달받은 함수를 실행한다.
b = b(func)
b(10) # 100

외부 함수 안에 내부 함수 정의

외부 함수는 기준이 되는 함수이다.

def outer(x, y):
    def inner(): # 함수 안에 내부 함수 정의
        return x + y
    return inner() # 내부 함수를 실행해서 결과 반환

outer(5, 5) # 10

외부 함수의 지역 이름공간에는 매개변수와 내부 함수가 들어가 있고, 내부 함수의 이름공간에는 외부 함수의 매개변수가 들어가 있다.

def outer(x, y):
    def inner():
        print('inner local', locals())
        return x + y
    print('outer local', locals())
    return inner()

outer(5, 5)
'''
outer local {'inner': <function~~~~>, 'y': 5, 'x': 5}
inner local {'y': 5, 'x': 5}
10
'''

함수 이름공간의 스코프

스코프란 이름공간에 있는 변수를 참조하여 범위를 정하는 규칙을 말한다.

def func(x, y):
    result = result + x + y
    return result

func(5, 5) # 예외 발생

예외가 발생하는 이유는 함수 내부에 정의된 result 지역변수가 두 매개변수와 더할 때 변숫값을 가져오지 못해서 예외가 발생하기 때문이다. 이는 result를 변수에 할당하지 않고 내부적으로 인식해 result를 지역 이름공간에서만 찾기 때문이다.
즉, 변수를 먼저 값을 할당하고 사용해야 한다.

def func(x, y):
    result = 0
    result = result + x + y
    return result

func(5, 5) # 10

전역변수는 모듈에 정의되어 전역 이름공간에 저장되는 변수를 말한다. 전역변수와 지역변수가 이름이 같으면 지역 이름공간을 먼저 찾는다.

result = 0
def func(x, y):
    result = result + x + y
    return result

func(5, 5) # 예외 발생

global 예약어로 전역변수를 참조할 수 있게 만들 수 있다.

def func(x, y):
    global result
    result = result + x + y
    return result

지역 이름공간이 계층화된 경우

def outer():
    def inner():
        x = x + 1
        return x
    return inner()

값을 먼저 할당하고 사용해야 한다.

def outer():
    def inner():
        x = 0
        x = x + 1
        return x
    return inner()

x = 0을 외부 함수에 정의했을 경우에는 x가 내부 함수의 지역 이름공간에 없기 때문에 예외가 발생하여 nonlocal 예약어를 사용해주어야 한다.

def outer():
    x = 0
    def inner():
        nonlocal x
        x = x + 1
        return x
    return inner()

외부 함수에서 x에 빈 리스트를 할당하고 내부 함수에서 원소를 추가하고 할당하려고 하면 예외가 발생하는데 이는 먼저 할당될 변수를 인식하기 때문이다.
변경할 수 있는 리스트는 어디서나 객체를 추가할 수 있기 때문에 내부 함수의 변수 할당을 없애면 리스트 객체에 원소를 추가한 값을 반환한다.

def outer():
    x = []
    def inner():
        x.append(5)
        return x
    return inner()

클로저(closure) 환경

클로저란 외부 함수 내에 내부 함수를 작성하고, 내부 함수를 외부로 반환할 때 내부함수에서 외부함수의 지역변수를 계속 사용하는 환경을 말한다.

클로저 환경이 구성되면 자유 변수에 대한 정보가 __closure__ 속성에 저장된다.

def outer(x):
    def inner(y):
        return x + y
    return inner

a = outer(5)

a.__name__ # 'inner', 변수 a에 내부함수가 저장되었다.
a.__closure__[0].cell_contents # 5, 내부에 저장된 것을 확인한다.

부분함수 처리

부분함수란 함수의 인자를 구분해 처리하는 것을 말한다.

# 부분함수를 호출한 후에 내부함수를 호출해야 실제 덧셈이 처리되는 구조
def outer(x):
    def inner(y):
        return x + y
    return inner

inner = outer(5)
inner(10)

커링 처리

커링은 부분함수를 다르게 부르는 말이다.

functools 모듈 사용
patial 클래스는 부분함수를 처리하는 클래스다.

import functools as fs

def add(x, y, z):
    return x + y + z

add = fs.partial(add, 1, 2) # 부분함수를 처리하기 위해 함수와 2개의 인자 전달
add(3) # 6, 나머지 하나의 인자를 전달해서 실행한다.

pymonad 모듈 사용

from pymonad import curry
@curry # 실행 함수에 부분 함수를 데코레이터 처리
def f(x, y, z):
    return x + y + z

cu = f(1)
cu = cu(2)
cu = cu(3)
cu # 6

데코레이터 처리를 하는 것과 안하는 것의 차이

# 데코레이터 처리한 것
def outer(func):
    def inner():
        func()
    return inner

@outer
def func():
    print('decorate')

func() # decorate

# 데코레이터 처리하지 않은 것
def outer(func):
    def inner():
        func()
    return inner

def func1():
    print('No decorate')

No_deco = outer(func1)
No_deco() # No decorate

함수 객체에 속성 추가

함수를 정의할 때 내부 함수 이름에 점 연산을 사용해서 속성을 추가할 수 있다.

def func(x, y):
    func.sum = func.sum + x + y
    return x + y

func.sum = 0 # 외부에서 함수 속성에 초깃값 정의도 가능하다.
func.__dict__ # {'sum': 0}, 이름공간 확인

func(1, 5) # 6
func.__dict__ # {'sum': 6}

hasattr을 이용해서 함수내부에 함수객체의 속성이 없으면 초기화

def func1(x, y):
    if not hasattr(func1, 'sum'):
        func1.sum = 0

    func1.sum = func1.sum + x + y
    return x + y

func1(5, 5) # 10
func1.__dict__ # {'sum': 10}

실행되는 함수 정보 공유

memorization은 함수의 객체 이름공간에 속성을 추가해서 반복되는 계산이 있을 때 전에 처리한 결과를 메모리에 저장해서 매번 다시 검색해 실행하지 않도록 해서 실행 속도를 빠르게 하는 기술이다.

일반 팩토리얼 함수

def fact(n):
    if (n == 0) or (n == 1):
        return 1
    return n * fact(n-1)

fact(10) # 함수를 실행할 때마다 모든 값을 다시 계산한다.

이전에 실행된 값을 저장하도록 수정한 팩토리얼 함수

def factorial(k):
    if not hasattr(factorial, '_memo'): # 계산한 값을 저장하도록 속성 정의
        factorial._memo = {1:1}
    
    if k not in factorial._memo: # 계산한 결과가 없으면, 먼저 함수 속성에 팩토리얼을 계산해서 할당
        factorial._memo[k] = k * factorial(k-1)
    return factorial._memo.setdefault(k, 1) # 계산한 결과가 있으면 조회

factorial(5) # 120
factorial._memo # {1: 1, 2: 2, 3: 6, 4: 24, 5: 120}, 계산한 값들이 딕셔너리에 들어가 있다.

기존에 계산된 인자를 전달하면 재귀 호출을 하지 않고 이름공간에서 검색한 결과만 반환한다.

외부 함수와 내부 함수 정의로 만들기

def momorize(func): # memorization만 처리하는 함수
    memorize.cache = {}

    def inner(x):
        if x not in memorize.cache:
            print('Value is not in cache')
            for i in range(0, x+1):
                if i not in memorize.cache:
                    memorize.cache[i] = func(i)
        
        return memorize.cache[x]
    return inner

a = memorize(fact)
a(5) # 120
memorize.__dict__ # {'cache': {0: 1, 1: 1, 2: 2, 3: 6, 4: 24, 5: 120}}

functools의 lru_cache 함수 사용

import functools

@functools.lru_cache(maxsize=None)
def fact(n):
    if (n == 0) or (n === 1):
        retrun 1
    
    return n * fact(n - 1)

fact(10)

# cache_info 메소드로 메모리에 저장된 정보를 볼 수 있다.
fact.cache_info()

# 데코레이터로 처리하면 스페셜 속성인 __wrapped__에 함수가 전달되어 내부에 보관된다. 이 속성을 실행하면 fact가 실행된다.
fact.__dict__ # {'__module__': ~~~~, '__wrapped__': <function ___main__.fact(n)>}
fact.__wrapped__(5) # 120

멀티 디스패치 처리

파이썬은 함수를 이름으로만 검색해서 매개변수가 다를 경우 작성할 수가 없다.
멀티 디스패치로 여러 함수를 정의하면 내부에 저장했다가 매개변수가 같으면 저장된 함수를 실행해서 결과를 반환하는 것이 가능하다.
multipledispatch 모듈을 사용하면 하나의 이름으로 여러 함수를 정의해서 사용할 수 있다.

from multipledispatch import dispatch

#데코레이터로 처리하면 join 이름을 가진 곳에 함수를 등록할 수 있다.
# int + int
@dispatch(int, int)
def join(x, y):
    print('int + int')
    return x + y

join(10, 10) # int + int와 20이 출력

# float + int
@dispatch(float, int)
def join(x, y):
    print('float + int')
    return x + y

join(10.2, 10) # float + int와 20.2가 출력

join의 자료형은 Dispatcher 클래스의 객체이다.
객체 안에 저장된 함수는 funcs 속성을 조회하면 확인할 수 있다.
매개변수의 자료형은 키로, 값은 함수로 저장된다.

type(join) # multipledispatch~~~

join.name # 'join', 객체 이름과 함수 이름이 같다.

join.funcs # {(int, int): <function~~~>, (float, int): <function~~~>}

join.funcs[(int, int)] # <function~~~>, 키를 조회한다.