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時間ウェブを検索しましたが、正しい方向へのリードを見つけることができませんでした。ただし、このプログラミングの概念がどのように呼ばれているのかわからないので、これはそれほど驚くことではないかもしれません。
この概念をどのように呼んでいますか、そしてそれについてどこでもっと読むことができますか?
これが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
サブクラスとして、戻り値はint
sの__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行で行われます。
type
を使用して、新しい型を作成できます:type(name, bases, dict) -> a new type
。 name
には、空の文字列を提供します。この場合、名前は実際には必要ありません。 bases
(タプル)の場合、(int,)
を提供します。これは、int
の継承と同じです。 dict
はクラス属性であり、__call__
ラムダを付加します。self.__class__(self + v)
はreturn CustomInt(self + v)
と同じです複数回呼び出される関数を定義する場合、最初に毎回呼び出し可能なオブジェクト(関数など)を返す必要があります。それ以外の場合は、__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
これを行うPythonの方法は、動的引数を使用することです。
def add(*args):
return sum(args)
これはあなたが探している答えではなく、これを知っているかもしれませんが、誰かが好奇心からではなく仕事のためにこれをやろうと思っているのであれば、私はそれを与えると思いました。彼らはおそらく「正しいこと」の答えを持っているはずです。
結果を取得するために追加の()
を受け入れる場合は、 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
をサブクラス化する方法があります。
単に:
class add(int):
def __call__(self, n):
return add(self + n)