Closure & Decorator

Jina·2020년 3월 27일
0

Today I Learned

목록 보기
5/21
post-thumbnail

Nested Function

중첩함수

  • 함수 내에 정의된 함수
  • 중첩함수는 상위 함수 내에서 호출 및 반환 가능
  • 중첩함수는 상위 함수 외부에서 호출 불가
  • 중첩함수를 사용하면 가독성을 높일 수 있음

예시 1 ) 중첩함수는 상위 함수 내에서 호출 및 반환 가능

def outer_func():
    print('외부함수')
    
    # 중첩 함수
    def inner_func():
    	return '중첩함수'
        
    print(inner_func())
    # 함수에서 중첩함수 호출 가능

outer_func()
# 외부함수
# 중첩함수

예시 2 ) 중첩함수는 상위 함수 외부에서 호출 불가

def outer() : 
    x=100
    def inner():
        x=1000
        return x    
    
    return x


print(outer())  # 100

print(inner())
# NameError: name 'inner' is not defined
# 상위 함수(외부 함수) 밖에서 호출 불가능

First Class Object

객체 (object)

  • 메모리에 존재하는 데이터를 가리키는 개념
  • 파이썬의 모든 데이터는 객체나 객체 간의 관계로 표현됨
  • 숫자, 문자열, 튜플 등

First Class Object

다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체

보통 아래의 연산을 지원할 때 일급 객체라고 함

  • 변수(variable)에 담을 수 있음
  • 매개변수(parameter)로 전달 할 수 있음
  • 반환 값(return value)이 될 수 있음

파이썬에서 함수는 일급 객체

예시 1 ) 함수를 변수에 할당 가능


def yell(text):
    return text.upper()

# 함수를 변수에 할당
bark = yell

print(yell('python'))
# PYTHON

print(bark('python'))
# PYTHON

예시 2 ) 함수를 매개변수(parameter)로 전달 가능


def yaho(func) :
    return func('yaho!')

# 함수 yell을 매개변수로 전달

print(yaho(yell)) 
# YAHO!

예시 3 ) 함수가 return value가 될 수 있음

def add(a,b) :
    return a+b

# 함수를 return value로 사용할 수 있음

def cal(*args):
    return add(*args)-3
    
print(cal(4,3))
# 4

위의 예시들을 통하여 파이썬에서 함수는 1급 객체라는 것을 확인할 수 있음

이러한 1급 객체의 특성이 있어야 closure가 성립

nonlocal

outer 함수의 안에 있으며 inner 함수의 밖에 있는 영역

자세한 내용은 여기를 참조

Closure

  • 중첩 함수가 상위 함수의 변수/ 정보를 가두어 사용하는 것
  • 내부 함수(중첩 함수)가 외부 함수(상위 함수)의 맥락에 접근할 수 있는 것

closure의 조건

  • 중첩 함수여야 함
  • 외부 함수 내의 값을 반드시 참조해야 함
  • 외부 함수는 중첩 함수를 return해야 함

closure 예시

아래는 closure의 예시

def root_cal(number) :
    
    def nth_root(rt):
        return number**(1/rt)
    
    return nth_root

아래의 그림과 같은 구조로 이루어져 있음

이 함수를 이용하여 계산을 진행하면 다음과 같이 진행됨

def root_cal(number) :
    
    def nth_root(rt):
        return number**(1/rt)
    
    return nth_root

find_1000_nth_root=root_cal(1000)

print(find_1000_nth_root(2))
# 31.622776601683793

print(find_1000_nth_root(3))
# 9.999999999999998

진행 순서를 도식화 하면 다음과 같음

3 --> 4 번 순서를 지나며
root_cal(1000)의 결과가 nth_root(rt)를 return한다는 것을 저장

5번 순서를 지나며 find_1000_nth_root가 nth_root(rt)값을 받아옴을 저장

6번 순서를 지나며 rt=3

따라서 31.6228이라는 값을 return함

아래의 이미지로 위의 내용을 정리할 수 있음

closure의 장점

  • global 변수를 사용하지 않아도 됨 (nonlocal 변수를 사용하기 때문에)

    • 따라서 변수의 불필요한 충돌을 방지 할 수 있음
  • 데이터를 숨기기 용이함

    • closure에 속한 지역 변수는 바깥에서 직접 접근할 수 없음
    • 클로저는 외부 함수의 상태값을 참조, 함수가 메모리에서 사라져도 값이 유지됨 (아래의 예시 참조)

예시 )

def root_cal(number) :
    
    def nth_root(rt):
        return number**(1/rt)
    
    return nth_root

find_1000_nth_root=root_cal(1000)


del(root_cal)
# root_cal 함수를 삭제

print(find_1000_nth_root(4))
# 5.623413251903491

위와 같이 root_cal( ) 함수를 삭제하였음에도 해당 함수를 find_1000_nth_root 함수는 사용할 수 있음

  • decorator를 시행할 수 있음

Decorator

  • 다른 함수를 인자로 받아서 그 함수를 꾸며주는 함수
  • 외부함수가 내부함수를 return 함
  • 다른 함수를 decorator 함수의 인자로 전달

decorator의 구조


def decorator_func(other_func):
    def wrapper_func():
        other_func()   # 다른 함수를 인자로 받음
        print('그 외 other_func를 꾸며줄 내용')
      
    return wrapper_func   # 외부함수가 내부함수를 return

decorator의 이용_1

아래의 예시와 같이 decorator를 이용할 수 있음

예시)


def decorator_func(other_func):
    def wrapper_func():
        other_func()   # 다른 함수를 인자로 받음
        print('그 외 other_func를 꾸며줄 내용')
      
    return wrapper_func   # 외부함수가 내부함수를 return


def 적용할_함수() :
    print('이 함수를 인자로 받음')


imple=decorator_func(적용할_함수)

imple()
# 이 함수를 인자로 받음
# 그 외 other_func를 꾸며줄 내용

decorator의 이용_2

@를 이용하여 decorator를 간단하게 나타낼 수 있음

def decorator_func(other_func):
    def wrapper_func():
        other_func()
        print('그 외 other_func를 꾸며줄 내용')
      
    return wrapper_func
    
@ decorator_func
def 적용할_함수():
    print('이 함수를 인자로 받음')

# 위의 imple=decorator_func(적용할_함수)와 동일한 의미

적용할_함수()
# 이 함수를 인자로 받음
# 그 외 other_func를 꾸며줄 내용

하나의 함수에 여러개의 decorator를 적용할 수 있음
아래의 예시처럼 사용할 수 있음

예시)

def decorator1(func) :
    def wrapper1(*args) :
        print('Hello')
        func(*args)
    return wrapper1

def decorator2(func):
    def wrapper2(*args) :
        func(*args)
        print('Nice to meet you')
    return wrapper2    

@decorator1
@decorator2
def other_func(name):
	print(f'{name}')
    

other_func('Jina')

# Hello
# Jina
# Nice to meet you

만약에 name으로 여러개의 값을 받고 싶다면

def decorator1(func) :
    def wrapper1(*args) :
        print('Hello')
        func(*args)
    return wrapper1

def decorator2(func):
    def wrapper2(*args) :
        func(*args)
        print('Nice to meet you')
    return wrapper2    

@decorator1
@decorator2
def other_func(*name):
	print(f'{name}')
    

other_func('Jina','Naji')

#Hello
#('Jina', 'Naji')
#Nice to meet you

def other_func(*name) : 여기에도 args를 받는다는 표현을 해주어야함!

parameter를 가지는 decorator

decorator에 parameter를 전달하고 싶을 때는
function을 감싸는 decorator를 또 감싸주고 parameter를 전달해주면 됨

아래의 예시를 참고하기

def parameter_decorator(para):
    def function_decorator(func):
        def wrapper(*args):
            print(para)
            func(*args)
        return wrapper
    return function_decorator

@parameter_decorator("Parameter")
def func(args):
    print(f"function {args}")

func("decorator")

# Parameter
#function decorator

위의 사진을 보면 파란색으로 표시된 영역 A가 Decorator
이 데코레이터는 function을 조작하는 역할을 함

파란색 영역을 감싸고 있는 B는 A(decorator)를 중첩함수로 받아서 function에 parameter를 전달해줌


Ref

아래의 내용을 참고 및 정리

https://tech.ssut.me/python-functions-are-first-class/
https://shoark7.github.io/programming/python/closure-in-python
https://jiminsun.github.io/2018-05-07/python-closure/
https://data-flair.training/blogs/python-closure/
https://nachwon.github.io/decorator/
http://abh0518.net/tok/?p=604

0개의 댓글