web-dev-qa-db-ja.com

Pythonの関数チェーン

Codewars.comで、次のタスクに遭遇しました。

連続して呼び出されたときに数字を加算する関数addを作成します。 add(1)1を返す必要があり、add(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時間ウェブを検索しましたが、正しい方向へのリードを見つけることができませんでした。ただし、このプログラミングの概念がどのように呼ばれているのかわからないので、これはそれほど驚くことではないかもしれません。

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

83
Stefan Mesken

これがfunction連鎖callable連鎖であるかどうかはわかりませんが、関数arecallables害はありません。いずれにせよ、私はこれを行うことを考えることができる2つの方法があります:

intのサブクラス化と__call__の定義:

最初の方法は、 __call__ を定義するカスタムintサブクラスを使用して、更新された値で自身の新しいインスタンスを返すことです。

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サブクラスとして、戻り値はintsの__repr__および__str__動作を保持します。 ただし、より複雑な操作の場合は、他のダンダーを適切に定義する必要があります

@Caridorcがコメントで指摘したように、addは単に次のように書くこともできます。

add = CustomInt 

クラスの名前をaddではなくCustomIntに変更しても、同様に機能します。


クロージャーを定義し、値を生成するための追加の呼び出しが必要です。

私が考えることができる他の唯一の方法は、結果を返すために余分な空の引数呼び出しを必要とするネストされた関数を伴います。私はnonlocalを使用してnotであり、関数オブジェクトに属性を付加して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)を継続的に返し、valが指定されている場合はそれをインクリメントし(_inner_adder += val)、そうでない場合はそのままの値を返します。前述のように、インクリメントされた値を返すために、余分な()呼び出しが必要です。

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

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

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

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

  1. typeを使用して、新しい型を作成できます:type(name, bases, dict) -> a new typenameには、空の文字列を提供します。この場合、名前は実際には必要ありません。 bases(タプル)の場合、(int,)を提供します。これは、intの継承と同じです。 dictはクラス属性であり、__call__ラムダを付加します。
  2. self.__class__(self + v)return CustomInt(self + v)と同じです
  3. 新しい型が構築され、外側のラムダ内に返されます。
23
Jordan Jambazov

複数回呼び出される関数を定義する場合、最初に毎回呼び出し可能なオブジェクト(関数など)を返す必要があります。それ以外の場合は、__call__属性を定義して、それは呼び出し可能です。

次のポイントは、すべての引数を保存する必要があることです。この場合、 Coroutines または再帰関数を使用することを意味します。ただし、コルーチンは、特にそのようなタスクのために、再帰関数よりもはるかに最適化/柔軟です。

以下は、コルーチンを使用したサンプル関数で、それ自体の最新の状態を保存します。戻り値は呼び出し不可能な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
16
Kasrâmvd

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

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

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

5
nichochar

結果を取得するために追加の()を受け入れる場合は、 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をサブクラス化する方法があります。

1
a_guest

単に:

class add(int):
   def __call__(self, n):
      return add(self + n)
1
Nicolae