소개
이 섹션에서는 함수를 사용하여 다른 함수를 생성하는 아이디어를 소개합니다.
소개
다음 함수를 고려해 봅시다.
def add(x, y):
def do_add():
print('Adding', x, y)
return x + y
return do_add
이것은 다른 함수를 반환하는 함수입니다.
>>> a = add(3,4)
>>> a
<function add.<locals>.do_add at 0x7f27d8a38790>
>>> a()
Adding 3 4
7
지역 변수 (Local Variables)
내부 함수가 외부 함수에 의해 정의된 변수를 어떻게 참조하는지 살펴보십시오.
def add(x, y):
def do_add():
## `x` 와 `y` 는 `add(x, y)` 위에 정의되어 있습니다.
print('Adding', x, y)
return x + y
return do_add
또한, add()가 종료된 후에도 해당 변수가 어떻게든 유지되는지 관찰하십시오.
>>> a = add(3,4)
>>> a
<function do_add at 0x6a670>
>>> a()
Adding 3 4 ## 이 값들은 어디에서 오는 걸까요?
7
클로저 (Closures)
내부 함수가 결과로 반환될 때, 해당 내부 함수를 클로저 (closure) 라고 합니다.
def add(x, y):
## `do_add` 는 클로저입니다.
def do_add():
print('Adding', x, y)
return x + y
return do_add
핵심 기능: 클로저는 나중에 함수가 제대로 실행되는 데 필요한 모든 변수의 값을 유지합니다. 클로저를 함수와, 함수가 의존하는 변수의 값을 담고 있는 추가적인 환경으로 생각할 수 있습니다.
클로저 사용하기 (Using Closures)
클로저는 Python 의 필수적인 기능입니다. 하지만, 그 사용법은 종종 미묘합니다. 일반적인 사용 사례는 다음과 같습니다:
- 콜백 함수 (callback functions) 에서 사용.
- 지연 평가 (delayed evaluation).
- 데코레이터 함수 (decorator functions) (나중에).
지연 평가 (Delayed Evaluation)
다음과 같은 함수를 생각해 봅시다:
def after(seconds, func):
import time
time.sleep(seconds)
func()
사용 예시:
def greeting():
print('Hello Guido')
after(30, greeting)
after 함수는 제공된 함수를 나중에 실행합니다.
클로저는 추가 정보를 함께 전달합니다.
def add(x, y):
def do_add():
print(f'Adding {x} + {y} -> {x+y}')
return do_add
def after(seconds, func):
import time
time.sleep(seconds)
func()
after(30, add(2, 3))
## `do_add` 는 x -> 2 및 y -> 3 의 참조를 가지고 있습니다.
코드 중복 (Code Repetition)
클로저는 과도한 코드 중복을 피하기 위한 기술로도 사용될 수 있습니다. 코드를 생성하는 함수를 작성할 수 있습니다.
연습 문제 7.7: 클로저를 사용하여 중복 방지하기
클로저의 가장 강력한 기능 중 하나는 반복적인 코드를 생성하는 데 사용되는 것입니다. 연습 문제 5.7 로 돌아가서, 타입 검사를 사용하여 속성을 정의하는 코드를 기억해 보세요.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
...
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected int')
self._shares = value
...
이 코드를 반복해서 입력하는 대신, 클로저를 사용하여 자동으로 생성할 수 있습니다.
typedproperty.py 파일을 만들고 다음 코드를 넣으세요:
## typedproperty.py
def typedproperty(name, expected_type):
private_name = '_' + name
@property
def prop(self):
return getattr(self, private_name)
@prop.setter
def prop(self, value):
if not isinstance(value, expected_type):
raise TypeError(f'Expected {expected_type}')
setattr(self, private_name, value)
return prop
이제 다음과 같이 클래스를 정의하여 사용해 보세요:
from typedproperty import typedproperty
class Stock:
name = typedproperty('name', str)
shares = typedproperty('shares', int)
price = typedproperty('price', float)
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
인스턴스를 생성하고 타입 검사가 작동하는지 확인해 보세요.
>>> s = Stock('IBM', 50, 91.1)
>>> s.name
'IBM'
>>> s.shares = '100'
... should get a TypeError ...
>>>
연습 문제 7.8: 함수 호출 간소화하기
위의 예에서, 사용자는 typedproperty('shares', int)와 같은 호출을 입력하는 것이 다소 장황하다고 느낄 수 있습니다. 특히 반복적으로 사용되는 경우 더욱 그렇습니다. typedproperty.py 파일에 다음 정의를 추가하세요:
String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)
이제, 이러한 함수를 대신 사용하여 Stock 클래스를 다시 작성하세요:
class Stock:
name = String('name')
shares = Integer('shares')
price = Float('price')
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
아, 훨씬 낫네요. 여기서 핵심은 클로저와 lambda를 사용하여 코드를 간소화하고 성가신 반복을 제거할 수 있다는 것입니다. 이는 종종 좋은 일입니다.
요약
축하합니다! 함수 반환 (Returning Functions) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.