functools가 부분적으로 어떤 역할을합니까?


181

functools에서 부분이 어떻게 작동하는지 머리를 댈 수 없습니다. 여기에 다음 코드가 있습니다 .

>>> sum = lambda x, y : x + y
>>> sum(1, 2)
3
>>> incr = lambda y : sum(1, y)
>>> incr(2)
3
>>> def sum2(x, y):
    return x + y

>>> incr2 = functools.partial(sum2, 1)
>>> incr2(4)
5

이제 라인에

incr = lambda y : sum(1, y)

나는에 전달할 그 어떤 인수 얻을 incr로 전달됩니다 그것 y으로 lambda반환하는 sum(1, y)1 + y.

나는 이해. 그러나 나는 이것을 이해하지 못했다 incr2(4).

부분 함수에서 4와 같이 Get은 어떻게 전달 x됩니까? 나 4에게을 교체해야합니다 sum2. x과 의 관계는 무엇입니까 4?

답변:


218

대략 partial다음과 같이하십시오 (키워드 인수 지원 등 제외).

def partial(func, *part_args):
    def wrapper(*extra_args):
        args = list(part_args)
        args.extend(extra_args)
        return func(*args)

    return wrapper

따라서을 호출 partial(sum2, 4)하면과 같이 동작 sum2하지만 위치 인수가 적은 새로운 함수 (정확하게 호출 가능)를 만듭니다 . 누락 된 인수는 항상으로 대체 4되므로partial(sum2, 4)(2) == sum2(4, 2)

왜 필요한지에 대해서는 다양한 경우가 있습니다. 하나의 경우, 두 개의 인수가 예상되는 곳에 함수를 전달해야한다고 가정하십시오.

class EventNotifier(object):
    def __init__(self):
        self._listeners = []

    def add_listener(self, callback):
        ''' callback should accept two positional arguments, event and params '''
        self._listeners.append(callback)
        # ...

    def notify(self, event, *params):
        for f in self._listeners:
            f(event, params)

그러나 이미 기능 context을 수행 하려면 일부 세 번째 객체에 액세스해야합니다 .

def log_event(context, event, params):
    context.log_event("Something happened %s, %s", event, params)

따라서 몇 가지 솔루션이 있습니다.

커스텀 객체 :

class Listener(object):
   def __init__(self, context):
       self._context = context

   def __call__(self, event, params):
       self._context.log_event("Something happened %s, %s", event, params)


 notifier.add_listener(Listener(context))

람다 :

log_listener = lambda event, params: log_event(context, event, params)
notifier.add_listener(log_listener)

부분적으로 :

context = get_context()  # whatever
notifier.add_listener(partial(log_event, context))

이 세 가지 중에서 partial가장 짧고 가장 빠릅니다. (더 복잡한 경우에는 커스텀 객체를 원할 수도 있습니다).


1
어디에서 extra_args변수를
얻었습니까

2
extra_args부분 호출자가 전달한 것으로, 예제 p = partial(func, 1); f(2, 3, 4)에서는입니다 (2, 3, 4).
bereal

1
그러나 왜 우리가 그렇게 할 것인가, 무언가 만 부분적으로 만 수행하고 다른 것으로는 할 수없는 특별한 사용 사례
user1865341

@ user1865341 답변에 예를 추가했습니다.
bereal

귀하의 예제와 함께, 사이의 관계 무엇 callbackmy_callback
user1865341

92

부분 은 매우 유용합니다.

예를 들어, '파이프 라이닝 된'함수 호출 순서 (한 함수에서 반환 된 값이 다음 함수로 전달되는 인수)

때때로 이러한 파이프 라인의 함수에는 단일 인수 가 필요 하지만이 함수에서 즉시 업스트림 함수는 두 개의 값을 반환 합니다 .

이 시나리오에서는이 functools.partial기능 파이프 라인을 그대로 유지할 수 있습니다.

다음은 구체적이고 격리 된 예입니다. 일부 대상에서 각 데이터 포인트의 거리를 기준으로 일부 데이터를 정렬한다고 가정합니다.

# create some data
import random as RND
fnx = lambda: RND.randint(0, 10)
data = [ (fnx(), fnx()) for c in range(10) ]
target = (2, 4)

import math
def euclid_dist(v1, v2):
    x1, y1 = v1
    x2, y2 = v2
    return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)

이 데이터를 대상과의 거리를 기준으로 정렬하려면 다음과 같이하십시오.

data.sort(key=euclid_dist)

그러나 정렬 방법의 주요 매개 변수는 단일 인수 를 취하는 함수 만 허용합니다 .

따라서 단일 매개 변수 euclid_dist를 사용하는 함수로 다시 작성하십시오 .

from functools import partial

p_euclid_dist = partial(euclid_dist, target)

p_euclid_dist 이제 단일 인수를 허용합니다.

>>> p_euclid_dist((3, 3))
  1.4142135623730951

이제 sort 메소드의 주요 인수에 대한 부분 함수를 전달하여 데이터를 정렬 할 수 있습니다.

data.sort(key=p_euclid_dist)

# verify that it works:
for p in data:
    print(round(p_euclid_dist(p), 3))

    1.0
    2.236
    2.236
    3.606
    4.243
    5.0
    5.831
    6.325
    7.071
    8.602

또는 예를 들어 함수의 인수 중 하나가 외부 루프에서 변경되지만 내부 루프에서 반복하는 동안 고정됩니다. 부분 (partial)을 사용하면 수정 된 (부분) 함수가 필요하지 않기 때문에 내부 루프를 반복하는 동안 추가 매개 변수를 전달할 필요가 없습니다.

>>> from functools import partial

>>> def fnx(a, b, c):
      return a + b + c

>>> fnx(3, 4, 5)
      12

키워드 arg를 사용하여 부분 함수 만들기

>>> pfnx = partial(fnx, a=12)

>>> pfnx(b=4, c=5)
     21

위치 인수를 사용하여 부분 함수를 만들 수도 있습니다

>>> pfnx = partial(fnx, 12)

>>> pfnx(4, 5)
      21

그러나 이것은 던질 것입니다 (예 : 키워드 인수로 부분을 만든 다음 위치 인수를 사용하여 호출)

>>> pfnx = partial(fnx, a=12)

>>> pfnx(4, 5)
      Traceback (most recent call last):
      File "<pyshell#80>", line 1, in <module>
      pfnx(4, 5)
      TypeError: fnx() got multiple values for keyword argument 'a'

또 다른 사용 사례 : 파이썬 multiprocessing라이브러리를 사용하여 분산 코드 작성 . 프로세스 풀은 풀 메소드를 사용하여 작성됩니다.

>>> import multiprocessing as MP

>>> # create a process pool:
>>> ppool = MP.Pool()

Pool map 메소드가 있지만 단일 iterable 만 필요하므로 더 긴 매개 변수 목록이있는 함수를 전달 해야하는 경우 함수를 부분적으로 재정 의하여 하나를 제외한 모든 것을 수정하십시오.

>>> ppool.map(pfnx, [4, 6, 7, 8])

1
이 함수가 실제로 사용
되었습니까?

3
@ user1865341 님이 내 답변에 두 가지 모범 사례를 추가했습니다
Doug

IMHO, 이것은 객체와 클래스와 같은 관련이없는 개념을 유지하고 이것이 모든 기능에 초점을 맞추기 때문에 더 나은 대답입니다.
akhan

35

짧은 대답, partial그렇지 않으면 기본값이없는 함수의 매개 변수에 기본값을 제공합니다.

from functools import partial

def foo(a,b):
    return a+b

bar = partial(foo, a=1) # equivalent to: foo(a=1, b)
bar(b=10)
#11 = 1+10
bar(a=101, b=10)
#111=101+10

5
우리는 기본값을 무시할 수 있기 때문에 반은 사실이며, 그 이후의 방법으로도 무시 된 매개 변수를 무시할 수도 있습니다 partial.
Azat Ibrakov

33

일부는 입력 매개 변수가 미리 지정된 새로운 파생 함수를 만드는 데 사용할 수 있습니다

일부 실제 사용 부분을 보려면 다음 블로그 게시물을 참조하십시오.
http://chriskiehl.com/article/Cleaner-coding-through-partially-applied-functions/

간단하지만 블로그에서 깔끔한 초보자의 예는, 커버 하나는 사용하는 방법 partial에 대한 re.search코드를보다 읽기 쉽게 만들 수 있습니다. re.search메소드의 서명은 다음과 같습니다.

search(pattern, string, flags=0) 

적용 partial하면 search요구 사항에 맞게 여러 버전의 정규식 을 만들 수 있습니다 . 예를 들면 다음과 같습니다.

is_spaced_apart = partial(re.search, '[a-zA-Z]\s\=')
is_grouped_together = partial(re.search, '[a-zA-Z]\=')

지금 is_spaced_apartis_grouped_together유래의 두 가지 새로운 함수이다 re.search즉있다 pattern(이후 인수인가 pattern필드에 첫 번째 인자 인 re.search방법의 서명).

이 두 가지 새로운 기능 (호출 가능)의 서명은 다음과 같습니다.

is_spaced_apart(string, flags=0)     # pattern '[a-zA-Z]\s\=' applied
is_grouped_together(string, flags=0) # pattern '[a-zA-Z]\=' applied

다음은 일부 텍스트에서 이러한 부분 함수를 사용하는 방법입니다.

for text in lines:
    if is_grouped_together(text):
        some_action(text)
    elif is_spaced_apart(text):
        some_other_action(text)
    else:
        some_default_action()

위 의 링크 를 참조하면 이 특정 예와 그 이상의 내용을 다루므로 주제에 대해 더 깊이 이해할 수 있습니다.


1
이것과 같지 is_spaced_apart = re.compile('[a-zA-Z]\s\=').search않습니까? 그렇다면 partial관용구가 더 빠른 재사용을 위해 정규 표현식을 컴파일 한다는 보장이 있습니까?
Aristide

10

제 생각 에는 파이썬에서 카레 를 구현하는 방법 입니다.

from functools import partial
def add(a,b):
    return a + b

def add2number(x,y,z):
    return x + y + z

if __name__ == "__main__":
    add2 = partial(add,2)
    print("result of add2 ",add2(1))
    add3 = partial(partial(add2number,1),2)
    print("result of add3",add3(1))

결과는 3과 4입니다.


1

또한 부분 함수가 일부 매개 변수를 "하드 코딩"하려는 다른 함수를 전달할 때 가장 중요한 매개 변수 여야합니다.

def func(a,b):
    return a*b
prt = partial(func, b=7)
    print(prt(4))
#return 28

그러나 우리가 똑같이하지만 대신 매개 변수를 변경하면

def func(a,b):
    return a*b
 prt = partial(func, a=7)
    print(prt(4))

"TypeError : func ()에 인수 'a'에 대한 여러 값이 있습니다."라는 오류가 발생합니다.


응? 다음과 같이 가장 왼쪽에있는 매개 변수를 수행하십시오.prt=partial(func, 7)
DylanYoung

0

이 답변은 예제 코드에 가깝습니다. 위의 모든 답변은 왜 부분적으로 사용해야하는지에 대한 좋은 설명을 제공합니다. 부분에 대한 관찰과 사용 사례를 제공 할 것입니다.

from functools import partial
 def adder(a,b,c):
    print('a:{},b:{},c:{}'.format(a,b,c))
    ans = a+b+c
    print(ans)
partial_adder = partial(adder,1,2)
partial_adder(3)  ## now partial_adder is a callable that can take only one argument

위 코드의 출력은 다음과 같아야합니다.

a:1,b:2,c:3
6

위의 예에서 매개 변수 (c)를 인수로 취하는 새로운 호출 가능 항목이 리턴되었습니다. 함수의 마지막 인수이기도합니다.

args = [1,2]
partial_adder = partial(adder,*args)
partial_adder(3)

위 코드의 출력도 다음과 같습니다.

a:1,b:2,c:3
6

키워드가 아닌 인수의 압축을 푸는 데 *가 사용되었으며, 인수가 취할 수있는 방식으로 반환 된 호출 가능 항목은 위와 같습니다.

또 다른 관찰은 다음 과 같습니다. 아래 예제는 선언되지 않은 매개 변수 (a)를 인수로 취하는 호출 가능 부분이 부분적으로 반환됨을 보여줍니다.

def adder(a,b=1,c=2,d=3,e=4):
    print('a:{},b:{},c:{},d:{},e:{}'.format(a,b,c,d,e))
    ans = a+b+c+d+e
    print(ans)
partial_adder = partial(adder,b=10,c=2)
partial_adder(20)

위 코드의 출력은 다음과 같아야합니다.

a:20,b:10,c:2,d:3,e:4
39

비슷하게,

kwargs = {'b':10,'c':2}
partial_adder = partial(adder,**kwargs)
partial_adder(20)

위의 코드 인쇄

a:20,b:10,c:2,d:3,e:4
39

모듈의 Pool.map_async메소드 를 사용할 때 사용해야했습니다 multiprocessing. 하나의 인수 만 작업자 함수에 전달할 수 있으므로 partial하나의 입력 인수만으로 작업자 함수를 호출 가능하게 만드는 데 사용해야 하지만 실제로는 작업자 함수에 여러 개의 입력 인수가 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.