함수형 함수 만들기

Beginner

This tutorial is from open-source community. Access the source code

소개

이 섹션에서는 함수를 사용하여 다른 함수를 생성하는 아이디어를 소개합니다.

소개

다음 함수를 고려해 봅시다.

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 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.