파이썬에서의 함수

휴먼스케이프

안녕하세요. 휴먼스케이프에서 개발자로 일하고 있는 김병하입니다.

이 번 포스트에서는 파이썬의 함수에 대해서 이야기해보려고 합니다.

일급 함수

first class

파이썬에서의 함수는 일급 객체(first class object)입니다. 줄여서 ‘일급 함수'라고 말합니다. 일급 객체의 정의는 다음과 같습니다.

일급함수의 정의 - 런타임에 생성할 수 있다. - 데이터 구조체의 변수나 요소에 할당할 수 있다. - 함수 인수로 전달할 수 있다. - 함수 결과로 반환할 수 있다.

즉 자바스크립트의 함수처럼 파이썬의 함수도 객체로 취급됩니다.

고위 함수

또한 위에 서술한 것처럼 고위 함수(high-order function)도 가능합니다.

고위 함수는 함수를 인수로 받거나, 함수를 결과로 반환하는 함수를 말합니다.

익명 함수

또한 파이썬에서도 lambda 키워드를 이용해서 익명함수를 만들 수 있습니다. 가독성이 떨어진다는 이유로 자주 쓰이진 않습니다.

런드의 람다 리펙토링 비법 1. 람다가 하는 일이 무엇인지 설명하는 주석을 작성한다. 2. 잠시 주석을 주의 깊게 파악하고, 주석의 본질을 전달하는 이름을 생각해낸다. 3. 그 이름을 이용해서 람다를 def 문으로 변경한다. 4. 주석을 제거한다.

콜러블 객체

def 문을 이용해서 함수를 정의할 수 있지만, __call__() 던더 메소드를 이용해서 객체를 함수처럼 호출할 수 있도록 만들 수 있습니다. 이런 객체를 ‘호출할 수 있는 객체' 즉 콜러블 객체(Callable object)라고 합니다.

class MyObject:
    __init__(self):
        print('init MyObject')
    __call__():
        print('call MyObject')

위와 같이 클래스를 정의하고 던더 메소드 __call__()을 정의하면

my_obj = MyObject()
my_obj()

와 같이 클래스의 인스턴스를 함수처럼 호출할 수 있습니다.

일급 함수 디자인 패턴

파이썬의 함수는 일급 함수입니다. 덕분에 더욱 간단하게 구현할 수 있는 디자인 패턴들이 있습니다.

전략 패턴 ( Strategy Pattern )

디자인 패턴 중 하나인 ‘전략 패턴'은 일급 함수를 이용하면 더 간단하게 구현할 수 있습니다. 도서 [디자인 패턴]에서 설명하는 전략 패턴의 정의는 다음과 같습니다.

전략 패턴 일련의 알고리즘을 정의하고 각각을 하나의 클래스 안에 넣어서 교체하기 쉽게 만든다. 전략 패턴을 이용하면 사용하는 클라이언트에 따라 알고리즘을 독립적으로 변경할 수 있다.

예제를 통해 살펴보겠습니다. 아래 예제는 쇼핑몰에서 각 주문에 대해 조건별로 할인을 적용하는 과정을 ‘전략 패턴'을 이용해 구현합니다.

객체지향 관점에서 전략 패턴

객체지향 관점으로 클래스를 이용해서 구현하면 아래와 같은 UML이 나옵니다.

전략 디자인 패턴으로 구현한 주문 할인 처리에 대한 UML 클래스 다이어그램

구체적인 전략들 FidelityPromo, BulkItemPromo, LargeOrderPromo 클래스들이 인터페이스를 공유하고, discount() 메소드에 로직을 구현하면, 전략의 추가나 변동과 독립적으로 Promotion 클래스에서 할인 적용을 할 수 있게 됩니다. 구체적인 코드는 아래와 같습니다.

플러그형 할인 전략을 가진 Order 클래스 구현

함수지향 관점에서 전략 패턴

이미 효율적인 코드이지만 파이썬에서는 더 잘할 수 있습니다. 일급 함수가 가능하기 때문이죠. 인터페이스를 이용하여 클래스 안에 discount() 메소드를 정의하고 상위 클래스에서 호출하는 대신, 각 전략을 함수로 구현해서 파라미터로 전달합니다.

할인 전략을 함수로 구현한 Order 클래스

위처럼 일급 함수를 이용하면, 전략 패턴을 인터페이스를 공유하는 클래스로 구현하는 것보다 훨씬 간결한 코드가 가능해집니다. 위 예제와 달리 전략의 로직이 내부 상태가 필요하고, 복잡하다면 클래스로 구현하는 편이 좋을 수 있지만, 그렇지 않은 경우 함수로 구현하는 것이 더 가볍습니다.

함수도 ‘여러 컨텍스트에서 동시에 공유할 수 있는 공유 객체'임을 명심해주세요.

명령 패턴 (Command Pattern)

명령 패턴은 메서드 호출을 일급(first-class)로 만든다는 뜻이다. 객체지향 관점으로는 함수 호출을 객체로 감싼다는 것으로, 콜백을 객체지향적으로 표현한 것이다. 도서 [디자인 패턴]에서 설명하는 명령 패턴의 정의는 다음과 같습니다.

요청 자체를 캡슐화하는 것입니다. 이를 통해 서로 다른 사용자(client)를 매개변수로 만들고, 요청을 대기시키거나 로깅하며, 되돌릴 수 있는 연산을 지원합니다.

객체지향 관점에서 명령 패턴

객체지향 관점에서 텍스트 편집기를 명령 패턴으로 구현한다면 다음과 같은 UML을 그릴 수 있습니다.

명령 디자인 패턴으로 구현한 메뉴 방식 텍스트 편집기에 대한 UML 클래스 다이어그램. 각각의 명령은 서로 다른 수신자(행동을 구현하는 객체)를 가질 수 있다. PasteCommand의 경우 Document가 수신자이며, OpenCommand의 경우 Application이 수신자이다.

입력이 들어올 때, 하나의 메소드에서 입력에 연결된 메소드를 호출해서 처리하는 것이 아니라, 각 기능을 명령 클래스로 감싸서, 입력을 처리하는 로직과, 명령을 분배하는 로직, 실행하는 로직을 분리합니다.

각 실행 로직을 담당하는 클래스에 상태를 저장한다면, 실행취소(undo) 기능도 dependency 없이 구현할 수 있게 됩니다.

함수지향 관점에서 명령 패턴

일급 함수를 이용해서, 호출에서 명령 객체(Command class) 대신 간단히 함수를 바로 지정할 수 있습니다. 호출자는 단지 command()를 호출하면 됩니다.

실행 취소를 위해서 상태 저장이 필요하다면, 던더 메소드 __call__()을 이용해서 상태를 저장하거나, 클로저를 이용할 수 있습니다.

클로저

자바스크립트를 접한 분들이라면 익히 알고 계실 클로저도 파이썬에서 사용 가능합니다. 클로저는 함수 본체 외부에 정의된 비전역 변수를 의미합니다.

변수 범위 규칙

일단 클로저를 보기 전 파이썬에서의 변수 범위 규칙을 보겠습니다.

b = 6
def f1(a):
    print(a)  # 매개 변수 a 출력
    print(b)  # 전역 변수 b 출력
f1(3)

위 코드의 함수 f1은 전역변수 b를 출력하므로 b를 함수 외부에 정의해놓지 않으면 에러가 발생합니다.

b = 6
def f2(a):
    print(a)  # 매개 변수 a 출력
    print(b)  # 에러 발생
    b = 9  # 전역 변수 b 변경되지 않음
f2(3)

그런데 함수 f2의 경우, 변수가 할당되지 않았다는 에러가 발생합니다. 파이썬이 함수 본체를 컴파일할 때, b가 함수 안에서 할당되므로 지역 변수로 판단하여 발생하는 에러입니다. 이런 경우 global 키워드를 이용해서 전역 변수임을 명시해줘야 합니다.

b = 6
def f3(a):
    global b  # 전역 변수로 사용하겠다
    print(a)
    print(b)  # 에러 안남
    b = 9
f3(3)

클로저

파이썬에서도 함수 본체 외부에 정의된 비전역 변수, 즉 클로저가 가능합니다.

다음은 이동 평균을 계산하는 클래스를 클로저를 이용한 고위함수로 변경한 예시입니다.

클래스로 구현한 이동 평균과 클로저를 이용한 이동 평균

make_averager 안에 있는 series는 자유 변수(free variable)로 지역 범위에 바인딩되어 있지 않은 변수를 의미합니다. 클로저는 함수를 정의할 때 존재하던 자유 변수에 대한 바인딩을 유지하는 함수입니다.

여기서 유의해야 할 부분이 있습니다. 위에 변수 범위 규칙을 설명할 때, 함수 내부에서 변수 할당을 하면 파이썬은 컴파일 시점에 그 변수를 지역 변수로 판단하기 때문에 함수 내부에서 global 키워드를 이용해서 전역 변수임을 명시해줘야 합니다.

전역 변수 뿐만 아니라 자유 변수에도 동일한 규칙이 적용됩니다. 대신 이 때는 global 키워드가 아닌 nonlocal 키워드를 사용합니다.

전체 이력을 유지하지 않고 이동 평균 계산

데커레이터 (Decorator)

데커레이터는 다른 함수를 인수로 받는 콜러블입니다. 데커레이터는 데커레이트된 함수에 어떤 처리를 수행하고, 함수를 반환하거나 함수를 다른 함수나 콜러블 객체로 대체합니다.

사용법은 아래와 같습니다.

@decorate  # @을 이용해서 target()에 데커레이터 적용
def target():
    print('running target()')

위 코드는 아래 코드와 동일합니다.

def target():
    print('running target()')
target = decorate(target)

데커레이터는 파이썬이 모듈을 로딩하는 시점, 즉 임포트 타임(import time)에 실행됩니다. 때문에 함수를 정의하는 것과 같이 사용할 수 있습니다.

데커레이터로 개선한 전략 패턴(strategy pattern)

위에 일급 함수를 이용한 전략 패턴에서 여러 독립적인 할인 로직을 각각 함수로 구현했습니다. 만약 이 때, 가능한 할인 방식 중 가장 할인 폭이 큰 방법을 자동으로 적용해주는 best_promo 라는 함수를 만들어보겠습니다.

promos = [fidelity_promo, bulk_item_promo, large_order_promo]
def best_promo(order):
    '''Select best discount available
    '''
    return max(promo(order) for promo in promos)

할인 전략들을 promos 리스트에 다 넣고, best_promo() 안에서 그 중 할인 최대치를 반환하도록 했습니다. 이 방식도 좋지만, 단점은 할인 전략이 추가될 때마다 promos 리스트에 전략을 추가해야 한다는 점입니다. 데커레이터를 이용해서 이를 해결할 수 있습니다.

promotion 데커레이터로 채운 promos 리스트

promotion 데커레이터를 정의하고, 각 할인 전략들을 정의할 때, 손쉽고 명시적으로 promos 리스트에 추가할 수 있습니다.

데커레이터를 이용한 함수 오버로딩

파이썬은 함수 오버로딩을 지원하지 않습니다. 하지만 functools.singledispatch()를 이용해서 데커레이트하면 일련의 함수를 첫 번째 인수의 자료형에 따라 서로 다른 방식으로 동작하도록 할 수 있습니다.

여러 함수를 범용 함수로 묶는 커스텀 htmlize.register()를 생성하는 singledispatch

위 예시에서처럼 데커레이터를 겹쳐서 사용하면 차례로 적용됩니다.

@d1
@d2
def f():
    print('running f()')

위 코드는

f = d1(d2(f))

와 동일합니다.

파라미터를 받는 데커레이터

인수를 받을 수 있는 데커레이터도 만들 수 있습니다. 인수를 받아 데커레이터를 반환하는 데커레이터 팩토리를 만들면 됩니다.

다음은 그 예시입니다. set으로 선언된 registry에 함수를 추가하는 기능을 하는 코드입니다.

매개변수를 받기 위해 함수로 호출되어야 하는 register() 데커레이터

정리

비록 파이썬의 철학이 함수형 프로그래밍을 지원하는 방향은 아니지만, 파이썬의 함수는 일급 함수임을 잘 이용하면, 함수형 프로그래밍의 이점을 이용할 수 있습니다. 또 데커레이터를 잘 이용하면 더욱 읽기 좋고 간결한 코드를 작성할 수 있습니다.

감사합니다.

이 포스트는 루시아누 하말류의 책 [Fluent Python]을 참고해서 작성했습니다.

Get to know us better! Join our official channels below.

Telegram(EN) : t.me/Humanscape KakaoTalk(KR) : open.kakao.com/o/gqbUQEM Website : humanscape.io Medium : medium.com/humanscape-ico Facebook : www.facebook.com/humanscape Twitter : twitter.com/Humanscape_io Reddit : https://www.reddit.com/r/Humanscape_official Bitcointalk announcement : https://bit.ly/2rVsP4T Email : support@humanscape.io

기업문화 엿볼 때, 더팀스

로그인

/