def print_all_elements(list_of_things):
## 중첩함수 선언
def print_each_element(things):
for thing in things:
print(thing)
if len(list_of_things) > 0:
print_each_element(list_of_things)
else:
print("There is nothing!")
closure 란 사전적 의미는 폐쇄이다. 뭔가를 닫아서 가둔다는 의미가 있는 단어이다.
파이썬에서 사용하는 closure는 어떤 것을 외부로부터 격리해서 사용한다는 느낌이 더 강하다.
바로 중첩함수가 부모함수의 변수나 정보를 가두어 사용하는 것을 closure 라고 한다. 부모함수는 중첩함수를 리턴해주는데, 그러면 부모함수의 변수를 외부로부터 직접적인 접근은 격리하면서 중첩함수를 통해 격리된 부모함수의 변수를 사용한 연산은 가능하게 해준다.
그렇다면 closure는 언제 사용되는가?
예를 들어보면 만일 주어진 어떠한 숫자의 수를 구하는 함수는 다음과 같을 것이다.
def calculate_power_of_two(power):
return 2 ** power
calculate_power_of_two(7)
> 128
calculate_power_of_two(10)
> 1024
def generate_power(base_number):
def nth_power(power):
return base_number ** power
return nth_power
calculate_power_of_two = generate_power(2)
calculate_power_of_two(7)
> 128
calculate_power_of_two(10)
> 1024
calculate_power_of_seven = generate_power(7)
calculate_power_of_seven(3)
> 343
calculate_power_of_seven(5)
> 16907
Decorator를 구현해보세요.
Decorator는 앞서 배운 closure를 한단계 더 나아가 사용하는 고급 기능입니다.
Decorator는 closure처럼 중첩함수를 리턴하는 함수 이며 다른 함수에 적용해서, 적용된 함수가 실행되기 전에 무조건 실행됩니다. 즉 특정 함수를 실행하기 전에 강제적으로 다른 함수가 먼저 실행된후 실행되도록 하는 강제성을 제공하는 기능입니다.
더 자세한 내용은 아래 링크의 스택오버플로우 질문을 참조하세요:
decorator란 장식 호는 장식하는 사람이라는 뜻이 있다. 즉, 장식에 관련되어 있다는 의미이다.
먼저 대박 주식 정보를 리턴하는 함수가 있다고 가정하자.
def jackpot_stock_information():
return "계시가 내려왔는데, 삼성전자를 사세요."
그러나 이 대박 주식 정보를 유료 회원만 받을 수 있다. 그러므로 이 함수가 호출되기 전에 해당 유저가 유료회원인지를 확인해야한다.
해당 회원이 유료회원인지 확인하는 함수는 다음가 같다. 일단 모든 유저가 유료 회원이라고 가정하겠다.
def is_paid_user():
return True
그럼 jackpot_stock_information 함수를 호출하기 전에 항상 is_paid_user 함수가 먼저 호출되어야 한다. 다음과 같이 말이다.
if is_paid_user():
jackpot_stock_information()
지금까지는 문제없어 보이지만, 문제는 jackpot_stock_information 함수가 여러 곳에서 자주 사용될 때이다.
jackpot_stock_information 함수가 호출될 때, is_paid_user 가 무조건 먼저 호출되야 하는데, 이 연결고리를 잊어먹을 확률이 있다.
즉, jackpot_stock_information 가 is_paid_user 함수 호출 없이 호출될 수 있다는 것이다. 개발자들도 사람이니 실수를 할 수 있는 부분이다.
그럼 jackpot_stock_information 함수가 호출될 때, 자동으로 is_paid_user 함수가 강제적으로 먼저 호출되게 할 수 있는 방법은 무엇일까?
여기서 decorator가 사용된다. decorator를 적용하면 다음과 같이 된다.
@is_paid_user
def jackpot_stock_information():
return "계시가 내려왔는데, 삼성전자를 사세요."
jackpot_stock_information 함수 정의 위에 is_paid_user 함수가 골뱅이 마크(@)와 함께 달려있은것을 볼 수 있다. 그래서 장식, decorator 라고 하는 것이다.
함수위에 다른 함수를 골뱅이 마크를 사용해서 장식처럼 달아놓는 것이다.
저렇게 decorator로 달아놓으면 해당 함수가 호출되기전에 장식으로 달린 함수가 먼저 호출되고 난 후 본 함수가 호출된다.
그럼 아무나 decorator로 장식할 수 있을까?
아니다. decorator로 장식할 수 있는 함수는 중첩함수를 리턴하는 함수만 decorator 함수로 사용될 수 있다.
이유는 decorator의 기능을 다르게 설명하면 chain of functions, 즉 여러개의 함수가 연속적으로 호출이 자동으로 되게 해주는 것이다.
![](https://velog.velcdn.com/images%2Fmagnoliarfsit%2Fpost%2Fbc40e269-ab6d-4a67-b110-481333686eb5%2Fdecorator.png)
그럴려면 마지막 함수를 제외한 함수들은 그 다음 함수를 리턴해주어야 파이썬이 함수들을 차례대로 호출할 수 있다. 만약 다음 함수를 리턴하지 않고 다른 함수를 리턴해버리면, 그 다음 함수로 넘어가지 못하고 그냥 함수 실행이 종료되기 때문이다. 그렇기 때문에 decorator 함수는 그 다음 함수를 리턴해주어야 한다.
그럼 is_paid_user 함수를 decorator로 만들어 보자.
def is_paid_user(func):
user_paid = True # 간단화 하기 위해 무조건 True
def wrapper(*args, **kwargs):
if user_paid:
func()
else:
return
return wrapper
이제 is_paid_user를 decorator로 장식하면 된다.
@is_paid_user
def jackpot_stock_information():
return "계시가 내려졌는데, 삼성전자를 사세요."
![](https://velog.velcdn.com/images%2Fmagnoliarfsit%2Fpost%2Faa23feb1-a372-411b-96fe-675eb943fbaa%2Fdecorator_chain.png)
왼쪽 상단의 greeting 함수에 적용될 decorator 함수를 구현하여 greeting 함수에 적용해주세요.
Greeting 함수가 호출 되었을때 decorator 함수에 parametor 값이 greeting 함수 리턴값의 다음에 붙혀져서 리턴되어야 합니다. Decorator 함수의 이름은 name_decorator 여야 합니다.
예를 들어, 다음 처럼 정의 하여 name_decorator decorator에 "정우성"을 parameter로 적용하여 greeting을 호출하면:
@name_decorator("정우성")
def greeting():
return "Hello, "
greeting()
결과값은 다음과 같아야 합니다.
"Hello, 정우성"
# My Solution
def name_decorator(x):
def deco(func):
def wrapper():
result = func() + x
return result
return wrapper
return deco
@name_decorator("정우성")
def greeting():
return "Hello, "
# 코딩도장 - 매개변수가 있는 데코레이터를 참조하면서 만들었다.
# 조금더 공부해보자.
def trace(func):
# 호출할 함수를 배개변수로 받는다.
def wrapper():
def trace(func): # 호출할 함수를 매개변수로 받음
def wrapper(): # 호출할 함수를 감싸는 함수
print(func.__name__, '함수 시작') # __name__으로 함수 이름 출력
func() # 매개변수로 받은 함수를 호출
print(func.__name__, '함수 끝')
return wrapper # wrapper 함수 반환
def trace(func): # 호출할 함수를 매개변수로 받음
def wrapper(a, b): # 호출할 함수 add(a, b)의 매개변수와 똑같이 지정
r = func(a, b) # func에 매개변수 a, b를 넣어서 호출하고 반환값을 변수에 저장
print('{0}(a={1}, b={2}) -> {3}'.format(func.__name__, a, b, r)) # 매개변수와 반환값 출력
return r # func의 반환값을 반환
return wrapper # wrapper 함수 반환
@trace # @데코레이터
def add(a, b): # 매개변수는 두 개
return a + b # 매개변수 두 개를 더해서 반환
print(add(10, 20))
#실행 결과
add(a=10, b=20) -> 30
30
def is_multiple(x): # 데코레이터가 사용할 매개변수를 지정
def real_decorator(func): # 호출할 함수를 매개변수로 받음
def wrapper(a, b): # 호출할 함수의 매개변수와 똑같이 지정
r = func(a, b) # func를 호출하고 반환값을 변수에 저장
if r % x == 0: # func의 반환값이 x의 배수인지 확인
print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, x))
else:
print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, x))
return r # func의 반환값을 반환
return wrapper # wrapper 함수 반환
return real_decorator # real_decorator 함수 반환
@is_multiple(3) # @데코레이터(인수)
def add(a, b):
return a + b
print(add(10, 20))
print(add(2, 5))
#실험 결과
add의 반환값은 3의 배수입니다.
30
add의 반환값은 3의 배수가 아닙니다.
7
Global scope은 함수 안에서 선언된것이 아닌 함수 밖에서 선언된 변수나 함수를 이야기 합니다. 앞부분에서 설명했듯이, 변수나 함수는 선언된 지점과 동일한 level의 지역, 그리고 더 안쪽의 지역들까지 범위가 유효합니다.
그리고 global scope을 가지고 있는 변수와 함수들은 선언된 지점이 해당 파일에서 가장 바깥쪽에서 선언되므로 해당 파일에서 선언된 지점 아래로는 다 유효한 범위를 가지고 있습니다. 그래서 "Global" scope 이라고 하는 것입니다.
Local => Enclosing => Global => Built-in
왼쪽 상단에 코드를 수정하셔서 scope_test 함수에 parameter 값에 상관 없이 무조건 63이 리턴되도록 수정해주세요.
예를 들어 다음과 같이 되어야 합니다.
scope_test(10) == 63
scope_test(20) == 63
scope_test(333) == 63
class ClassName:
...class 내용 코드들
class Car:
pass
hyundai = Car()
bmw = Car()
class Car:
def __init__(self, maker, model, horse_power):
self.maker = maker
self.model = model
self.horse_power = horse_power
class Car:
def __init__(self, maker, model, horse_power):
self.maker = maker
self.model = model
self.horse_power = horse_power