リスト内のアイテムが存在する場合、それを削除するにはどうすればよいですか?


259

私は取得していますnew_tagと、フォームのテキストフィールドからself.response.get("new_tag")selected_tagsして、チェックボックスフィールドから

self.response.get_all("selected_tags")

私はこれらを次のように組み合わせます:

tag_string = new_tag
new_tag_list = f1.striplist(tag_string.split(",") + selected_tags)

f1.striplistリストの文字列内の空白を取り除く関数です。)

しかし、それtag_listが空の場合(新しいタグは入力されません)がいくつかあるselected_tags場合new_tag_list、空の文字列が含まれます" "

たとえば、次のようになりlogging.infoます。

new_tag
selected_tags[u'Hello', u'Cool', u'Glam']
new_tag_list[u'', u'Hello', u'Cool', u'Glam']

空の文字列を削除するにはどうすればよいですか?

リストに空の文字列がある場合:

>>> s = [u'', u'Hello', u'Cool', u'Glam']
>>> i = s.index("")
>>> del s[i]
>>> s
[u'Hello', u'Cool', u'Glam']

しかし、空の文字列がない場合:

>>> s = [u'Hello', u'Cool', u'Glam']
>>> if s.index(""):
        i = s.index("")
        del s[i]
    else:
        print "new_tag_list has no empty string"

しかし、これは与える:

Traceback (most recent call last):
  File "<pyshell#30>", line 1, in <module>
    if new_tag_list.index(""):
        ValueError: list.index(x): x not in list

なぜこれが発生し、どうすれば回避できますか?

回答:


718

1)ほとんど英語のスタイル:

in演算子を使用して存在をテストしてから、removeメソッドを適用します。

if thing in some_list: some_list.remove(thing)

removeこの方法は、唯一の最初の発生を削除しますthing、あなたが使用できるすべての出現を除去するために、while代わりのをif

while thing in some_list: some_list.remove(thing)    
  • 十分に単純で、おそらく私の選択です。小さなリストの場合(ワンライナーには抵抗できません)

2)アヒル型EAFPスタイル:

このシュートファーストアスク質問最後の態度は、Pythonでは一般的です。オブジェクトが適切かどうかを事前にテストする代わりに、操作を実行して関連する例外をキャッチするだけです。

try:
    some_list.remove(thing)
except ValueError:
    pass # or scream: thing not in some_list!
except AttributeError:
    call_security("some_list not quacking like a list!")

もちろん、上記の例の2番目のexcept節は、疑わしいユーモアの問題であるだけでなく、まったく不必要です(要点は、概念に精通していない人のためにアヒルのタイピングを説明することでした)。

事の複数の発生が予想される場合:

while True:
    try:
        some_list.remove(thing)
    except ValueError:
        break
  • この特定のユースケースでは少し冗長ですが、Pythonでは非常に慣用的です。
  • これは#1よりもパフォーマンスが良い
  • PEP 463はtry / exceptの短い構文を提案しましたが、ここでは便利ですが、承認されていません。

ただし、contextlibのsuppress()contextmanager(Python 3.4で導入)を使用すると、上記のコードを次のように簡略化できます。

with suppress(ValueError, AttributeError):
    some_list.remove(thing)

繰り返しになりますが、物事の複数の発生が予想される場合:

with suppress(ValueError):
    while True:
        some_list.remove(thing)

3)機能的なスタイル:

1993年頃、Pythonはなったlambdareduce()filter()及びmap()、の礼儀のLispそれらを逃し、*作業パッチを提出したハッカーを。を使用filterして、リストから要素を削除できます。

is_not_thing = lambda x: x is not thing
cleaned_list = filter(is_not_thing, some_list)

あなたのケースに役立つかもしれないショートカットがあります:空のアイテム(実際bool(item) == FalseにはNone、ゼロ、空の文字列、またはその他の空のコレクションなどのアイテム)をフィルターで除外したい場合、最初の引数としてNoneを渡すことができます:

cleaned_list = filter(None, some_list)
  • [更新]:Python 2.xでは、filter(function, iterable)以前は[item for item in iterable if function(item)](または[item for item in iterable if item]最初の引数がの場合)と同等Noneでした。Python 3.xでは、と同等になりました(item for item in iterable if function(item))。微妙な違いは、リストを返すために使用されるフィルターですが、今はジェネレーター式のように機能します。これは、クリーンなリストを繰り返し処理して破棄するだけの場合は問題ありませんが、リストが本当に必要な場合は、filter()呼び出しを囲む必要があります。list()コンストラクタ。
  • *これらのLispyフレーバーコンストラクトは、Pythonでは少しエイリアンと見なされます。2005年頃、Guidoはドロップについても話していましたfilter -コンパニオンmapと一緒にreduce(それらはまだ消えていませんがreducefunctoolsモジュールに移動されました。高次関数が好きなら一見の価値があります)。

4)数学的スタイル:

リスト内包表記は、バージョン2.0のPEP 202で導入されて以来、Pythonでのリスト操作に適したスタイルになりました。その背後にある理論的根拠は、リスト内包表記がmap()filter()および/またはネストされたループが現在使用されている状況でリストを作成するためのより簡潔な方法を提供することです。

cleaned_list = [ x for x in some_list if x is not thing ]

ジェネレータ式は、PEP 289によってバージョン2.4で導入されました。ジェネレータ式は、要素を一度に1つずつ反復処理する場合など、完全なリストをメモリに作成する必要がない(またはしたくない)状況に適しています。リストを反復するだけの場合は、ジェネレータ式を遅延評価リスト内包と考えることができます。

for item in (x for x in some_list if x is not thing):
    do_your_thing_with(item)

ノート

  1. 不等式演算子を使用することができます !=代わりにis not違いは重要です
  2. リストのコピーを意味するメソッドの批評家の場合:一般的な考えに反して、ジェネレータ式はリストの内包よりも常に効率的ではありません-不平を言う前にプロファイリングしてください

3
(2)のAttributeError処理を省略することを提案できますか?他のセクション(または同じセクションの他の部分)で処理されず、煩わしいものです。さらに悪いことに、例外を非常に積極的に抑制していることに気付かずに、そのコードを誰かがコピーするかもしれません。元の質問はリストを前提としていますが、答えもそうです。
Jason R. Coombs 2017

1
超包括的な答え!「スタイル」によって複数のセクションに分割できるのは素晴らしいことです。ありがとう!
Halloleo

どちらが最速ですか?
Sheshank S.

12
try:
    s.remove("")
except ValueError:
    print "new_tag_list has no empty string"

これは、リストから空の文字列のインスタンスを1つだけ削除することに注意してください(コードもそうです)。リストに複数を含めることはできますか?


5

index検索された文字列が見つからない場合は、表示されている文字列をスローしValueErrorます。ValueErrorをキャッチします。

try:
    i = s.index("")
    del s[i]
except ValueError:
    print "new_tag_list has no empty string"

またはfind、その場合は-1を返すを使用します。

i = s.find("")
if i >= 0:
    del s[i]
else:
    print "new_tag_list has no empty string"

find()はリスト属性ですか?取得中:>>> s [u'Hello', u'Cool', u'Glam'] >>> i = s.find("") Traceback (most recent call last): File "<pyshell#42>", line 1, in <module> i = s.find("") AttributeError: 'list' object has no attribute 'find'
Zeynel、2011

2
Time Pietsckerのremove()アプローチははるかに直接的です。これは、コードが何をするつもりなのかを直接示します(実際には中間インデックスは必要ありませんi)。
エリックOレビゴット

1
@Zeynelいいえ、すべてのPythonに存在する必要があります。docs.python.org/library/string.html # string.findを参照してください。しかし、EOLが指摘したように、単にremoveを使用するほうが良いです。
phihag

4

この回答を完全にするために追加しますが、特定の条件下でのみ使用できます。

リストが非常に大きい場合は、リストのmemmove順序を変更できる状況で、リストの最後から削除すると、CPythonの内部処理が不要になります。それはする必要はありませんので、それは、リストの末尾から削除するパフォーマンスゲインを与えるmemmove すべてのバック一歩- 1あなた取り除いた後のアイテム(1)
1回限りの削除の場合、パフォーマンスの違いは許容できる場合がありますが、リストが大きく、多くのアイテムを削除する必要がある場合は、パフォーマンスが低下する可能性があります。

確かに、これらのケースでは、アイテムがほとんどリストの先頭にない限り、全リスト検索を実行することもパフォーマンスのボトルネックになる可能性があります。

この方法は
、リストの並べ替えが許容できる限り、より効率的な削除に使用できます。(2)

def remove_unordered(ls, item):
    i = ls.index(item)
    ls[-1], ls[i] = ls[i], ls[-1]
    ls.pop()

itemリストにない場合は、エラーが発生しないようにする必要があります。

def remove_unordered_test(ls, item):
    try:
        i = ls.index(item)
    except ValueError:
        return False
    ls[-1], ls[i] = ls[i], ls[-1]
    ls.pop()
    return True

  1. 私はこれをCPythonでテストしましたが、ほとんど/すべての他のPython実装は配列を使用してリストを内部に格納しています。したがって、効率的なリストのサイズ変更のために設計された高度なデータ構造を使用しない限り、同じパフォーマンス特性を持つ可能性があります。

これをテストする簡単な方法は、リストの先頭から削除することによる速度の違いと、最後の要素を削除することを比較します。

python -m timeit 'a = [0] * 100000' 'while a: a.remove(0)'

と:

python -m timeit 'a = [0] * 100000' 'while a: a.pop()'

(2番目の例がCPythonとPyPyの方が高速である場合は、桁違いの速度差が得られます)。

  1. この場合set、特にリストが重複を保存することを目的としていない場合は、の使用を検討することができます。
    実際には、に追加できない変更可能なデータを保存する必要がある場合がありますset。また、データを注文できるかどうかbtreeを確認してください。

3

うん、そんなに複雑なことはしないでください:)

ただfilter()あなたのタグ。 空の文字列をbool()返すFalseので、代わりに

new_tag_list = f1.striplist(tag_string.split(",") + selected_tags)

あなたは書くべきです

new_tag_list = filter(bool, f1.striplist(tag_string.split(",") + selected_tags))

またはさらに良いstriplist()ことに、最初に空の文字列を返さないように、このロジックを内部に配置します。


ありがとう!すべての良い答えですが、これを使用すると思います。これは私のあるstriplist機能を、どのように私はあなたのソリューションを組み込むん:(L)DEF striplist:「」「([Lにおけるxのx.strip()])、」リターン「リストl内の文字列からストリップの空白」
Zeynel

1
@Zeynel:確かに。あなたはどちらか、このようなあなたのリストの内包内のテストを置くことができます:[x.strip() for x in l if x.strip()]またはPythonのビルトインを使用mapしてfilter、このような機能を:filter(bool, map(str.strip, l))。テストする場合は、インタラクティブインタープリターで評価してくださいfilter(bool, map(str.strip, [' a', 'b ', ' c ', '', ' ']))
dfichter

フィルターには、この場合のショートカットがあります(ブールコンテキストで要素を評価します)。最初の引数にのNone代わりに使用するboolだけで十分です。
Paulo Scardine 2013年

2

ここに、別のワンライナーアプローチがあります。

next((some_list.pop(i) for i, l in enumerate(some_list) if l == thing), None)

リストのコピーを作成せず、リストを複数回パスせず、追加の例外処理を必要とせず、一致したオブジェクトを返すか、一致しない場合はNoneを返します。唯一の問題は、それが長い声明を出すことです。

一般に、例外をスローしないワンライナーソリューションを探す場合、next()が適しています。これは、デフォルトの引数をサポートする数少ないPython関数の1つだからです。


1

あなたがしなければならないすべてはこれです

list = ["a", "b", "c"]
    try:
        list.remove("a")
    except:
        print("meow")

しかし、その方法には問題があります。あなたは例外の場所に何かを置かなければならないので、私はこれを見つけました:

list = ["a", "b", "c"]
if "a" in str(list):
    list.remove("a")

3
組み込みのリストは上書きしないでください。また、2番目のスニペットでは、文字列への変換は必要ありません。
Robert Caspary 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.