파이썬 : functools.partial이 왜 필요한가요?


193

부분 적용이 멋지다. functools.partial람다를 통과 할 수없는 기능은 무엇입니까 ?

>>> 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

functools보다 효율적으로, 또는 읽을 어떻게 든?

답변:


266

functools.partial람다를 통과 할 수없는 기능은 무엇입니까 ?

추가 기능 측면에서는 그리 많지 않지만 (나중에 참조하십시오) – 가독성은 보는 사람의 눈에 있습니다.
함수형 프로그래밍 언어 (특히 리스프 / 계획 가족의 것)을 잘 알고있는 대부분의 사람들은 좋아 보인다 lambda잘 - 나는 "가장", 확실히 말을 하지 모두 귀도 내가 확실히 ( "익숙"그 사이에 있기 때문에 등 ) 아직 lambda파이썬에서 눈에 띄지 않는 이상으로 생각합니다 ...
그는 파이썬으로 그것을 받아 들인 것에 대해 회개했지만 "파이썬의 결함"중 하나로 파이썬 3에서 제거하려고 계획했습니다.
나는 그를 완전히지지했다. (I 사랑 lambda 제도에 ... 잠시 한계를 파이썬에서 , 그리고 이상한 방법은 그냥 아무튼 나머지 언어로 피부를 크롤링하십시오).

그리 그러나,의 무리에 대한 lambda애호가 - 귀도 역 추적하고 떠나기로 결심 할 때까지, 지금까지 파이썬의 역사에서 볼 반란에 가장 가까운 것 중 하나 무대 lambda.의
에 몇 가지 추가 functools(함수는 상수, 정체성을 반환하게하는이, 등) (이상 중복 명시 적으로 피하기 위해 일어나지 않았다 lambda'의 기능),하지만 partial물론 남아의 그것은 전혀 없습니다 (한을 중복 않으며)는 눈에 거슬리는입니다.

기억 lambda의 신체가로 제한된다 표현 은 한계를 가지고 있으므로. 예를 들어 ... :

>>> import functools
>>> f = functools.partial(int, base=2)
>>> f.args
()
>>> f.func
<type 'int'>
>>> f.keywords
{'base': 2}
>>> 

functools.partial의 반환 함수는 내부 검사에 유용한 속성, 즉 랩핑하는 함수, 그리고 어떤 위치 및 명명 된 인수로 수정되는지로 장식됩니다. 또한 명명 된 인수를 다시 무시할 수 있습니다 ( "고정"은 다소 기본 설정입니다).

>>> f('23', base=10)
23

보시다시피 , 그것은 간단 하지 않습니다 lambda s: int(s, base=2)!-)

예, 당신은 할 수 당신이 몇 가지 제공하기 위해 람다를 곡해 - 키워드 - 오버 라이딩을 위해, 예를

>>> f = lambda s, **k: int(s, **dict({'base': 2}, **k))

하지만 사랑스러운 희망 도 가장 열렬한 것을 lambda-lover은 고려하지 않습니다 댄 공포 더 읽기 partial전화 -!). "속성 설정"부분은 파이썬의 "본문은 단일 표현"제한으로 인해 더욱 어려워집니다 lambda(그리고 할당은 절대로 파이썬 표현식의 일부가 될 수 없다는 사실). 디자인 한계를 넘어서 목록 이해력을 확장함으로써 ... :

>>> f = [f for f in (lambda f: int(s, base=2),)
           if setattr(f, 'keywords', {'base': 2}) is None][0]

이제 하나의 표현으로 명명 된 인수 overridability 플러스 세 가지 속성의 설정을, 결합, 그리고 얼마나 읽을 말해 ... 될 것입니다!


2
네, functools.partial언급 한 기능이 람다보다 우수 하다고 말하고 싶습니다 . 아마도 이것은 다른 게시물의 주제 일지 모르지만, 디자인 수준에서 당신을 너무 귀찮게하는 것은 lambda무엇입니까?
Nick Heiner

11
@Rosarch, 내가 말했듯이 : 첫째, 제한 (Python은 표현과 문장을 크게 구별 합니다. 단일 표현 내에 할 수 없거나 현명하게 할 수없는 것이 많으며 람다의 몸 무엇입니까 ); 둘째, 그것의 절대적으로 이상한 구문 설탕. 내가 파이썬에서 시간을 변경 한 것은 다시 갈 수 있다면, 그것은 터무니없는, 의미, 눈에 거슬리는 것 def그리고 lambda그들 모두합니다 키워드 function(하나 개의 이름 선택 자바 스크립트가있어 정말 오른쪽), 내 반대의 적어도 1/3은 사라질 것이다 !-). 내가 말했듯이, 나는 Lisp 에서 람다 반대하지 않습니다 ...!-)
Alex Martelli

1
@Alex Martelli, Guido가 왜 람다에 대해 "body 's a single expression"이라는 제한을 설정 했습니까? C #의 람다 본문은 함수 본문에서 유효한 것이 될 수 있습니다. Guido가 파이썬 람다에 대한 제한을 제거하지 않는 이유는 무엇입니까?
Peter Long

3
@PeterLong 희망적으로 Guido 가 귀하의 질문에 대답 할 수 있기를 바랍니다 . 그것의 요지는 그것이 너무 복잡하고 def어쨌든 사용할 수 있다는 것 입니다. 우리의 자비로운 지도자가 말했습니다!
new123456

5
@AlexMartelli 보관 용은 귀도에 흥미로운 영향을 미쳤다 - twitter.com/gvanrossum/status/391769557758521345
데이비드

83

다음은 차이점을 보여주는 예입니다.

In [132]: sum = lambda x, y: x + y

In [133]: n = 5

In [134]: incr = lambda y: sum(n, y)

In [135]: incr2 = partial(sum, n)

In [136]: print incr(3), incr2(3)
8 8

In [137]: n = 9

In [138]: print incr(3), incr2(3)
12 8

Ivan Moore의 다음 게시물은 "람다의 한계"와 파이썬의 클로저를 확장합니다.


1
좋은 예입니다. 나에게 이것은 실제로 람다에 대한 "버그"처럼 보이지만 다른 사람들이 동의하지 않을 수도 있음을 이해합니다. (여러 프로그래밍 언어로 구현 된 것처럼 루프 내에 정의 된 클로저에서도 비슷한 일이 발생합니다.)
ShreevatsaR

28
이 "초기 대 후기 바인딩 딜레마"에 대한 수정은 원하는 경우 초기 바인딩을 명시 적으로 사용하는 것 lambda y, n=n: ...입니다. 늦은 (표시 이름의 결합 하지 그에서, 함수의 본문에 def상당 lambda) 아무것도는 하지만, 버그, 내가 과거에 긴 SO 응답의 길이에 표시 한대로 : 당신이 초기 바인딩 만약 당신이 원하는 명시 적으로 때, 때 늦은 바인딩 기본값을 사용 그게 당신이 원하는, 그리고 그건 정확히 파이썬의 디자인의 나머지의 맥락 주어진 권리 디자인 선택.
Alex Martelli 4

1
@Alex Martelli : 예, 죄송합니다. 나는 실제로 바인딩을 늦추는 데 익숙해지지 않을 것입니다. 실제로 뭔가 좋은 것을 정의하는 함수를 정의 할 때 생각하기 때문에 예상치 못한 놀라움으로 인해 두통이 발생할 수 있습니다. (하지만 파이썬보다 Javascript에서 기능적인 일을하려고 할 때 더 많은 일을합니다.) 많은 사람들 늦게 바인딩하는 것에 익숙하고 나머지 Python 디자인과 일치한다는 것을 알고 있습니다. 그래도 다른 긴 답변을 읽고 싶습니다. 링크? :-)
ShreevatsaR

3
알렉스가 맞아, 버그가 아니야 그러나 그것은 많은 람다 열광자를 포획하는 "gotcha"입니다. haskel / functional 유형의 인수에 대한 "버그"측면에 대해서는 Andrej Bauer의 게시물 : math.andrej.com/2009/04/09/pythons-lambda-is-broken
ars

@ars : 예, Andrej Bauer의 게시물에 대한 링크에 감사드립니다. 네, 늦은 바인딩의 효과는 확실히 우리가 수학 유형 (심지어 Haskell 배경으로)이 예상치 못한 충격과 충격을 계속 발견하는 것입니다. :-) 바우어 교수까지 가서 설계 오류라고 생각하지는 않지만, 인간 프로그래머가 한 가지 사고 방식과 다른 사고 방식을 완전히 전환 하는 것은 어렵습니다. (또는 아마도 이것은 부족한 파이썬 경험 일 것입니다.)
ShreevatsaR

26

최신 버전의 Python (> = 2.7)에서는 다음을 수행 할 수 있지만 다음을 수행 할 수 picklepartial없습니다 lambda.

>>> pickle.dumps(partial(int))
'cfunctools\npartial\np0\n(c__builtin__\nint\np1\ntp2\nRp3\n(g1\n(tNNtp4\nb.'
>>> pickle.dumps(lambda x: int(x))
Traceback (most recent call last):
  File "<ipython-input-11-e32d5a050739>", line 1, in <module>
    pickle.dumps(lambda x: int(x))
  File "/usr/lib/python2.7/pickle.py", line 1374, in dumps
    Pickler(file, protocol).dump(obj)
  File "/usr/lib/python2.7/pickle.py", line 224, in dump
    self.save(obj)
  File "/usr/lib/python2.7/pickle.py", line 286, in save
    f(self, obj) # Call unbound method with explicit self
  File "/usr/lib/python2.7/pickle.py", line 748, in save_global
    (obj, module, name))
PicklingError: Can't pickle <function <lambda> at 0x1729aa0>: it's not found as __main__.<lambda>

1
불행히도 부분 함수는 피클 링되지 않습니다 multiprocessing.Pool.map(). stackoverflow.com/a/3637905/195139
wting

3
@wting 그 게시물은 2010 년부터입니다. partialPython 2.7에서 피클 가능합니다.
Fred Foo

23

functools가 어떻게 더 효율적입니까?

이것에 대한 부분 답변으로 나는 성능을 테스트하기로 결정했습니다. 내 예는 다음과 같습니다.

from functools import partial
import time, math

def make_lambda():
    x = 1.3
    return lambda: math.sin(x)

def make_partial():
    x = 1.3
    return partial(math.sin, x)

Iter = 10**7

start = time.clock()
for i in range(0, Iter):
    l = make_lambda()
stop = time.clock()
print('lambda creation time {}'.format(stop - start))

start = time.clock()
for i in range(0, Iter):
    l()
stop = time.clock()
print('lambda execution time {}'.format(stop - start))

start = time.clock()
for i in range(0, Iter):
    p = make_partial()
stop = time.clock()
print('partial creation time {}'.format(stop - start))

start = time.clock()
for i in range(0, Iter):
    p()
stop = time.clock()
print('partial execution time {}'.format(stop - start))

파이썬 3.3에서는 다음을 제공합니다.

lambda creation time 3.1743163756961392
lambda execution time 3.040552701787919
partial creation time 3.514482823352731
partial execution time 1.7113973411608114

즉, partial은 작성 시간이 조금 더 걸리지 만 실행 시간은 상당히 줄어 듭니다. 이것은 ars 의 답변에서 논의 된 초기 및 후기 바인딩의 효과 일 수 있습니다 .


3
더 중요한 것은 partial순수한 파이썬이 아닌 C로 작성되었으므로 다른 함수를 호출하는 함수를 만드는 것보다 더 효율적으로 호출 할 수 있다는 의미입니다.
chepner 2016 년

12

Alex가 언급 한 추가 기능 외에도 functools.partial의 또 다른 장점은 속도입니다. 부분적으로 다른 스택 프레임을 구성 (및 파괴)하지 않아도됩니다.

부분 또는 람다에 의해 생성 된 함수는 기본적으로 docstring을 갖지 않습니다 (단, 객체를 통해 doc 문자열을 설정할 수는 있음 __doc__).

이 블로그에서 자세한 내용을 확인할 수 있습니다 : Python의 부분 함수 응용 프로그램


속도 이점을 테스트 한 경우 람다보다 부분 속도가 어느 정도 향상 될 것으로 예상됩니까?
Trilarion

1
docstring이 상속되었다고 말할 때 어떤 Python 버전을 참조합니까? Python 2.7.15 및 Python 3.7.2에서는 상속되지 않습니다. 원래 docstring이 부분적으로 적용된 인수가있는 함수에 반드시 맞지는 않기 때문에 좋은 것입니다.
jan

파이썬 2.7의 경우 ( docs.python.org/2/library/functools.html#partial-objects ) : " 이름문서 속성은 자동으로 생성되지 않습니다". 3. [5-7]와 동일합니다.
Yaroslav Nikitenko

링크에 실수가 있습니다 : log_info = partial (log_template, level = "info")-예제에서 level이 키워드 인수가 아니기 때문에 불가능합니다. 파이썬 2와 3은 "TypeError : log_template ()에 인수 'level'에 대해 여러 값이 있습니다"라고 말합니다.
Yaroslav Nikitenko

실제로, 나는 부분적으로 (f) 부분을 만들었고 doc 필드에 'partial (func, * args, ** keywords)-주어진 인수와 키워드의 부분적 적용 \ n을 가진 새로운 함수 \ n'(둘 다) 파이썬 2와 3).
야로슬라프 니키 첸코

1

세 번째 예에서 가장 빠른 의도를 이해합니다.

람다를 구문 분석 할 때 표준 라이브러리에서 직접 제공하는 것보다 더 많은 복잡성 / oddity가 예상됩니다.

또한 세 번째 예는 전체 서명에 의존하지 않는 유일한 예입니다. sum2 . 따라서 약간 느슨하게 결합됩니다.


1
흠, 나는 실제로 반대되는 설득력이 functools.partial있으며, 람다는 자명 한 반면, 전화 를 파싱하는 데 더 오래 걸렸습니다 .
David Z
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.