私はPythonで [〜#〜] raii [〜#〜] を見つけようとしてきました。 Resource Allocation Is Initializationは、オブジェクトが作成されるときにオブジェクトが初期化されるC++のパターンです。失敗すると、例外がスローされます。このようにして、プログラマーは、オブジェクトが半分構築された状態のままになることは決してないことを知っています。 Pythonこれだけのことができます。
ただし、RAIIはC++のスコープ規則とも連携して、オブジェクトの迅速な破棄を保証します。変数がスタックから外れるとすぐに破棄されます。これはPythonで発生する可能性がありますが、外部参照または循環参照がない場合に限ります。
さらに重要なことに、オブジェクトの名前は、オブジェクトが存在する関数が終了するまで(場合によってはそれより長く)存在し続けます。モジュールレベルの変数は、モジュールの存続期間中は存続します。
次のようなことをするとエラーが発生します:
for x in some_list:
...
... 100 lines later ...
for i in x:
# Oops! Forgot to define x first, but... where's my error?
...
使用後に手動で名前を削除することもできますが、それはかなり醜く、私の側で努力が必要です。
この場合、Do-What-I-Meanを実行したいと思います。
for x in some_list:
surface = x.getSurface()
new_points = []
for x,y,z in surface.points:
... # Do something with the points
new_points.append( (x,y,z) )
surface.points = new_points
x.setSurface(surface)
Pythonはいくつかのスコープを実行しますが、インデントレベルではなく、機能レベルでのみ実行します。名前を再利用できるように、変数のスコープを設定するためだけに新しい関数を作成する必要があるのはばかげているようです。
Python 2.5には "with"ステートメント がありますが、これには__enter__
関数と__exit__
関数を明示的に挿入する必要があり、一般に、ファイルやミューテックスロックなどのリソースのクリーンアップに向いているようです。出口ベクトルの。スコーピングには役立ちません。それとも私は何かが足りないのですか?
「PythonRAII」と「Pythonスコープ」を検索しましたが、問題に直接かつ信頼できる方法で対処するものは見つかりませんでした。私はすべてのPEPを調べました。この概念はPython内では扱われていないようです。
Pythonでスコープ変数が欲しいので、私は悪い人ですか?それはあまりにも非Pythonicですか?
私はそれを食べていませんか?
おそらく私は、言語の動的な側面の利点を取り除こうとしています。スコープを強制したい場合があるのは利己的ですか?
コンパイラー/インタープリターに私の怠慢な変数の再利用の間違いを見つけてもらいたいのは怠惰ですか?ええ、もちろん、私は怠け者ですが、悪い意味で怠け者ですか?
tl; dr RAIIは不可能です。一般的にスコープと混同し、これらの余分なスコープを見逃すと、おそらく悪いコードを書いていることになります。
おそらく私はあなたの質問を受け取らないか、Pythonについていくつかの非常に重要なことを理解していません...まず、スコープに関連付けられた決定論的なオブジェクトの破壊は不可能ですガベージコレクションされた言語で。 Pythonの変数は単なる参照です。ポインタが指すとすぐに、メモリのmalloc
'dチャンクがfree
'されることは望ましくありません。範囲外になりますか?参照カウントを使用する場合のいくつかの状況での実用的な例外-しかし、正確に設定するのに十分な言語はありません石での実装。
そしてたとえCPythonのように参照カウントがあるとしても、それは実装の詳細です。一般に、参照カウントを使用せずにさまざまな実装notがあるPython)に含めると、すべてのオブジェクトがぶら下がっているようにコーディングする必要がありますメモリがなくなるまで。
関数呼び出しの残りの部分に存在する名前の場合:del
ステートメントを使用して、現在のスコープまたはグローバルスコープから名前をcan削除できます。 。ただし、これは手動のメモリ管理とは関係ありません。参照を削除するだけです。これは、参照されるオブジェクトがGCされるようにトリガーする場合としない場合があり、演習のポイントではありません。
正解です。with
はスコープとは関係がなく、決定論的なクリーンアップとは関係ありません(したがって、最終的にはRAIIと重複しますが、手段では重複しません)。
おそらく私は、言語の動的な側面の利点を取り除こうとしています。スコープを強制したい場合があるのは利己的ですか?
いいえ。まともな字句スコープは、動的/静的性に依存しないメリットです。確かに、Python(2-3はこれをほぼ修正しました)にはこの点で弱点がありますが、クロージャの領域にあります。
しかし、「なぜ」を説明するために:Pythonmust宣言なしで別のことを言っているので、新しいスコープを開始する場所を控えめにする必要があります、名前に割り当てると、その名前は最も内側/現在のスコープに対してローカルになります。たとえば、forループに独自のスコープがある場合、ループ外の変数を簡単に変更することはできません。
コンパイラー/インタープリターに私の怠慢な変数の再利用の間違いを見つけてもらいたいのは怠惰ですか?ええ、もちろん、私は怠け者ですが、悪い意味で怠け者ですか?
繰り返しになりますが、(エラーや落とし穴をもたらすような方法で)名前を誤って再利用することはまれであり、とにかく小さなことだと思います。
編集:これをできるだけ明確にもう一度述べるには:
with
ステートメントを介して行われます。はい、新しいスコープは導入されません(以下を参照)。これは目的ではないためです。管理対象オブジェクトがバインドされている名前が削除されても、削除されません。クリーンアップは行われましたが、残っているのは「使用できない私に触れないでください」オブジェクト(閉じたファイルストリームなど)です。あなたはwith
について正しいです-それは変数のスコープとは完全に無関係です。
問題があると思われる場合は、グローバル変数を避けてください。これには、モジュールレベルの変数が含まれます。
Pythonで状態を非表示にする主なツールはクラスです。
ジェネレータ式(およびPython 3でもリスト内包表記)には独自のスコープがあります。
関数がローカル変数を見失うほど長い場合は、おそらくコードをリファクタリングする必要があります。
ただし、RAIIはC++のスコープ規則とも連携して、オブジェクトの迅速な破棄を保証します。
これは、メモリが 代替可能 であるという考えに基づいているGC言語では重要でないと見なされます。新しいオブジェクトを割り当てるのに十分なメモリが他の場所にある限り、オブジェクトのメモリを再利用する差し迫った必要はありません。ファイルハンドル、ソケット、ミューテックスなどの代替不可能なリソースは、特別に処理される特殊なケースと見なされます(例:with
)。これは、すべてのリソースを同じように扱うC++のモデルとは対照的です。
変数がスタックから外れるとすぐに破棄されます。
Pythonにはスタック変数がありません。 C++の用語では、すべてはshared_ptr
。
Pythonはいくつかのスコープを実行しますが、インデントレベルではなく、機能レベルでのみ実行します。名前を再利用できるように、変数のスコープを設定するためだけに新しい関数を作成する必要があるのはばかげているようです。
それまたはジェネレーター理解レベルで(そして3.xではall理解で)スコープを行います。
for
ループ変数を壊したくない場合は、それほど多くのfor
ループを使用しないでください。特に、ループでappend
を使用するのは非Pythonicです。の代わりに:
new_points = []
for x,y,z in surface.points:
... # Do something with the points
new_points.append( (x,y,z) )
書く:
new_points = [do_something_with(x, y, z) for (x, y, z) in surface.points]
または
# Can be used in Python 2.4-2.7 to reduce scope of variables.
new_points = list(do_something_with(x, y, z) for (x, y, z) in surface.points)
基本的に、あなたはおそらく間違った言語を使用しています。正しいスコープルールと信頼性の高い破棄が必要な場合は、C++を使用するか、Perlを試してください。メモリがいつ解放されるかについてのGCの議論は、要点を見逃しているようです。ミューテックスやファイルハンドルなどの他のリソースを解放することです。 C#は、参照カウントがゼロになったときに呼び出されるデストラクタと、メモリをリサイクルすることを決定したときに呼び出されるデストラクタを区別していると思います。人々はメモリのリサイクルについてそれほど心配していませんが、参照されなくなったらすぐに知りたいと思っています。 Pythonは言語としての本当の可能性を持っていたので残念です。しかし、それは型破りなスコープであり、信頼性の低いデストラクタ(または少なくとも実装に依存するもの)は、C++とPerlで得られる力を否定されていることを意味します。
GCで古いメモリをリサイクルするのではなく、利用可能な場合は新しいメモリを使用することについての興味深いコメント。それはメモリリークを言うための単なる空想的な言い方ではありません:-)
何年にもわたるC++の後でPythonに切り替えると、ファイルや接続を閉じるなど、RAIIタイプの動作を模倣するために__del__
に頼りたくなることがわかりました。ただし、状況があります。 (たとえば、Rxによって実装されたオブザーバーパターン)監視対象がオブジェクトへの参照を維持し、オブジェクトを存続させます!したがって、ソースによって終了される前に接続を閉じたい場合は、試行してもどこにも到達しません__del__
でそれを行います。
UIプログラミングでは、次の状況が発生します。
class MyComponent(UiComponent):
def add_view(self, model):
view = TheView(model) # observes model
self.children.append(view)
def remove_view(self, index):
del self.children[index] # model keeps the child alive
したがって、RAIIタイプの動作を取得する方法は次のとおりです。追加フックと削除フックを使用してコンテナーを作成します。
import collections
class ScopedList(collections.abc.MutableSequence):
def __init__(self, iterable=list(), add_hook=lambda i: None, del_hook=lambda i: None):
self._items = list()
self._add_hook = add_hook
self._del_hook = del_hook
self += iterable
def __del__(self):
del self[:]
def __getitem__(self, index):
return self._items[index]
def __setitem__(self, index, item):
self._del_hook(self._items[index])
self._add_hook(item)
self._items[index] = item
def __delitem__(self, index):
if isinstance(index, slice):
for item in self._items[index]:
self._del_hook(item)
else:
self._del_hook(self._items[index])
del self._items[index]
def __len__(self):
return len(self._items)
def __repr__(self):
return "ScopedList({})".format(self._items)
def insert(self, index, item):
self._add_hook(item)
self._items.insert(index, item)
UiComponent.children
がScopedList
であり、子に対してacquire
メソッドとdispose
メソッドを呼び出す場合、使用されているのと同じように、確定的なリソースの取得と破棄が保証されます。 C++で。