본문 바로가기
Study/Python

python functools.partial 사용

by 개발새-발 2021. 7. 22.
반응형

partial을 사용하여 인자가 여러 개인 함수에 몇 개의 인자가 이미 설정되어있는 새로운 함수를 생성할 수 있다.

 

그런데 언제 필요할까? multiprocessing.Pool의 map()은 함수를 인자로 받는다. 그 함수는 인자가 하나여야 한다. 하필 내가 사용해야 하는 함수는 인자를 여러 개를 받는데, map()에서 실행하는 경우 인자 중 하나를 제외하고 계속 동일한 값이 들어가는 경우라면? partial을 이용하면 그 하나의 인자를 제외한 나머지 인자들을 고정한 함수를 만들 수 있다. 이 방법으로 새로운 함수는 인자를 하나만 받게 하여 map()에서 사용할 수 있도록 한다.

partial의 간단한 사용법

사용법이 매우 간단하다.

from functools import partial

아래 두 수를 곱하는 함수 mul이 있다. 이 mul을 partial을 이용하여 새로운 함수를 만들어보겠다.

def mul(a,b):
    return a*b

partial을 이용하여 mul의 인자 b가 5로 설정된 함수 mul5를 만들어 보자. mul5는 5를 곱해 반환하는 함수이다.

mul5 = partial(mul,b=5)

이렇게 만들어진 mul5는 평소 쓰던 함수처럼 사용하면 된다.

print(mul5(5), mul5(7))
25 35

이번엔 b가 3으로 설정된 함수 mul3를 만들어 보자. mul3은 3을 곱해 반환하는 함수이다.

mul3 = partial(mul,b=3)
print(mul3(3), mul3(4))
9 12

partial가 작동하는 방법

우리가 partial(~)로 코드를 작성하면 partial 객체(object)가 만들어진다. 위 코드에서는 mul5와 mul3가 partial 객체이다. 우리는 이 객체들을 만들 때 함수와 설정해줄 인자들을 넘겨주었다. partial 객체들은 넘겨받은 함수와 인자들을 기억하고 있다. 우리가 이 객체를 함수처럼 사용하면 __call__이 호출이 된다. 이 __call__에서 새로운 인자들과 기억하고 있던 인자를 합쳐서 함수에 넘겨진 결과물을 반환하는 것이다. 간단히 말하자면 아래와 같다.

  • partial로 생성 시에 본래의 함수와 인자들을 기억한다.
  • partial로 만들었던 것을 사용할 때 본래의 함수에 기억하고 있던 인자와 새로 입력된 인자를 전달한다.
  • 함수의 결과물을 반환한다.

위 내용을 코드로 작성하면 아래와 같다. 실제 functools.partial의 코드는 이보다 더 길고 복잡하다.

class MyPartial:

    def __init__(self,func,/,*args,**keywords):
        self.func = func
        self.args = args
        self.keywords = keywords

    def __call__(self,/,*args,**keywords):
        keywords = {**self.keywords, **keywords}
        return self.func(*args,*self.args,**keywords)
mymul3 = MyPartial(mul,b=3)
print(mymul3(3))
9

사용 시 주의해야 할 점

1. "~~ got multiple values for argument '???'" 과같은 오류가 발생하는 경우

partial객체는 본래의 함수에 *args와 **keywords를 넘겨준다. 이때 동일한 인자가 중복되어 전달되지 않도록 주의하여야 한다. **keywords에는 객체 생성 시에 넘겨주었던 인자와 이후 사용 시에 넘겨주었던 인자가 포함되어 있다. 그래서 아래 코드와 같은 경우 동일한 인자가 여러 개 전해졌다고 오류가 발생한다.

mul2 = partial(mul,a=2)
print(mul2(7))
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-8-e8c93694db5d> in <module>
      1 mul2 = partial(mul,a=2)
----> 2 print(mul2(7))


TypeError: mul() got multiple values for argument 'a'

위 코드에서 객체 생성 시에 a가 2라는 것을 기억한다. 2는 **keyword의 형태로 mul에 전달된다. 그런데 mul2를 사용할 때 7은 본래 함수인 mul에 *args의 형태로 넘겨질 때 가장 처음의 위치인 a의 위치로 전달된다. *args로 한번, **keywords로 한번 전달되어 a가 두 개 전달된 것이다.

 

2. 의도하지 않은 방향으로 작동하는 경우

partial객체에서 본래 함수에 전달하는 **keywords는 객체 생성 시에 전달한 인자에 사용 시 새로 전달받은 인자를 붙여서 전달하게 된다. 이때, 동일한 인자가 존재하는 경우 새로 전달받은 값을 사용하게 된다. 그래서 실수로 처음 전달한 인자를 덮어쓰게 되면, 객체 사용 시 원하는 방향으로 동작하지 않을 수 있다. 아래 코드는 기존 전달하였던 인자 b를 덮어쓰게 되어 5를 곱하여 반환해야 하는 mul5가 그렇게 동작하지 않는 모습을 보여준다.

mul5 = partial(mul,b=5)
print(mul5(4,b=1))
4

이러한 실수는 아래처럼 dictionary 형태로 값을 전달할 때 발생할 가능성이 높다.

mul5 = partial(mul,b=5)

params = {
    'a':4,
    'b':1
}

print(mul5(**params))
4
반응형

댓글