Tại sao danh sách không có phương thức get get an toàn như từ điển?


264

Tại sao danh sách không có phương pháp "lấy" an toàn như từ điển?

>>> d = {'a':'b'}
>>> d['a']
'b'
>>> d['c']
KeyError: 'c'
>>> d.get('c', 'fail')
'fail'

>>> l = [1]
>>> l[10]
IndexError: list index out of range

1
Danh sách được sử dụng cho các mục đích khác nhau từ điển. Không cần get () cho các trường hợp sử dụng điển hình của danh sách. Tuy nhiên, đối với từ điển, get () thường khá hữu ích.
mgronber

42
Bạn luôn có thể lấy một danh sách con trống ra khỏi danh sách mà không cần tăng IndexError nếu bạn yêu cầu một lát thay thế: l[10:11]thay vì l[10], chẳng hạn. () Danh sách con sẽ có yếu tố mong muốn nếu nó tồn tại)
jsbueno

56
Trái với một số ở đây, tôi ủng hộ ý tưởng về một sự an toàn .get. Nó sẽ tương đương l[i] if i < len(l) else default, nhưng dễ đọc hơn, súc tích hơn và cho phép itrở thành một biểu thức mà không cần phải tính toán lại
Paul Draper

6
Hôm nay tôi muốn điều này tồn tại. Tôi sử dụng một hàm đắt tiền trả về một danh sách, nhưng tôi chỉ muốn mục đầu tiên hoặc Nonenếu nó không tồn tại. Thật tốt khi nói điều x = expensive().get(0, None)đó vì vậy tôi sẽ không phải đặt sự trở lại vô ích của sự đắt đỏ vào một biến tạm thời.
Ryan Hiebert

2
@Ryan câu trả lời của tôi có thể giúp bạn stackoverflow.com/a/23003811/246265
Jake

Câu trả lời:


112

Cuối cùng, nó có thể không có .getphương pháp an toàn vì một dictbộ sưu tập kết hợp (các giá trị được liên kết với các tên) trong đó không hiệu quả để kiểm tra xem có khóa không (và trả về giá trị của nó) mà không ném ngoại lệ, trong khi nó rất tầm thường để tránh ngoại lệ truy cập các phần tử danh sách (vì lenphương thức này rất nhanh). Các .getphương pháp cho phép bạn truy vấn giá trị gắn liền với một cái tên, không thể truy cập trực tiếp mục 37 trong từ điển (mà sẽ được nhiều hơn như những gì bạn đang yêu cầu danh sách của bạn).

Tất nhiên, bạn có thể dễ dàng thực hiện điều này cho mình:

def safe_list_get (l, idx, default):
  try:
    return l[idx]
  except IndexError:
    return default

Bạn thậm chí có thể đưa nó vào hàm __builtins__.listtạo __main__, nhưng đó sẽ là một thay đổi ít phổ biến hơn vì hầu hết các mã không sử dụng nó. Nếu bạn chỉ muốn sử dụng điều này với các danh sách được tạo bởi mã của riêng bạn, bạn có thể chỉ cần phân lớp listvà thêm getphương thức.


24
Python không cho phép ghép các kiểu dựng sẵn nhưlist
Imran

7
@CSZ: .getgiải quyết vấn đề mà danh sách không có - một cách hiệu quả để tránh ngoại lệ khi nhận dữ liệu có thể không tồn tại. Thật là tầm thường và rất hiệu quả để biết chỉ số danh sách hợp lệ là gì, nhưng không có cách nào đặc biệt tốt để làm điều này cho các giá trị chính trong từ điển.
Nick Bastin

10
Tôi không nghĩ rằng đây là về hiệu quả hoàn toàn - kiểm tra xem một khóa có trong từ điển và / hoặc trả lại một mục không O(1). Về mặt kiểm tra len, nó sẽ không nhanh như kiểm tra , nhưng từ quan điểm phức tạp thì tất cả đều như vậy O(1). Câu trả lời đúng là cách sử dụng / ngữ nghĩa điển hình ...
Mark Longair

3
@Mark: Không phải tất cả O (1) đều được tạo bằng nhau. Ngoài ra, dictchỉ là trường hợp tốt nhất O (1), không phải tất cả các trường hợp.
Nick Bastin

4
Tôi nghĩ rằng mọi người đang thiếu điểm ở đây. Các cuộc thảo luận không nên về hiệu quả. Hãy dừng lại với việc tối ưu hóa sớm. Nếu chương trình của bạn quá chậm, bạn đang lạm dụng .get()hoặc bạn gặp vấn đề ở nơi khác trong mã (hoặc môi trường). Điểm của việc sử dụng một phương pháp như vậy là khả năng đọc mã. Kỹ thuật "vanilla" đòi hỏi bốn dòng mã ở mọi nơi mà điều này cần phải được thực hiện. Các .get()kỹ thuật chỉ yêu cầu một và có thể dễ dàng bị xiềng xích với các cuộc gọi phương pháp tiếp theo (ví dụ my_list.get(2, '').uppercase()).
Tyler Crompton

67

Điều này hoạt động nếu bạn muốn phần tử đầu tiên, như my_list.get(0)

>>> my_list = [1,2,3]
>>> next(iter(my_list), 'fail')
1
>>> my_list = []
>>> next(iter(my_list), 'fail')
'fail'

Tôi biết đó không chính xác là những gì bạn yêu cầu nhưng nó có thể giúp đỡ người khác.


7
ít pythonic hơn lập trình chức năng-esque
Eric

next(iter(my_list[index:index+1]), 'fail')Cho phép mọi chỉ số, không chỉ 0. Hoặc ít FP hơn mà còn có thể nói là Pythonic nhiều hơn, và gần như chắc chắn dễ đọc hơn : my_list[index] if index < len(my_list) else 'fail'.
alphasoup

47

Có lẽ bởi vì nó không có ý nghĩa nhiều đối với ngữ nghĩa danh sách. Tuy nhiên, bạn có thể dễ dàng tạo riêng của mình bằng cách phân lớp.

class safelist(list):
    def get(self, index, default=None):
        try:
            return self.__getitem__(index)
        except IndexError:
            return default

def _test():
    l = safelist(range(10))
    print l.get(20, "oops")

if __name__ == "__main__":
    _test()

5
Đây là, cho đến nay, pythonic nhất trả lời OP. Lưu ý rằng bạn cũng có thể trích xuất một danh sách con, đây là một hoạt động an toàn trong Python. Cho mylist = [1, 2, 3], bạn có thể thử trích xuất phần tử thứ 9 bằng mylist [8: 9] mà không kích hoạt ngoại lệ. Sau đó, bạn có thể kiểm tra xem danh sách có trống không và trong trường hợp không trống, hãy trích xuất phần tử đơn từ danh sách được trả về.
jose.angel.jimenez

1
Đây phải là câu trả lời được chấp nhận, không phải là các bản hack một lớp không pythonic khác, đặc biệt vì nó bảo tồn tính đối xứng với từ điển.
Eric

1
Không có gì pythonic về phân lớp danh sách của riêng bạn chỉ vì bạn cần một getphương pháp hay. Tính dễ đọc. Và khả năng đọc chịu đựng với mọi lớp không cần thiết bổ sung. Chỉ cần sử dụng try / exceptcách tiếp cận mà không tạo các lớp con.
Jeyekomon

@Jeyekomon Đó là Pythonic hoàn hảo để giảm nồi hơi bằng cách phân lớp.
Keith

42

Thay vì sử dụng .get, sử dụng như thế này sẽ ổn cho danh sách. Chỉ là một sự khác biệt sử dụng.

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'

15
Điều này thất bại nếu chúng ta cố gắng lấy phần tử mới nhất với -1.
Pretobomba

Lưu ý rằng điều này không hoạt động cho các đối tượng danh sách liên kết tròn. Ngoài ra, cú pháp gây ra cái mà tôi muốn gọi là "khối quét". Khi quét qua mã để xem những gì nó làm, đây là một dòng sẽ làm tôi chậm lại một chút.
Tyler Crompton

nội tuyến nếu / khác không hoạt động với python cũ như 2.6 (hoặc là 2.5?)
Eric

3
@TylerCrompton: Không có danh sách liên kết tròn trong python. Nếu bạn tự viết một cái, bạn có thể tự do thực hiện một .getphương pháp (ngoại trừ tôi không chắc bạn sẽ giải thích chỉ số đó có ý nghĩa như thế nào trong trường hợp này, hoặc tại sao nó lại thất bại).
Nick Bastin

Một giải pháp thay thế xử lý các chỉ số tiêu cực ngoài giới hạn sẽ làlst[i] if -len(lst) <= i < len(l) else 'fail'
mic

17

Thử cái này:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'

1
Tôi thích cái này, mặc dù nó yêu cầu tạo ra một danh sách phụ mới trước.
Rick ủng hộ Monica

15

Tín dụng cho jose.angel.jimenez


Dành cho người hâm mộ "oneliner"


Nếu bạn muốn phần tử đầu tiên của danh sách hoặc nếu bạn muốn một giá trị mặc định nếu danh sách trống, hãy thử:

liste = ['a', 'b', 'c']
value = (liste[0:1] or ('default',))[0]
print(value)

trả lại a

liste = []
value = (liste[0:1] or ('default',))[0]
print(value)

trả lại default


Ví dụ cho các yếu tố khác

liste = ['a', 'b', 'c']
print(liste[0:1])  # returns ['a']
print(liste[1:2])  # returns ['b']
print(liste[2:3])  # returns ['c']

Với dự phòng mặc định

liste = ['a', 'b', 'c']
print((liste[0:1] or ('default',))[0])  # returns a
print((liste[1:2] or ('default',))[0])  # returns b
print((liste[2:3] or ('default',))[0])  # returns c

Đã thử nghiệm với Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)


1
Thay thế ngắn : value, = liste[:1] or ('default',). Có vẻ như bạn cần dấu ngoặc đơn.
qräbnö

13

Điều tốt nhất bạn có thể làm là chuyển đổi danh sách thành một dict và sau đó truy cập nó bằng phương thức get:

>>> my_list = ['a', 'b', 'c', 'd', 'e']
>>> my_dict = dict(enumerate(my_list))
>>> print my_dict
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
>>> my_dict.get(2)
'c'
>>> my_dict.get(10, 'N/A')

20
Một cách giải quyết hợp lý, nhưng hầu như không phải là "điều tốt nhất bạn có thể làm".
tripleee

3
Rất không hiệu quả mặc dù. Lưu ý: Thay vì zip range lenđiều đó, người ta chỉ có thể sử dụngdict(enumerate(my_list))
Marian

3
Đây không phải là điều tốt nhất, nó là điều tồi tệ nhất bạn có thể làm.
erikbwork

3
Đó là điều tồi tệ nhất nếu bạn xem xét hiệu suất ... nếu bạn quan tâm đến hiệu suất, bạn không mã hóa bằng ngôn ngữ được giải thích như python. Tôi tìm thấy giải pháp này bằng cách sử dụng một từ điển khá thanh lịch, mạnh mẽ và pythonic. Tối ưu hóa sớm là xấu dù sao đi nữa, vì vậy hãy có một quyết định và xem sau đó nó là một nút cổ chai.
Eric

7

Vì vậy, tôi đã nghiên cứu thêm về điều này và hóa ra không có gì cụ thể cho việc này. Tôi đã rất phấn khích khi tìm thấy list.index (value), nó trả về chỉ mục của một mục được chỉ định, nhưng không có gì để nhận giá trị ở một chỉ mục cụ thể. Vì vậy, nếu bạn không muốn sử dụng giải pháp safe_list_get mà tôi nghĩ là khá tốt. Dưới đây là một số lớp lót nếu các câu lệnh có thể hoàn thành công việc cho bạn tùy thuộc vào kịch bản:

>>> x = [1, 2, 3]
>>> el = x[4] if len(x) > 4 else 'No'
>>> el
'No'

Bạn cũng có thể sử dụng Không thay vì 'Không', điều này có ý nghĩa hơn:

>>> x = [1, 2, 3]
>>> i = 2
>>> el_i = x[i] if len(x) == i+1 else None

Ngoài ra nếu bạn muốn chỉ có mục đầu tiên hoặc cuối cùng trong danh sách, điều này hoạt động

end_el = x[-1] if x else None

Bạn cũng có thể biến chúng thành các hàm nhưng tôi vẫn thích giải pháp ngoại lệ IndexError. Tôi đã thử nghiệm một phiên bản rút gọn của safe_list_getgiải pháp và làm cho nó đơn giản hơn một chút (không có mặc định):

def list_get(l, i):
    try:
        return l[i]
    except IndexError:
        return None

Không được điểm chuẩn để xem cái gì là nhanh nhất.


1
Không thực sự pythonic.
Eric

@Eric mà đoạn nào? Tôi nghĩ rằng sự cố gắng, ngoại trừ có ý nghĩa nhất bằng cách nhìn vào nó một lần nữa.
radtek

Một chức năng độc lập không phải là pythonic. Các trường hợp ngoại lệ thực sự là một pythonic hơn một chút nhưng không nhiều vì đó là một mô hình phổ biến trong các ngôn ngữ lập trình. Những gì nhiều pythonic là một đối tượng mới mở rộng loại dựng sẵn listbằng cách phân lớp nó. Bằng cách đó, hàm tạo có thể lấy một listhoặc bất cứ thứ gì hoạt động như một danh sách và thể hiện mới hoạt động giống như a list. Xem câu trả lời của Keith dưới đây sẽ là IMHO được chấp nhận.
Eric

1
@Eric Tôi đã phân tích câu hỏi không phải là cụ thể OOP mà là "tại sao danh sách không có dict.get()giá trị tương tự để trả về giá trị mặc định từ tham chiếu chỉ mục danh sách thay vì phải bắt IndexError? Vì vậy, đây thực sự là về tính năng ngôn ngữ / thư viện (chứ không phải OOP Hơn nữa so với bối cảnh FP). Hơn nữa, người ta có thể phải đủ điều kiện sử dụng 'pythonic' của bạn vì có thể WWGD (vì sự khinh bỉ của anh ta đối với FP Python là nổi tiếng) và không nhất thiết chỉ đáp ứng PEP8 / 20.
cowbert

1
el = x[4] if len(x) == 4 else 'No'- ý bạn là len(x) > 4sao? x[4]là ngoài giới hạn nếu len(x) == 4.
mic

4

Từ điển là để tra cứu. Nó có ý nghĩa để hỏi nếu một mục tồn tại hay không. Danh sách thường được lặp đi lặp lại. Không có gì phổ biến để hỏi liệu L [10] có tồn tại không mà là nếu độ dài của L là 11.


Vâng, đồng ý với bạn. Nhưng tôi chỉ phân tích url tương đối của trang "/ nhóm / Page_name". Chia nó cho '/' và muốn kiểm tra xem PageName có bằng trang nhất định không. Sẽ thật thoải mái khi viết một cái gì đó như [url.split ('/'). Get_from_index (2, Không có) == "lalala"] thay vì kiểm tra thêm độ dài hoặc bắt ngoại lệ hoặc viết hàm riêng. Có lẽ bạn đúng nó chỉ đơn giản được coi là bất thường. Dù sao tôi vẫn không đồng ý với điều này =)
CSZ

@Nick Bastin: Không có gì sai. Đó là tất cả về sự đơn giản và tốc độ của mã hóa.
CSZ

Nó cũng sẽ hữu ích nếu bạn muốn sử dụng danh sách như một từ điển hiệu quả hơn về không gian trong trường hợp các khóa là số nguyên liên tiếp. Tất nhiên sự tồn tại của chỉ mục tiêu cực đã dừng điều đó.
Antimon

-1

Usecase của bạn về cơ bản chỉ phù hợp khi thực hiện các mảng và ma trận có độ dài cố định, để bạn biết chúng dài bao nhiêu trước khi ra tay. Trong trường hợp đó, bạn thường tạo chúng trước khi điền vào chúng bằng Không hoặc 0, do đó trên thực tế, bất kỳ chỉ mục nào bạn sẽ sử dụng đều tồn tại.

Bạn có thể nói điều này: Tôi cần .get () trên từ điển khá thường xuyên. Sau mười năm làm lập trình viên toàn thời gian, tôi không nghĩ mình đã từng cần nó trong danh sách. :)


Làm thế nào về ví dụ của tôi trong ý kiến? Những gì đơn giản và dễ đọc hơn? (url.split ('/'). getFrom Index (2) == "lalala") HOẶC (result = url.split (); len (result)> 2 và result [2] == "lalala"). Và vâng, tôi biết tôi có thể tự viết hàm như vậy =) nhưng tôi đã ngạc nhiên khi hàm đó không được tích hợp.
CSZ

1
Id 'nói trong trường hợp của bạn, bạn đang làm sai. Việc xử lý URL phải được thực hiện bằng các tuyến (khớp mẫu) hoặc truyền tải đối tượng. Nhưng, để trả lời trường hợp cụ thể của bạn : 'lalala' in url.split('/')[2:]. Nhưng vấn đề với giải pháp của bạn ở đây là bạn chỉ nhìn vào yếu tố thứ hai. Nếu URL là '/ khỉbonkey / lalala' thì sao? Bạn sẽ nhận được Truengay cả khi URL không hợp lệ.
Lennart Regebro

Tôi chỉ lấy phần tử thứ hai vì tôi chỉ cần phần tử thứ hai. Nhưng vâng, các lát có vẻ hoạt động tốt thay thế
CSZ

@CSZ: Nhưng sau đó, phần tử đầu tiên bị bỏ qua và trong trường hợp đó bạn có thể bỏ qua nó. :) Xem ý tôi là gì, ví dụ này không hoạt động tốt trong cuộc sống thực.
Lennart Regebro
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.