티스토리 뷰

First Class Function

함수를 다른 함수의 인자로 넣기

  • 프로그래밍 언어가 함수를 first-class citizen으로 취급
  • 함수 자체를 argument로써 다른 함수에 전달하거나 다른 함수의 결과값으로 반환할 수도 있고, 함수를 변수에 할당하거나 데이터 구조 안에 저장할 수 있는 함수
def square(x):
return x * x

print(square(5))

f = square

print(square)
print(f)
(base) jieunpark@MacBook-Air 230220_decorator % python first_class_function.py
25
<function square at 0x7f8b080e80d0>
<function square at 0x7f8b080e80d0>
  • squaref의 출력은 메모리 주소값에 저장된 square 함수 오브젝트가 할당되어 있음
def square(x):
return x * x

print(square(5))

f = square

print(f(5))
(base) jieunpark@MacBook-Air 230220_decorator % python first_class_function.py
25
25
  • f(5)square 함수 호출
  • 프로그래밍 언어가 first class function을 지원하면, 변수에 함수를 할당할 수 있을 뿐만 아니라, 인자로써 다른 함수에 전달하거나, 함수의 반환값으로도 사용할 수 있음
def square(x):
return x * x

def my_map(func, arg_list):
result = []
for i in arg_list:
result.append(func(i)) # func == square
return result

num_list = [1, 2, 3, 4, 5]

squares = my_map(square, num_list)

print(squares)
(base) jieunpark@MacBook-Air 230220_decorator % python first_class_function.py
[1, 4, 9, 16, 25]
  • my_map 함수에 square 함수를 인자로 전달한 후 for문 안에서 square 함수 호출

함수의 결과값으로 또 다른 함수 반환하기

def logger(msg):
def log_message():
print('Log: ', msg)

return log_message

log_hi = logger('Hi')
print(log_hi) # log_message 출력

log_hi() # 'Log: Hi' 출력
(base) jieunpark@MacBook-Air 230220_decorator % python first_class_function.py
<function logger.<locals>.log_message at 0x7fcf08088280>
Log: Hi
  • log_message라는 함수를 logger 함수의 반환값으로 반환하여 log_hi라는 변수에 할당한 후 호출
  • msg와 같은 함수의 지역변수값은 함수가 호출된 이후 메모리상에서 사라지므로 다시 참조할 수가 없는데, msg 변수에 할당되었던 ‘Hi’ 값이 logger 함수가 종료된 이후에도 참조됨
  • 이러한 log_message와 같은 함수를 closure라고 부르며, closure는 다른 함수의 지역변수를 그 함수가 종료된 이후에도 기억할 수 있음
# 단순한 일반 함수
def simple_html_tag(tag, msg):
print('<{0}>{1}<{0}>'.format(tag, msg))

simple_html_tag('h1', 'simple heading title')

print('-' * 30)

# 함수를 반환하는 함수
def html_tag(tag):
def wrap_text(msg):
print('<{0}>{1}<{0}>'.format(tag, msg))

return wrap_text

print_h1 = html_tag('h1')
print(print_h1)
print_h1('first heading title')
print_h1('second heading title')

print_p = html_tag('p')
print_p('this is a paragraph')
(base) jieunpark@MacBook-Air 230220_decorator % python first_class_function.py
<h1>simple heading title<h1>
------------------------------
<function html_tag.<locals>.wrap_text at 0x7fc5e0248700>
<h1>first heading title<h1>
<h1>second heading title<h1>
<p>this is a paragraph<p>

파이썬 – 퍼스트클래스 함수 (First Class Function) - schoolofweb.net

Closure

  • 어떤 함수를 자신이 가지고 있는 환경과 함께 저장한 레코드
  • 함수가 가진 free variable을 closure가 만들어지는 당시의 값과 reference에 mapping하는 역할
  • 일반 함수와 다르게, 자신의 영역 밖에서 호출된 함수의 변수값과 reference를 복사하고 저장한 뒤, 이 값들에 접근하도록 도와줌
  • free variable: 코드블럭 안에서 사용은 되었지만, 해당 코드블럭 안에서 정의되지 않은 변수
def outer_func(): # 1
message = 'Hi' # 3

def inner_func(): # 4
print(message) # 6

return inner_func() # 5

outer_func() # 2
(base) jieunpark@MacBook-Air 230220_decorator % python closure.py
Hi
  • ‘Hi’가 출력되기까지의 프로세스
  1. #1에서 정의된 함수 outer_func를 #2에서 호출 (outer_func는 어떤 인수도 받지 않음)
  2. outer_func가 실행된 후, 가장 먼저 하는 것은 messge 라는 변수에 ‘Hi’라는 문자열을 할당 (#3)
  3. #4번에서 inner_func를 정의하고 #5번에서 inner_func를 호출하며 동시에 반환
  4. #6에서 message 변수를 참조하여 출력
  • 여기서 message는 inner_func 안에서 정의되지는 않았지만, inner_func 안에서 사용되기 때문에 free variable이라고 부름
def outer_func(): # 1
message = 'Hi' # 3

def inner_func(): # 4
print(message) # 6

return inner_func # 5

my_func = outer_func() # 2

my_func() # 7
(base) jieunpark@MacBook-Air 230220_decorator % python closure.py
Hi
  • outer_func는 #2에서 호출된 후 종료되었는데, #7에서 호출된 my_func 함수가 outer_func의 local variable인 message를 참조함

⇒ closure를 통해 함수의 free variable 값을 어딘가에 저장함

def outer_func():
message = 'Hi'

def inner_func():
print(message)

return inner_func

my_func = outer_func()

print(my_func) # 1
print()
print(dir(my_func)) # 2
print()
print(type(my_func.__closure__)) # 3
print()
print(my_func.__closure__) # 4
print()
print(my_func.__closure__[0]) # 5
print()
print(dir(my_func.__closure__[0])) # 6
print()
print(my_func.__closure__[0].cell_contents) # 7
(base) jieunpark@MacBook-Air 230220_decorator % python closure.py
<function outer_func.<locals>.inner_func at 0x7f8f10248280>

['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

<class 'tuple'>

(<cell at 0x7f8f10249d90: str object at 0x7f8f10037fb0>,)

<cell at 0x7f8f10249d90: str object at 0x7f8f10037fb0>

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']

Hi
  • #1: my_func 변수에 inner_func 함수 오브젝트가 할당되어 있음
  • #2: my_func 네임스페이스 확인 결과, __closure__라는 속성이 있음
  • #3: __closure__는 tuple 형식
  • #4: tuple 안에 아이템이 하나 들어있음
  • #5: tuple 안에 첫 번째 아이템은 cell이라는 문자열 오브젝트임
  • #6: cell 오브젝트 안에 있는 속성으로 cell_contents가 있음
  • #7: cell_contents 안에 ‘Hi’가 들어있음
def outer_func(tag):
text = 'Some text'
tag = tag

def inner_func():
print('<{0}>{1}<{0}>'.format(tag, text))

return inner_func

h1_func = outer_func('h1')
p_func = outer_func('p')

h1_func()
p_func()
(base) jieunpark@MacBook-Air 230220_decorator % python closure.py
<h1>Some text<h1>
<p>Some text<p>
def outer_func(tag):
text = 'Some text'
tag = tag

def inner_func(txt):
text = txt
print('<{0}>{1}<{0}>'.format(tag, text))

return inner_func

h1_func = outer_func('h1')
p_func = outer_func('p')

h1_func('h1 태그')
p_func('p 태그')
(base) jieunpark@MacBook-Air 230220_decorator % python closure.py
<h1>h1 태그<h1>
<p>p 태그<p>
  • 이렇게 하나의 함수로 여러가지의 함수를 간단히 만들거나, 기존에 만들어진 함수나 모듈을 수정하지 않고도 wrapper 함수를 이용하여 커스터마이징 가능하게 함

파이썬 – 클로저 (Closure) - schoolofweb.net

Decorator

  • 기존 코드에 여러가지 기능을 추가하는 파이썬 구문
  • 함수를 다른 함수의 인자로 전달
def decorator_function(original_function): # 1
def wrapper_function(): # 5
return original_function() # 7
return wrapper_function # 6

def display(): # 2
print('display 함수가 실행되었습니다.') # 8

decorated_display = decorator_function(display) # 3
decorated_display() # 4
(pytorch_3) jieunpark@MacBook-Air 230220_decorator % python decorator.py
display 함수가 실행되었습니다.
  • 데코레이터 함수인 decorator_function과 일반 함수인 display를 #1과 #2에서 정의
  • #3의 decorated_display라는 변수에 display 함수를 인자로 갖는 decorator_function을 실행한 반환값을 할당
  • wrapper_function
  • 아직 실행되지는 않음, decorated_display 변수 안에서 호출되기를 기다림
  • #4의 decorated_display()를 통해 wrapper_function을 호출하면, #5에서 정의된 wrapper_function이 호출됨
  • #7에서 original_functiondisplay 함수가 호출되어 문자열 출력

Decorator를 사용하는 이유

  • 이미 만들어져 있는 기존의 코드를 수정하지 않고도, wrapper 함수를 이용하여 여러가지 기능을 추가할 수 있음
def decorator_function(original_function):
def wrapper_function():
print('{} 함수가 호출되기 전입니다.'.format(original_function.__name__))
return original_function()
return wrapper_function

def display_1():
print('display_1 함수가 실행되었습니다.')

def display_2():
print('display_2 함수가 실행되었습니다.')

display_1 = decorator_function(display_1)
display_2 = decorator_function(display_2)

display_1()
print()
display_2()
(pytorch_3) jieunpark@MacBook-Air 230220_decorator % python decorator.py
display_1 함수가 호출되기 전입니다.
display_1 함수가 실행되었습니다.

display_2 함수가 호출되기 전입니다.
display_2 함수가 실행되었습니다.
  • 하나의 데코레이터 함수를 만들어 display_1display_2 2개의 함수에 기능을 추가할 수 있음
  • 일반적으로 @ + 데코레이터 함수 이름의 간단한 구문 사용
def decorator_function(original_function):
def wrapper_function():
print('{} 함수가 호출되기 전입니다.'.format(original_function.__name__))
return original_function()
return wrapper_function

@decorator_function
def display_1():
print('display_1 함수가 실행되었습니다.')

@decorator_function
def display_2():
print('display_2 함수가 실행되었습니다.')

# display_1 = decorator_function(display_1)
# display_2 = decorator_function(display_2)

display_1()
print()
display_2()
(pytorch_3) jieunpark@MacBook-Air 230220_decorator % python decorator.py
display_1 함수가 호출되기 전입니다.
display_1 함수가 실행되었습니다.

display_2 함수가 호출되기 전입니다.
display_2 함수가 실행되었습니다.
  • 인수를 가진 함수 데코레이팅
def decorator_function(original_function):
def wrapper_function():
print('{} 함수가 호출되기 전입니다.'.format(original_function.__name__))
return original_function()
return wrapper_function

@decorator_function
def display():
print('display_1 함수가 실행되었습니다.')

@decorator_function
def display_info(name, age):
print('display_2 함수가 실행되었습니다.')

display()
print()
display_info('John', 25)
(pytorch_3) jieunpark@MacBook-Air 230220_decorator % python decorator.py
display 함수가 호출되기 전입니다.
display_1 함수가 실행되었습니다.

Traceback (most recent call last):
File "decorator.py", line 17, in <module>
display_info('John', 25)
TypeError: wrapper_function() takes 0 positional arguments but 2 were given
  • 인자 개수 관련 에러 발생
def decorator_function(original_function):
def wrapper_function(*args, **kwargs):
print('{} 함수가 호출되기 전입니다.'.format(original_function.__name__))
return original_function(*args, **kwargs)
return wrapper_function

@decorator_function
def display():
print('display_1 함수가 실행되었습니다.')

@decorator_function
def display_info(name, age):
print('display_2 함수가 실행되었습니다.')

display()
print()
display_info('John', 25)
(pytorch_3) jieunpark@MacBook-Air 230220_decorator % python decorator.py
display 함수가 호출되기 전입니다.
display_1 함수가 실행되었습니다.

display_info 함수가 호출되기 전입니다.
display_2 함수가 실행되었습니다.

*args

  • 가변 인자를 위한 변수
  • tuple 형태로 저장
  • 인자의 개수가 변할 수 있음
  • 만약 add(1, 2, 3)이라는 함수를 호출하면, add라는 함수 안에 args라는 tuple이 생김
  • args[0]으로 첫 번째 인자에 접근 가능
def add(*args):
result = 0
for i in args:
result += i
print(result)

add(1, 10, 1302)

출력 결과:
1313

**kwargs

  • keyword arguments의 약자
  • dictionary 형태로 저장
  • parameter명을 같이 저장할 수 있음
def name_and_age(**kwargs):
print(kwargs)

name_and_age(name="홍길동", age="50")

출력 결과:
{'age': '50', 'name': '홍길동'}

*args, **kwargs

  • 함수에 key-value 형태의 인자를 주면 자동으로 kwargs에 저장
  • *args를 선언해놓고 사용하지 않을 수도 있음
  • 이런 경우에는 args에는 아무 값도 저장되지 않음
def number_and_name(*args, **kwargs):
print(args, kwargs)

number_and_name(1, 2, 3, name="홍길동")

출력 결과:
(1, 2, 3) {'name': 'GilDong Hong'}

Class 활용

class DecoratorClass:
def __init__(self, original_function):
self.original_function = original_function

def __call__(self, *args, **kwargs):
print('{} 함수가 호출되기 전입니다.'.format(self.original_function.__name__))
return self.original_function(*args, **kwargs)

@DecoratorClass
def display():
print('display 함수가 실행되었습니다.')

@DecoratorClass
def display_info(name, age):
print('display_info({}, {}) 함수가 실행되었습니다.'.format(name, age))

display()
print()
display_info('John', 25)
(pytorch_3) jieunpark@MacBook-Air 230220_decorator % python decorator.py
display 함수가 호출되기 전입니다.
display 함수가 실행되었습니다.

display_info 함수가 호출되기 전입니다.
display_info(John, 25) 함수가 실행되었습니다.

실제 활용 사례

  • 로그를 남기거나 유저의 로그인 상태 등을 확인하여 로그인 상태가 아니면 로그인 페이지로 redirect하기 위하여 많이 사용
  • 프로그램의 성능 테스트
  • 리눅스 및 유닉스 서버 관리자는 스크립트가 실행되는 시간을 측정하기 위하여 date와 time 명령어 많이 사용

wraps decorator

  • 복수의 decorator를 스택하여 사용하면 아래쪽 데코레이터부터 실행됨
  • 이러한 현상을 방지하기 위하여 functools 모듈의 wraps 데코레이터 사용
from functools import wraps
import datetime
import time

def my_logger(original_function):
import logging
filename = '{}.log'.format(original_function.__name__)
logging.basicConfig(handlers=[logging.FileHandler(filename, 'a', 'utf-8')],
level=logging.INFO)

@wraps(original_function) # 1
def wrapper(*args, **kwargs):
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
logging.info(
'[{}] 실행결과 args - {}, kwargs - {}'.format(timestamp, args, kwargs))
return original_function(*args, **kwargs)

return wrapper

def my_timer(original_function):
import time

@wraps(original_function) # 2
def wrapper(*args, **kwargs):
t1 = time.time()
result = original_function(*args, **kwargs)
t2 = time.time() - t1
print('{} 함수가 실행된 총 시간: {} 초'.format(original_function.__name__, t2))
return result

return wrapper

@my_timer
@my_logger
def display_info(name, age):
time.sleep(1)
print('display_info({}, {}) 함수가 실행됐습니다.'.format(name, age))

display_info('Jimmy', 30) # 3

 

출처

- https://schoolofweb.net/blog/posts/%ed%8c%8c%ec%9d%b4%ec%8d%ac-%eb%8d%b0%ec%bd%94%eb%a0%88%ec%9d%b4%ed%84%b0-decorator/

- https://legitcode267.tistory.com/13

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함