Python의 함수 체인


90

Codewars.com 나는 다음과 같은 작업을 발생 :

add연속적으로 호출 될 때 숫자를 더하는 함수 를 만듭니다 . 그래서 add(1)return 1, add(1)(2)return 1+2, ...

나는 파이썬의 기초에 익숙하지만, 연속적 f(x)으로 호출 될 수 있는 함수, 즉 f(x)(y)(z).... 지금까지이 표기법을 어떻게 해석해야할지 모르겠습니다.

수학자로서, 그 의심 것 f(x)(y)그 모든에 할당하는 기능입니다 x함수 g_{x}다음 반환 g_{x}(y)마찬가지로위한 f(x)(y)(z).

이 해석이 옳다면 파이썬은 나에게 매우 흥미로워 보이는 함수를 동적으로 만들 수있게 해줄 것입니다. 지난 한 시간 동안 웹을 검색했지만 올바른 방향으로 리드를 찾을 수 없었습니다. 그러나이 프로그래밍 개념이 어떻게 호출되는지 모르기 때문에 이것은 그리 놀라운 일이 아닙니다.

이 개념을 어떻게 부르고 그것에 대한 자세한 내용을 어디에서 읽을 수 있습니까?


7
당신 같은 외모는 태닝 기능을 찾고 있습니다
OneCricketeer

2
힌트 : 중첩 된 함수는 동적으로 생성되고 부모 함수의 로컬에 액세스 할 수 있으며 (호출 가능한) 객체로 반환 될 수 있습니다.
Jonathon Reinhart

@JonathonReinhart 그것이 내가 문제에 대해 생각했던 방식입니다. 그러나 나는 그것을 구현하는 방법을 실제로 보지 못했습니다.
Stefan Mesken

3
곁에 : 파이썬은 확실히 당신이 동적으로 함수를 생성 할 수 있도록합니다. 관심이 있으시다면 몇 가지 관련 개념을 읽어보십시오. WP : 일류 기능 | 파이썬에서 고차 함수를 어떻게 만드나요? | functools.partial()| WP : 폐쇄
Lukas Graf

@LukasGraf 나는 그것을 볼 것이다. 감사합니다!
Stefan Mesken

답변:


100

이것이 콜 러블 체이닝 만큼 함수 체이닝 인지는 모르겠지만, 함수 콜 러블 이기 때문에 해를 끼치 지 않는 것 같습니다. 어느 쪽이든, 이렇게 생각할 수있는 두 가지 방법이 있습니다.

하위 분류 int및 정의 __call__:

첫 번째 방법은 업데이트 된 값으로 자신의 새 인스턴스를 반환하는 int것을 정의 하는 사용자 정의 하위 클래스를 사용하는 것입니다 __call__.

class CustomInt(int):
    def __call__(self, v):
        return CustomInt(self + v)

add이제 CustomInt자신의 업데이트 된 값을 반환하는 콜 러블로서 연속적으로 호출 할 수 있는 인스턴스 를 반환하도록 함수 를 정의 할 수 있습니다.

>>> def add(v):
...    return CustomInt(v)
>>> add(1)
1
>>> add(1)(2)
3
>>> add(1)(2)(3)(44)  # and so on..
50

또한 int하위 클래스 로서 반환 된 값 은 s 의 __repr____str__동작을 유지합니다 int. 하지만 더 복잡한 작업의 경우 다른 던더를 적절하게 정의해야합니다 .

@Caridorc가 주석에서 언급했듯이 add간단히 다음과 같이 작성할 수도 있습니다.

add = CustomInt 

add대신 클래스 이름을 바꾸는 CustomInt것도 비슷하게 작동합니다.


클로저를 정의하고 값을 산출하려면 추가 호출이 필요합니다.

내가 생각할 수있는 유일한 다른 방법은 결과를 반환하기 위해 추가 빈 인수 호출이 필요한 중첩 함수를 포함합니다. 나는 파이썬 사이에서 이식 가능하도록 함수 객체에 속성을 첨부하는 것을 사용 하지 않고nonlocal 선택합니다.

def add(v):
    def _inner_adder(val=None):  
        """ 
        if val is None we return _inner_adder.v 
        else we increment and return ourselves
        """
        if val is None:    
            return _inner_adder.v
        _inner_adder.v += val
        return _inner_adder
    _inner_adder.v = v  # save value
    return _inner_adder 

이것은 계속해서 자체 ( _inner_adder)를 반환 하며, a val가 제공되면 증분 ( _inner_adder += val)하고 그렇지 않으면 값을있는 그대로 반환합니다. 앞서 언급했듯이 ()증가 된 값을 반환하려면 추가 호출 이 필요 합니다.

>>> add(1)(2)()
3
>>> add(1)(2)(3)()  # and so on..
6

6
대화 형 코드 add = CostumInt에서도 작동하고 더 간단해야합니다.
Caridorc

4
내장 서브 클래 싱의 문제점 은 호출 (2*add(1)(2))(3)할 수 TypeError없기 때문에 실패 한다는 것 int입니다. 기본적으로는 호출 할 때를 제외하고 모든 컨텍스트 에서 사용될 때 CustomInt일반 으로 변환됩니다 . 보다 강력한 솔루션을 위해서는 기본적으로 버전을 포함한 모든 방법 을 다시 구현해야 합니다 ...int__*____r*__
Bakuriu

@Caridorc 또는 그것을 호출하지 않는 CustomInt전혀하지만 add그것을 정의 할 때.
minipif

27

당신은 나를 미워할 수 있지만 여기에 한 줄짜리가 있습니다 :)

add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)

편집 : 좋아, 어떻게 작동합니까? 코드는 @Jim의 답변과 동일하지만 모든 것이 한 줄에서 발생합니다.

  1. type새로운 유형을 생성하는 데 사용할 수 있습니다 : type(name, bases, dict) -> a new type. 를 들어 name이름이 정말이 경우에 필요하지 않는 한 우리는 빈 문자열을 제공합니다. 내용 bases(튜플) 우리가 제공 (int,)상속 동일하다를 int. 람다를 dict연결하는 클래스 속성 __call__입니다.
  2. self.__class__(self + v) ~와 동일하다 return CustomInt(self + v)
  3. 새 형식이 생성되고 외부 람다 내에서 반환됩니다.

17
또는 더 짧게 :class add(int):__call__ = lambda self, v: add(self+v)
Bakuriu

3
클래스 내의 코드는 일반 코드와 똑같이 실행되므로 할당으로 특수 메서드를 정의 할 수 있습니다. 유일한 차이점은 클래스 범위가 약간 특이하다는 것입니다.
Bakuriu

16

여러 번 호출 할 함수를 정의하려면 먼저 매번 호출 가능한 개체 (예 : 함수)를 반환해야합니다. 그렇지 않으면 다음을 정의하여 고유 한 개체를 만들어야합니다. __call__ 속성 호출 가능합니다.

다음 요점은 모든 인수를 보존해야한다는 것입니다.이 경우 코 루틴 또는 재귀 함수 를 사용하고 싶을 수 있습니다 . 그러나 코 루틴은 재귀 함수보다 훨씬 더 최적화되고 유연합니다. 특히 그러한 작업에 대해 하십시오.

다음은 최신 상태를 유지하는 코 루틴을 사용하는 샘플 함수입니다. 반환 값은 호출 할 수 없기 때문에 여러 번 integer호출 할 수 없지만 예상되는 객체로 바꾸는 것을 생각할 수 있습니다 ;-).

def add():
    current = yield
    while True:
        value = yield current
        current = value + current


it = add()
next(it)
print(it.send(10))
print(it.send(2))
print(it.send(4))

10
12
16

6

이를 수행하는 비단뱀적인 방법은 동적 인수를 사용하는 것입니다.

def add(*args):
    return sum(args)

이것은 당신이 찾고있는 대답이 아니고 당신도 이것을 알고 있을지도 모르지만, 누군가가 호기심 때문이 아니라 일을 위해 이것을하는 것에 대해 궁금해한다면 나는 그것을 줄 것이라고 생각했습니다. 그들은 아마도 "올바른 일"에 대한 답을 가지고있을 것입니다.


1
나는 당신의 ' PS '메모, nichochar를 제거 했습니다. 우리는 모두 파이썬이 얼마나 우아한 지 알고 있습니다. :-) 저는 그것이 답의 본문에 속한다고 생각하지 않습니다.
Dimitris Fasarakis Hilliard

6
난 그냥 할 수 있었 생각 add = sum그 길 갈 거라면
codykochmann

4

()결과를 검색하기 위해 추가를 수락하려면 다음을 사용할 수 있습니다 functools.partial.

from functools import partial

def add(*args, result=0):
    return partial(add, result=sum(args)+result) if args else result

예를 들면 :

>>> add(1)
functools.partial(<function add at 0x7ffbcf3ff430>, result=1)
>>> add(1)(2)
functools.partial(<function add at 0x7ffbcf3ff430>, result=3)
>>> add(1)(2)()
3

이를 통해 한 번에 여러 숫자를 지정할 수도 있습니다.

>>> add(1, 2, 3)(4, 5)(6)()
21

단일 번호로 제한하려면 다음을 수행 할 수 있습니다.

def add(x=None, *, result=0):
    return partial(add, result=x+result) if x is not None else result

당신이 원하는 경우 add(x)(y)(z)쉽게 결과를 반환 하고 있을 더 호출 한 후 하위 클래스라는이 int길을 가야하는 것입니다.


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