9

私は最近Pythonに飛び込みました。以前は、主に C++ と Matlab で数値およびデータ分析コードをプログラミングしていました。Python と Ruby、そしてクロージャーについての議論をたくさん見ました。ほとんどすべての例は次のようになります。

>>> def makeAdder(y):
...  def myAdder(x):
...   return x + y
...  return myAdder
... 
>>> f = makeAdder(10)
>>> f(5)
15

これはある意味で役立つことを理解しています。ただし、現実的には、このような状況 (いわば「読み取り専用」の状況) での動作は、オブジェクト (ファンクター) によって簡単にエミュレートできます。

>>> class MyAdder(object):
...  def __init__(self,y):
...   self.y = y
...  def __call__(self,x):
...   return self.y + x
... 
>>> f = MyAdder(5)
>>> f(10)
15

このオブジェクトは、コードを書くのにそれほど多くのスペースを必要とせず、はるかに用途が広いです。また、後続のコードの追跡とデバッグがはるかに簡単になります。

この場合、非ローカル変数からのみ読み取ります。しかし、そこに書き込むこともできます: Ruby ではもちろん、Python でも nonlocal キーワードを使用します。もちろん、オブジェクトもそれをサポートしています。しかし、オブジェクトを使用すると、データがまとめられているため、何が起こっているかを正確に知ることができます。クロージャーは、完全に非透過的な方法で変数を持ち運ぶ可能性があり、これによりデバッグが驚くほど困難なコードになる可能性があります。これは本当に奇妙な例です:

irb(main):001:0> def new_counter
irb(main):002:1> x = 0
irb(main):003:1> lambda { x +=1 }
irb(main):004:1> end
=> nil
irb(main):005:0> counter_a = new_counter
=> #<Proc:0x00007f85c6421cd0@(irb):3>
irb(main):006:0> counter_a.call
=> 1
irb(main):007:0> counter_a.call
=> 2

少なくとも私にとっては、この動作は直感的ではありません。また、メモリ リークを引き起こす可能性もあります。これにより、首を吊るすための膨大な量のロープが得られます。繰り返しますが、これは Ruby では特に当てはまり、(Python とは異なり) これを明示的に有効にする必要はありません。Ruby ではメイン コード全体にブロックがあり、すべてにアクセスできるためです。クロージャー内にある結果として外部変数が変更された場合、そのクロージャーを渡すと、変数が存在する場所からスコープ外で無期限に変更される可能性があります。常に安全にデータを運ぶオブジェクトとは対照的です。

クロージャーがいかに優れているか、Java にどのように組み込む必要があるか、完全に Python に組み込まれていなかったときにどのように機能しなかったかなどについて多くの話を聞くのはなぜですか? ファンクターを使用しないのはなぜですか?それともコードをリファクタリングして避けるべきでしょうか? 明確にするために、私は口から泡を吹くOOタイプの1人ではありません. それらの使用を過小評価したり、危険性を過大評価したり、あるいはその両方を行ったりしていませんか?

編集: たぶん、3 つのことを区別する必要があります: 1 回だけ読み取るクロージャー (これは私の例が示すものであり、ほとんどの人が議論しています)、一般的に読み取るクロージャー、および書き込むクロージャーです。外側の関数に対してローカルな変数を使用して別の関数内で関数を定義する場合、これが戻ってきて悩まされる可能性はほとんどありません。そのスペースの変数には、私が考えることができる方法ではアクセスできないため、変更できません。これは非常に安全で、関数を生成する便利な (おそらくファンクター以上の) 方法です。一方、クラス メソッド内またはメイン スレッド内でクロージャを作成すると、他の場所からアクセスできる変数が呼び出されるたびに読み込まれます。だから、それは変わることができます。関数ヘッダにクローズドオーバー変数が出てこないので危険だと思います。コードのページ 1 で、メイン スレッド変数 x を閉じる長いクロージャを言ってから、無関係な理由で x を変更することができます。次に、クロージャーを再利用すると、理解できない奇妙な動作が発生します。これはデバッグが難しい場合があります。囲まれた変数に実際に書き込むと、Ruby での私の例が示すように、実際に混乱を招き、予期しない動作を引き起こす可能性があります。

Edit2:非ローカル変数への書き込み、3 番目の使用法でクロージャーからの奇妙な動作の例を示しました。これは、2 番目の使用法 (クローズド オーバー変数を変更できるスコープでクロージャーを定義する) からの奇妙な (それほど悪くはない) 動作の例です。

>>> fs = [(lambda n: i + n) for i in range(10)]
>>> fs[4](5)
14
4

1 に答える 1

10

読みやすさ。あなたのPythonの例は、ファンクターと比較して、クロージャーバージョンがどれほど明白で読みやすいかを示しています。

また、関数のように振る舞うだけのクラスを作成することも慎重に避けています。これは冗長性の匂いがします。

少なくとも、私たちが何かをしているとき、それをオブジェクトではなくアクションとして説明することは理にかなっています

別の注意として、これらの構造がPython で非常に効果的に使用される例としては、デコレータがありますほとんどの関数デコレーターはこのようなことを行い、非常に便利な機能です。

編集: 状態に関する注意として、関数は Python では特別なものではなく、依然としてオブジェクトであることを覚えておいてください。

>>> def makeAdder(y):
...     def myAdder(x):
...         return x + myAdder.y
...     myAdder.y = y
...     return myAdder
... 
>>> f = makeAdder(10)
>>> f(5)
15
>>> f.y
10
于 2012-12-26T19:48:29.747 に答える