Làm thế nào để xóa một mục trong danh sách nếu nó tồn tại?


259

Tôi đang nhận được new_tagtừ một trường văn bản biểu mẫu với self.response.get("new_tag")selected_tagstừ các trường hộp kiểm với

self.response.get_all("selected_tags")

Tôi kết hợp chúng như thế này:

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

( f1.striplistlà một hàm loại bỏ khoảng trắng bên trong chuỗi trong danh sách.)

Nhưng trong trường hợp tag_listtrống (không có thẻ mới nào được nhập) nhưng có một số selected_tags, new_tag_listchứa một chuỗi trống " ".

Ví dụ logging.info: từ :

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

Làm thế nào để tôi thoát khỏi chuỗi trống?

Nếu có một chuỗi trống trong danh sách:

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

Nhưng nếu không có chuỗi trống:

>>> 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"

Nhưng điều này mang lại:

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

Tại sao điều này xảy ra, và làm thế nào để tôi làm việc xung quanh nó?

Câu trả lời:


718

1) Phong cách gần như tiếng Anh:

Kiểm tra sự hiện diện bằng cách sử dụng intoán tử, sau đó áp dụng removephương pháp.

if thing in some_list: some_list.remove(thing)

Các removephương pháp sẽ loại bỏ chỉ sự xuất hiện đầu tiên của thing, để loại bỏ tất cả các lần xuất hiện, bạn có thể sử dụng whilethay vì if.

while thing in some_list: some_list.remove(thing)    
  • Đủ đơn giản, có lẽ là sự lựa chọn của tôi. Cho các danh sách nhỏ (không thể cưỡng lại một lớp)

2) Kiểu vịt , kiểu EAFP :

Thái độ bắn-trước-hỏi-hỏi-cuối cùng này là phổ biến trong Python. Thay vì kiểm tra trước nếu đối tượng phù hợp, chỉ cần thực hiện thao tác và bắt Ngoại lệ có liên quan:

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!")

Dĩ nhiên, mệnh đề ngoại trừ thứ hai trong ví dụ trên không chỉ là sự hài hước đáng nghi ngờ mà còn hoàn toàn không cần thiết (vấn đề là minh họa cách gõ vịt cho những người không quen với khái niệm này).

Nếu bạn mong đợi nhiều lần xuất hiện:

while True:
    try:
        some_list.remove(thing)
    except ValueError:
        break
  • một chút dài dòng cho trường hợp sử dụng cụ thể này, nhưng rất thành ngữ trong Python.
  • điều này thực hiện tốt hơn # 1
  • PEP 463 đã đề xuất một cú pháp ngắn hơn để thử / ngoại trừ việc sử dụng đơn giản sẽ có ích ở đây, nhưng nó không được chấp thuận.

Tuy nhiên, với trình quản lý bối cảnh () được giới thiệu trong python 3.4), đoạn mã trên có thể được đơn giản hóa để:

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

Một lần nữa, nếu bạn mong đợi nhiều lần xuất hiện:

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

3) Phong cách chức năng:

Khoảng năm 1993, Python có lambda, reduce(), filter()map(), lịch sự của một Lisp hacker người nhớ họ và các bản vá lỗi làm việc gửi *. Bạn có thể dùngfilter để xóa các thành phần khỏi danh sách:

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

Có một lối tắt có thể hữu ích cho trường hợp của bạn: nếu bạn muốn lọc ra các mục trống (thực tế là các mục trong đó bool(item) == False, như None, số không, chuỗi rỗng hoặc các bộ sưu tập trống khác), bạn có thể chuyển Không làm đối số đầu tiên:

cleaned_list = filter(None, some_list)
  • [update] : trong Python 2.x, filter(function, iterable)được sử dụng tương đương với [item for item in iterable if function(item)](hoặc [item for item in iterable if item]nếu đối số đầu tiên là None); trong Python 3.x, bây giờ nó tương đương với (item for item in iterable if function(item)). Sự khác biệt tinh tế là bộ lọc được sử dụng để trả về một danh sách, bây giờ nó hoạt động giống như một biểu thức trình tạo - điều này ổn nếu bạn chỉ lặp lại danh sách đã xóa và loại bỏ nó, nhưng nếu bạn thực sự cần một danh sách, bạn phải kèm theofilter() cuộc gọi với các nhà list()xây dựng.
  • * Những cấu trúc có hương vị Lispy này được coi là một người ngoài hành tinh nhỏ trong Python. Khoảng năm 2005, Guido thậm chí đã nói về việc bỏfilter - cùng với những người bạn đồng hành mapreduce(họ chưa đi nhưng reduceđã được chuyển sang mô-đun funcools , đáng để xem nếu bạn thích các chức năng bậc cao ).

4) Phong cách toán học:

Việc hiểu danh sách trở thành kiểu ưa thích cho thao tác danh sách trong Python kể từ khi được giới thiệu trong phiên bản 2.0 bởi PEP 202 . Lý do đằng sau nó là việc hiểu danh sách cung cấp một cách ngắn gọn hơn để tạo danh sách trong các tình huống map()filter()và / hoặc các vòng lặp lồng nhau hiện sẽ được sử dụng.

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

Các biểu thức của trình tạo đã được giới thiệu trong phiên bản 2.4 bởi PEP 289 . Biểu thức trình tạo sẽ tốt hơn cho các tình huống mà bạn không thực sự cần (hoặc muốn) có một danh sách đầy đủ được tạo trong bộ nhớ - như khi bạn chỉ muốn lặp lại từng phần tử một. Nếu bạn chỉ lặp đi lặp lại trong danh sách, bạn có thể nghĩ về biểu thức trình tạo như một sự hiểu biết danh sách được đánh giá lười biếng :

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

Ghi chú

  1. bạn có thể muốn sử dụng toán tử bất đẳng thức !=thay vì is not( sự khác biệt là quan trọng )
  2. đối với các nhà phê bình về các phương pháp ngụ ý một bản sao danh sách: trái với niềm tin phổ biến, các biểu thức của trình tạo không phải lúc nào cũng hiệu quả hơn so với việc hiểu danh sách - vui lòng lập hồ sơ trước khi phàn nàn

3
Tôi có thể đề nghị bỏ qua việc xử lý AttributionError trong (2) không? Nó gây mất tập trung và không được xử lý trong các phần khác (hoặc các phần khác của cùng một phần). Tồi tệ hơn, ai đó có thể sao chép mã đó mà không nhận ra họ đang cực kỳ đàn áp các ngoại lệ. Câu hỏi ban đầu giả định một danh sách, câu trả lời cũng nên.
Jason R. Coombs

1
Câu trả lời siêu toàn diện! Thật tuyệt khi được chia thành các phần khác nhau bởi "Phong cách". Cảm ơn!
halloleo

Cái nào nhanh nhất?
Sheshank S.

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

Lưu ý rằng điều này sẽ chỉ xóa một phiên bản của chuỗi trống khỏi danh sách của bạn (như mã của bạn cũng sẽ có). Danh sách của bạn có thể chứa nhiều hơn một?


5

Nếu indexkhông tìm thấy chuỗi tìm kiếm, nó sẽ ném ValueErrorbạn đang thấy. Hoặc bắt ValueError:

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

hoặc sử dụng find, trả về -1 trong trường hợp đó.

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

Là find () một thuộc tính danh sách? Tôi nhận được:>>> 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

2
remove()Cách tiếp cận của Time Pietscker trực tiếp hơn nhiều: nó trực tiếp chỉ ra mã có nghĩa là gì (thực sự không cần chỉ số trung gian i).
Eric O Lebigot

1
@Zeynel không, nó phải ở trong mỗi Python, xem docs.python.org/library/string.html#string.find . Nhưng như EOL đã chỉ ra, chỉ cần sử dụng remove là tốt hơn.
phihag

4

Thêm câu trả lời này cho đầy đủ, mặc dù nó chỉ có thể sử dụng được trong một số điều kiện nhất định.

Nếu bạn có danh sách rất lớn, việc xóa khỏi cuối danh sách sẽ tránh các phần tử CPython phải memmove, trong các trường hợp bạn có thể sắp xếp lại danh sách. Nó mang lại hiệu suất tăng để loại bỏ từ cuối danh sách, vì nó sẽ không cần đến memmove mọi mục sau khi bạn xóa - lùi lại một bước (1) .
Đối với loại bỏ một lần, sự khác biệt về hiệu suất có thể được chấp nhận, nhưng nếu bạn có một danh sách lớn và cần xóa nhiều mục - bạn có thể sẽ nhận thấy một điểm nhấn về hiệu suất.

Mặc dù thừa nhận, trong những trường hợp này, thực hiện tìm kiếm danh sách đầy đủ có khả năng cũng là một nút cổ chai hiệu suất, trừ khi các mục chủ yếu nằm ở phía trước của danh sách.

Phương pháp này có thể được sử dụng để loại bỏ hiệu quả hơn,
miễn là sắp xếp lại danh sách là chấp nhận được. (2)

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

Bạn có thể muốn tránh gây ra lỗi khi itemkhông có trong danh sách.

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. Trong khi tôi đã thử nghiệm điều này với CPython, rất có thể hầu hết / tất cả các triển khai Python khác đều sử dụng một mảng để lưu trữ danh sách bên trong. Vì vậy, trừ khi họ sử dụng cấu trúc dữ liệu tinh vi được thiết kế để định cỡ lại danh sách hiệu quả, họ có thể có cùng đặc tính hiệu suất.

Một cách đơn giản để kiểm tra điều này, so sánh sự khác biệt về tốc độ từ việc xóa khỏi mặt trước của danh sách với việc loại bỏ phần tử cuối cùng:

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

Với:

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

(đưa ra một thứ tự chênh lệch tốc độ lớn trong đó ví dụ thứ hai nhanh hơn với CPython và PyPy).

  1. Trong trường hợp này, bạn có thể cân nhắc sử dụng một set, đặc biệt nếu danh sách không có nghĩa là lưu trữ các bản sao.
    Trong thực tế, mặc dù bạn có thể cần lưu trữ dữ liệu có thể thay đổi mà không thể thêm vào a set. Ngoài ra kiểm tra trên btree nếu dữ liệu có thể được đặt hàng.

3

Eek, đừng làm bất cứ điều gì phức tạp :)

Chỉ cần filter()thẻ của bạn. bool()trả về Falsecho chuỗi rỗng, vì vậy thay vì

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

bạn nên viết

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

hoặc tốt hơn nữa, đặt logic này vào bên trong striplist()để nó không trả về chuỗi trống ở vị trí đầu tiên.


Cảm ơn! Tất cả các câu trả lời tốt nhưng tôi nghĩ rằng tôi sẽ sử dụng này. Đây là của tôi striplistchức năng, làm thế nào để kết hợp giải pháp của bạn: striplist def (l): "" "dải khoảng trắng từ chuỗi trong một danh sách l" "" trở lại ([x.strip () cho x trong l])
Zeynel

1
@Zeynel: chắc chắn rồi. Bạn có thể đặt một bài kiểm tra bên trong khả năng hiểu danh sách của bạn như thế này: [x.strip() for x in l if x.strip()]hoặc sử dụng các hàm dựng sẵn của Python mapvà các filterhàm như thế này : filter(bool, map(str.strip, l)). Nếu bạn muốn kiểm tra nó, hãy đánh giá điều này trong trình thông dịch tương tác : filter(bool, map(str.strip, [' a', 'b ', ' c ', '', ' '])).
dfichter

Bộ lọc có lối tắt cho trường hợp này (đánh giá phần tử trong ngữ cảnh Boolean): sử dụng Nonethay boolcho đối số đầu tiên là đủ.
Paulo Scardine

2

Đây là một cách tiếp cận một lớp lót khác để ném ra đó:

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

Nó không tạo một bản sao danh sách, không thực hiện nhiều lần chuyển qua danh sách, không yêu cầu xử lý ngoại lệ bổ sung và trả về đối tượng phù hợp hoặc Không có nếu không có kết quả khớp. Chỉ có vấn đề là nó làm cho một tuyên bố dài.

Nói chung, khi tìm kiếm một giải pháp một lớp không đưa ra ngoại lệ, next () là cách để đi, vì đó là một trong số ít các hàm Python hỗ trợ một đối số mặc định.


1

Tất cả bạn phải làm là điều này

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

nhưng phương pháp đó có vấn đề Bạn phải đặt một cái gì đó ở vị trí ngoại trừ để tôi tìm thấy cái này:

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

3
Bạn không nên ghi đè lên danh sách tích hợp. Và việc chuyển đổi thành một chuỗi là không cần thiết trong đoạn 2.
Robert Caspary
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.