Pythonでの関数チェーン


90

Codewars.com私は、次のタスクを発生しました:

add連続して呼び出されたときに番号を加算する関数を作成します。だから、add(1)返す必要があり1add(1)(2)返す必要があり1+2、...

私はPythonの基本に精通していますが、このように連続して呼び出すf(x)ことができる関数、つまりとして呼び出すことができる関数に遭遇したことはありませんf(x)(y)(z)...。これまでのところ、この表記をどのように解釈するかさえわかりません。

数学者として、私はそれが疑われると思いf(x)(y)、すべてに割り当てる機能ですx機能g_{x}、その後、リターンg_{x}(y)と同様のためf(x)(y)(z)

この解釈が正しければ、Pythonを使用すると、非常に興味深い関数を動的に作成できます。過去1時間ウェブを検索しましたが、正しい方向へのリードを見つけることができませんでした。しかし、このプログラミングの概念がどのように呼ばれるかはわかりませんので、これはそれほど驚くことではないかもしれません。

この概念をどのように呼びますか、そしてどこでそれについてもっと読むことができますか?


7
カリー化機能を探しているようです
OneCricketeer 2016

2
ヒント:ネストされた関数は動的に作成され、その親関数のローカルにアクセスでき、(呼び出し可能な)オブジェクトとして返すことができます。
Jonathon Reinhart 2016

@JonathonReinhartそれが私が問題について考えていた方法です。しかし、私はそれを実装する方法を実際には見ていませんでした。
Stefan Mesken 2016

3
余談:Pythonはなります間違いなく、あなたが動的に関数を作成することができます。興味がある場合は、以下の関連する概念を読んでください。WP:ファーストクラス関数| Pythonで高階関数を作成するにはどうすればよいですか?| functools.partial()| WP:閉鎖
Lukas Graf

@LukasGraf見てみます。ありがとうございました!
Stefan Mesken 2016

回答:


100

これが呼び出し可能なチェーンと同じくらい関数チェーンであるかどうかはわかりませんが、関数呼び出し可能であるため、害はないと思います。いずれにせよ、私がこれを行うことを考えることができる2つの方法があります:

サブクラス化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、Python間で移植できるようにするために、関数オブジェクトに属性をアタッチすることを選択しています。

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)を返し、avalが指定されている場合はインクリメント(_inner_adder += val)し、指定されていない場合は値をそのまま返します。私が述べたように()、インクリメントされた値を返すために追加の呼び出しが必要です。

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

6
インタラクティブコードadd = CostumIntでも機能し、よりシンプルになるはずです。
Caridorc 2016

4
ビルトインのサブクラス化の問題は、呼び出し可能ではないために(2*add(1)(2))(3)失敗するTypeErrorことintです。基本的に、CustomIntは、呼び出し時を除いてint任意のコンテキストで使用されるとプレーンに変換されます。より堅牢なソリューションを得るには、基本的に__*____r*__バージョンを含むすべてのメソッドを再実装する必要があります...
Bakuriu 2016

@Caridorcまたは、定義するときCustomInt以外はまったく呼び出さないaddでください。
minipif 2016

27

あなたは私を憎むことができますが、ここにワンライナーがあります:)

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

編集:わかりました、これはどのように機能しますか?コードは@Jimの回答と同じですが、すべてが1行で行われます。

  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)
バクリウ2016

3
クラス内のコードは通常のコードとまったく同じように実行されるため、割り当てによって特別なメソッドを定義できます。唯一の違いは、クラススコープが少し...独特であるということです。
バクリウ2016

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

これを行うためのPythonの方法は、動的引数を使用することです。

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

これはあなたが探している答えではなく、あなたはこれを知っているかもしれませんが、誰かが好奇心からではなく仕事のためにこれを行うことについて疑問に思っているので、とにかくそれを与えると思いました。彼らはおそらく「正しいこと」の答えを持っているはずです。


1
' PS 'ノート、nichocharを削除しました。Pythonがいかにエレガントであるかは誰もが知っています:-)答えの本文に含まれているとは思いません。
Dimitris Fasarakis Hilliard 2016

6
add = sumもしそのルートに行くつもりなら、あなたはちょうどできたはずだと思います
codykochmann 2017年


3

()結果を取得するために追加を受け入れる場合は、次を使用できます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.