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>
square
와f
의 출력은 메모리 주소값에 저장된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에서 정의된 함수
outer_func
를 #2에서 호출 (outer_func
는 어떤 인수도 받지 않음) - outer_func가 실행된 후, 가장 먼저 하는 것은
messge
라는 변수에 ‘Hi’라는 문자열을 할당 (#3) - #4번에서
inner_func
를 정의하고 #5번에서inner_func를
호출하며 동시에 반환 - #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_function
인display
함수가 호출되어 문자열 출력
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_1
과display_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
출처
'Development > Python' 카테고리의 다른 글
아나콘다 가상환경 설치, 삭제, 복제, 조회 & Jupyter Notebook (0) | 2023.02.21 |
---|