web-dev-qa-db-ja.com

デルの使用は悪いですか?

私は通常、コードでdelを使用してオブジェクトを削除します。

_>>> array = [4, 6, 7, 'hello', 8]
>>> del(array[array.index('hello')])
>>> array
[4, 6, 7, 8]
>>> 
_

しかし、私は聞いたことがあります 多くの人delの使用は非Python的であると言います。 delを使用するのは悪い習慣ですか?

_>>> array = [4, 6, 7, 'hello', 8]
>>> array[array.index('hello'):array.index('hello')+1] = ''
>>> array
[4, 6, 7, 8]
>>> 
_

そうでない場合、Pythonで同じことを達成する方法がたくさんあるのはなぜですか? 1つは他よりも優れていますか?

オプション1:delを使用

_>>> arr = [5, 7, 2, 3]
>>> del(arr[1])
>>> arr
[5, 2, 3]
>>> 
_

オプション2:list.remove()を使用する

_>>> arr = [5, 7, 2, 3]
>>> arr.remove(7)
>>> arr
[5, 2, 3]
>>> 
_

オプション3:list.pop()を使用する

_>>> arr = [5, 7, 2, 3]
>>> arr.pop(1)
7
>>> arr
[5, 2, 3]
>>> 
_

オプション4:スライスの使用

_>>> arr = [5, 7, 2, 3]
>>> arr[1:2] = ''
>>> arr
[5, 2, 3]
>>> 
_

この質問が意見に基づいていると思われる場合は申し訳ありませんが、私の質問に対する合理的な回答を探しています。適切な回答が得られない場合は、2日後に報奨金を追加します。

編集:

delを使用してオブジェクトの特定の部分を削除する代わりの方法がたくさんあるため、delに残された唯一の要因は、オブジェクトを完全に削除できることです。

_>>> a = 'hello'
>>> b = a
>>> del(a)
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
'hello'
>>> 
_

しかし、それを使用してオブジェクトを「定義解除」することのポイントは何ですか?

また、次のコードが両方の変数を変更するのはなぜですか。

_>>> a = []
>>> b = a
>>> a.append(9)
>>> a
[9]
>>> b
[9]
>>> 
_

しかし、delステートメントは同じ効果を達成しませんか?

_>>> a = []
>>> b = a
>>> del(a)
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
[]
>>> 
_
46
A.J. Uppal

他の答えは技術的な観点からそれを見ています(つまり、リストを変更するための最良の方法は何ですか)が、人々が提案している(はるかに)より重要な理由と言えます。スライスとは、元のリストを変更しないことです。

これは、通常、リストがどこかからのものであるためです。これを変更すると、知らないうちに非常に悪い副作用や検出が困難な副作用が発生する可能性があり、プログラムの他の場所でバグが発生する可能性があります。または、すぐにバグを引き起こさなくても、プログラム全体を理解し、推論し、デバッグするのが難しくなります。

たとえば、リスト内包表記/ジェネレータ式は、渡される「ソース」リストを変更しないという点で優れています。

_[x for x in lst if x != "foo"]  # creates a new list
(x for x in lst if x != "foo")  # creates a lazy filtered stream
_

もちろん、これは新しいリストを作成するため(メモリに関して)より高価ですが、このアプローチを使用するプログラムは数学的に純粋であり、推論が容易です。また、レイジーリスト(ジェネレーターとジェネレーター式)を使用すると、メモリオーバーヘッドもなくなり、計算はオンデマンドでのみ実行されます。すばらしい紹介については、 http://www.dabeaz.com/generators/ を参照してください。また、プログラムを設計するときは、最適化についてあまり考えるべきではありません( https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evilを参照) )。また、リンクリストでない限り、リストからアイテムを削除するのは非常にコストがかかります(Pythonのlistはそうではありません。リンクリストについては、_collections.deque_を参照してください)。


実際、副作用のない関数と 不変データ構造関数型プログラミングの基礎です、非常に強力なプログラミングパラダイム。

ただし、特定の状況では、ローカルで作成されたデータ構造や関数の入力からコピーされたデータ構造など、データ構造を適切に変更しても問題ありません(FPでも、 言語で許可されている場合 )。

_def sorted(lst):
    ret = list(lst)  # make a copy
    # mutate ret
    return ret
_

—この関数は、入力を変更しないため(また、引数のみに依存し、他には何も依存しない(つまり、(グローバル)状態がない)ため、外部からは純粋関数のように見えます。これは、何かが存在するためのもう1つの要件です。 a 純粋関数 )。

自分が何をしているのかを知っている限り、delは決して悪いことではありません。ただし、必要な場合にのみ、細心の注意を払ってあらゆる種類のデータ変更を使用してください。常に、おそらく効率は劣りますが、より正確で数学的に洗練されたコードから始めます。

...そして学ぶ 関数型プログラミング :)

P.S。 delを使用してローカル変数を削除し、メモリ内のオブジェクトへの参照を削除することもできます。これは、GC関連の目的に役立つことがよくあります。


2番目の質問への回答:

delオブジェクトを完全に削除するに関する質問の2番目の部分については、そうではありません。実際、Pythonでは、インタプリタに伝えることさえできません。 Pythonはガベージコレクションされた言語(Java、C#、Ruby、Haskellなど)であり、何をいつ削除するかを決定するのはランタイムであるため、/ VMを使用してオブジェクトをメモリから削除します。

代わりに、(辞書キーやリストアイテムではなく)変数で呼び出されたときにdelが行うことは次のとおりです。

_del a
_

それはonlyがローカル(またはグローバル)変数を削除し、not変数が指すもの(Pythonは、コンテンツ自体ではなく、コンテンツへのポインタ/参照を保持します)。実際、ローカルとグローバルは内部で辞書として格納されているため( locals() および globals() を参照)、_del a_は次と同等です:

_del locals()['a']
_

またはdel globals()['a']グローバルに適用する場合。

だからあなたが持っているなら:

_a = []
b = a
_

リストを作成し、その参照をaに保存してから、その参照の別のコピーを作成し、リストオブジェクト自体をコピー/タッチせずにbに保存します。したがって、これら2つの呼び出しは、1つの同じオブジェクトに影響します。

_a.append(1)
b.append(2)
 # the list will be [1, 2]
_

一方、bを削除することは、bが指しているものに触れることとはまったく関係ありません。

_a = []
b = a
del b
# a is still untouched and points to a list
_

また、オブジェクト属性(_del self.a_など)でdelを呼び出した場合でも、実際にlocals()を変更するのと同じように、実際には辞書_self.__dict___を変更します。/globals() _del a_を実行する場合。

P.S. Sven Marcnahが指摘したように、del locals()['a']は、関数内でローカル変数aを実際には削除しません。これは、正しいことです。これはおそらく、locals()が実際のローカルのコピーを返すためです。ただし、答えはまだ一般的に有効です。

43
Erik Kaplun

Pythonには、リストからアイテムを削除するためのさまざまな方法が含まれています。すべてがさまざまな状況で役立ちます。

# removes the first index of a list
del arr[0]

# Removes the first element containing integer 8 from a list
arr.remove(8)

# removes index 3 and returns the previous value at index 3
arr.pop(3)

# removes indexes 2 to 10
del arr[2:10]

したがって、それらはすべて彼らの場所を持っています。明らかに、番号8を削除したい場合は、例番号2の方が1または3よりも優れたオプションです。したがって、状況に基づいて実際に意味があり、最も論理的に適切なのはそれです。

[〜#〜]編集[〜#〜]

arr.pop(3)del arr [3]の違い)は、popが削除されたアイテムを返すことです。したがって、削除されたアイテムを他の配列またはデータ構造に転送するのに役立ちます。それ以外の場合、2つの使用法に違いはありません。

12
Hunter Larco

いいえ、delの使用はまったく悪いことではないと思います。実際、辞書から要素を削除するなど、本質的にそれが唯一の合理的なオプションである状況があります。

k = {'foo': 1, 'bar': 2}
del k['foo']

おそらく問題は、初心者がPythonで変数がどのように機能するかを完全に理解していないため、delの使用(または誤用)に慣れていない可能性があることです。

8

del自体の使用自体は悪くありません。ただし、特定のコードの臭いの原因となる2つの側面があります。

  1. これは副作用であり、一連の手順の一部であり、それ自体では意味がありません。
  2. delは、Pythonスコープと自動メモリ管理の理解が不十分であることを示す手動メモリ管理を備えたコードで発生する可能性があります。同じように、withステートメントは、ファイルハンドルの処理に関してより慣用的です。 file.close、スコープとコンテキストの使用は、手動でメンバーを削除するよりも慣用的です。

しかし、これはほとんどキヤノンではありません– delキーワードが本当に「悪い」場合、それは言語の中核にはなりません。私は悪魔の代弁者を演じようとしているだけです。一部のプログラマーがそれを「悪い」と呼ぶ理由を説明し、おそらくあなたに反論する立場を与えるためです。 ;)

5
kojiro

delが悪であると誰かが言うのを聞いたことがないと思います。少なくとも、他の言語機能にすぎません。 delと他のアプローチの間の問題は、実際にはユースケースに帰着します。次の場合はdelに最適です。

  1. 現在のスコープから変数を削除します。なぜあなたはこれをしたいのですか?パッケージ変数を計算するモジュールを宣言しているが、そのモジュールのコンシューマーはそれを必要としないと想像してください。そのためのまったく新しいモジュールを作成することはできますが、それはやり過ぎであるか、実際に計算されているものを曖昧にする可能性があります。たとえば、次のようにすることができます。

    _GLOBAL_1 = 'Some arbitrary thing'
    GLOBAL_2 = 'Something else'
    
    def myGlobal3CalculationFunction(str1, str2):
        # Do some transforms that consumers of this module don't need
        return val
    
    GLOBAL_3 = myGlobal3CalculationFunction(GLOBAL_1, GLOBAL_2)
    # Mystery function exits stage left
    del myGlobal3CalculationFunction
    _

    基本的に、必要に応じてdelを使用してスコープから変数を削除することに同意する人は誰もいません。同じことが辞書の値、または名前や同様の不変の参照(クラスプロパティ、インスタンスプロパティ、dict値など)によってアクセスされるほとんどすべてのものに当てはまります。

  2. もう1つのケースは、リストまたは同様の順序付けられたシーケンスからアイテムを削除する場合です。これは、いくつかの点で最初のケースと実際にはそれほど違いはありません(リストには確実に順序付けられた整数キーが含まれているため、すべてキー値コンテナーとしてアクセスできるため)。これらすべての場合において、あなたはその特定のインスタンスに存在するいくつかのデータへの参照を削除したいという同じ船に乗っています(クラスでさえクラスであるインスタンスであるため)。インプレース変更を行っています。

    順序付けられた特別なインデックスがあるということは、リストで何かが違うということですか?リストとの根本的な違いは、インプレース変更を行うと、非常に注意しない限り、すべての古いキーが基本的に役に立たなくなることです。 Pythonは、データを非常に意味的に表現する優れた機能を提供します。_[actor, verb, object]_のリストとマッピングインデックスを使用する代わりに、_{'actor' : actor, 'verb' : verb, 'object' : object}_の優れた辞書を使用できます。多くの場合、その種のアクセスには多くの価値があります(これが、関数に番号ではなく名前でアクセスする理由です):順序が重要でない場合は、なぜ厳密にするのですか?順序がIS重要です。なぜ何かを台無しにすると、それへのすべての参照が無効になります(つまり、要素の位置、要素間の距離)。

問題は、インデックスごとにリスト値を直接削除する理由にあります。ほとんどの場合、リストの単一の要素をインプレースで変更する操作には、他の関数を介した明らかな実装があります。与えられた値でアイテムを殺しますか?あなたはそれをremoveします。キューまたはスタックを実装しますか?あなたはそれをpopします(それをロックしないでください)。リスト内のインスタンスの参照数を減らしますか? _l[i] = None_も同様に機能し、古いインデックスは引き続き同じことを指します。フィルタリング要素? filterするか、リスト内包表記を使用します。いくつかの要素を除いたリストのコピーを作成しますか?あなたはそれをsliceします。重複したハッシュ可能な要素を取り除きますか?一意の要素を1回トラバースする必要がある場合は、list(set([]))またはitertoolsを確認できます。

これらすべてのケースを取り除いた後、リストにdelを使用するための約2つの一般的なユースケースになります。まず、インデックスによってランダムな要素を削除している可能性があります。これが役立つ場合は少なくありませんが、delが完全に適切です。次に、リスト内のどこにいるかを表すインデックスを保存しました(つまり、Charlie Sheenプログラミングスタイルガイドから、部屋をランダムに破壊することがある廊下で部屋から部屋へと歩いていきます)。同じリストに複数のインデックスがある場合、これは困難になります。delを使用すると、それに応じてすべてのインデックスを調整する必要があるためです。インデックスを使用して歩く構造は、要素を削除する構造ではないことが多いため、これはあまり一般的ではありません(たとえば、ゲームボードの座標グリッド)。ただし、リストをループしてジョブをポーリングしたり、完了したジョブを削除したりするなど、実際に発生します。

これは、インデックスによってインプレースでリストから要素を削除する際の基本的な問題を示しています。一度に1つずつ実行するのはかなり行き詰まっています。削除する2つの要素のインデックスがある場合は、最初の要素を削除しますか?古いインデックスが以前のインデックスを指していない可能性があります。リストは注文を保存するためのものです。 delは絶対的な順序を変更するため、リストを歩いたりジャンプしたりするのに行き詰まります。繰り返しになりますが、確かなユースケース(ランダムな破壊など)がありますが、他にも間違っているケースがたくさんあります。特に新しいPythonプログラマーの間では、関数のwhileループで恐ろしいことをします(つまり、入力に一致する値が見つかるまでループします、del index)。Delは入力としてインデックスを必要とし、実行されるとすぐに、そのリストを参照するすべての既存のインデックスが完全に異なるデータを参照するようにします。複数のインデックスがある場合、それがメンテナンスの悪夢であることがわかります。繰り返しになりますが、それは悪いことではありません。Pythonでリストを使用して物事を行うための最良の方法が実際にはめったにないということだけです。

2
Namey

あなたの「編集」の質問について、

>>> a = []
>>> b = a
>>> a.append(9)
>>> a
[9]
>>> b
[9]
>>> del a
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
[9]
>>>

これは簡単に説明できます。次の点に注意してください。

>>> id(a) == id(b) 
True

abはメモリ内の同じオブジェクトを指します)そしてpythonのメモリはGCによって管理されます。delを呼び出すときオブジェクトでは、(スコープから名前を削除するとともに)参照カウントを1減らすだけで、参照カウントが0に達するとオブジェクトは破棄されます。その場合、bは引き続きオブジェクト、したがって、それは破壊されず、まだアクセス可能です。

あなたはより多くの情報を見つけることができます ここ

1
WDRust

delは変数を変更するだけで、不要な場合もあります。したがって、上記のソリューションの方が優れている可能性があります。ただし、delは、変数を「破棄」し、それらを完全に削除する唯一の方法です。

>>> a = 9
>>> del(a)
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> 

また、辞書からアイテムを削除することもできます。

>>> dict = {1: 6}
>>> dict[1]
6
>>> del(dict[1])
>>> dict
{}
>>> 
1
user3408589