web-dev-qa-db-ja.com

Pythonで関数をアンカリーするにはどうすればよいですか?

最近、標準を使って「プログラミング言語」を勉強しました [〜#〜] ml [〜#〜] 、そしてカリー化の方法(または何か)を学んだので、Pythonでそれを適用しました。以下は簡単な機能とカリー化です。

def range_new(x, y):
    return [i for i in range(x, y+1)]

def curry_2(f):
    return lambda x: lambda y: f(x, y)

def uncurry_2(f):
    pass # I don't know it...

print(range_new(1, 10))
curried_range = curry_2(range_new)
countup = curried_range(1)
print(countup(10))
print(curried_range(1)(10))

結果は以下のとおりです。そしてそれはうまく機能します。 curry_2新しい関数を作成できます(countup)。でも、カレーなしの機能を作りたいです。しかし、どうすれば作れるのかわかりません。どうすればいいですか?

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
10
frhyme

最も簡単な解決策は、カリー化された関数を、カリー化されていないコードで再度ラップすることです。

def uncurry_2(f):
    return lambda x, y: f(x)(y)

uncurried_range = uncurry_2(curried_range)
print(uncurried_range(1, 10))
15
Florian Brucker

これは必ずしも良いスタイルではありませんが、返されたlambdaの(おそらくCPythonのみ) __closure__ 属性を使用してクロージャー内の変数にアクセスできます。

>>> countup.__closure__[0].cell_contents
<function __main__.range_new>

これにより、関数curry_2の最も内側のクロージャー(最も内側のlambdaで使用される変数)のコンテンツにアクセスし、そこで使用した関数を返します。

ただし、本番コードではそれを使用しないでください。カリー化されていない関数(lambdaが提供しないもの)へのアクセスをサポートするカリー化用のクラス(または関数)を作成することをお勧めします。ただし、Pythonの一部のfunctoolsは、「装飾された」関数へのアクセスをサポートしています。たとえば、partial

>>> from functools import partial
>>> countup = partial(range_new, 1)
>>> print(countup(10))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> countup.func
<function __main__.range_new>
7
MSeifert

不確かなことは、関数がより多くの引数を受け入れることを許可したいということだと思います。 「部分」機能の使用を検討しましたか?これにより、メソッドを呼び出すときに必要な数の引数を使用できます。

from functools import partial

def f(a, b, c, d):
  print(a, b, c, d)

g = partial(partial(f, 1, 2), 3)
g(4)

実装はかなり簡単なはずです

def partial(fn, *args):
  def new_func(*args2):
    newArgs = args + args2
    fn(*newArgs)

  return new_func;

元の質問で提示されたコードと上記のコードの両方が部分適用として知られていることに注意してください。カリー化は通常これよりも柔軟性があります-Python 3でそれを行う方法は次のとおりです(Python 2)ではより注意が必要です)。

def curry(fn, *args1):
  current_args = args1
  sig = signature(fn)

  def new_fn(*args2):
    nonlocal current_args
    current_args += args2
    if len(sig.parameters) > len(current_args):
      return new_fn
    else:
      return fn(*current_args)

  return new_fn

j = curry(f)
j(1)(2, 3)(4)

ここで、コードに戻ります。 range_newは、いくつかの新しい方法で使用できるようになりました。

print(range_new(1, 10))
curried_range = curry(range_new)

countup = curried_range(1)
print(countup(10))

countup_again = curried_range
print(countup_again(1, 10))
6
Parris