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
'Study > Python' 카테고리의 다른 글
컴퓨터와 가위바위보 하기 1 (0) | 2021.08.12 |
---|---|
python argparse를 사용하여 커맨드 라인 인자 처리하기 (0) | 2021.08.04 |
Matplotlib로 subplot을 생성하고, 겹치지 않게 하기 (0) | 2021.07.17 |
공공 데이터 포털에서 "공휴일" 정보 받기 (3) | 2021.07.15 |
Python opencv로 좌표 형태의 도형을 mask 이미지 배열로 변환, 또 역으로 (0) | 2021.07.13 |
댓글