Tại sao không có sự hiểu biết tuple trong Python?


337

Như chúng ta đã biết, có danh sách hiểu, như

[i for i in [1, 2, 3, 4]]

và có sự hiểu từ điển, như

{i:j for i, j in {1: 'a', 2: 'b'}.items()}

nhưng

(i for i in (1, 2, 3))

sẽ kết thúc trong một máy phát điện, không phải là một sự tuplehiểu biết. Tại sao vậy?

Tôi đoán là a tuplelà bất biến, nhưng dường như đây không phải là câu trả lời.


15
Ngoài ra còn có một sự hiểu biết được thiết lập - trông rất giống với sự hiểu biết chính tả ...
mgilson

3
Có một lỗi Cú pháp trong mã của bạn: {i:j for i,j in {1:'a', 2:'b'}}nên là{i:j for i,j in {1:'a', 2:'b'}.items()}
Inbar Rose

@InbarRose Cảm ơn bạn đã chỉ ra -.-
Shady Xu

Chỉ vì lợi ích của hậu thế, có một cuộc thảo luận về điều này đang diễn ra trong Trò chuyện Python
Inbar Rose

Câu trả lời:


466

Bạn có thể sử dụng biểu thức trình tạo:

tuple(i for i in (1, 2, 3))

nhưng dấu ngoặc đơn đã được sử dụng cho các biểu thức trình tạo.


15
Theo lập luận này, chúng ta có thể nói rằng việc hiểu danh sách cũng không cần thiết : list(i for i in (1,2,3)). Tôi thực sự nghĩ rằng nó đơn giản chỉ vì không có một cú pháp rõ ràng cho nó (hoặc ít nhất là không ai nghĩ về nó)
mgilson

79
Một danh sách hoặc tập hợp hoặc hiểu chính tả chỉ là đường cú pháp để sử dụng biểu thức trình tạo ra một loại cụ thể. list(i for i in (1, 2, 3))là một biểu thức tạo ra một danh sách, set(i for i in (1, 2, 3))xuất ra một tập hợp. Điều đó có nghĩa là cú pháp hiểu không cần thiết? Có lẽ không, nhưng nó rất tiện dụng. Đối với các trường hợp hiếm hoi bạn cần một bộ thay thế, biểu thức trình tạo sẽ làm, rõ ràng và không yêu cầu phát minh ra một dấu ngoặc hoặc giá đỡ khác.
Martijn Pieters

16
Câu trả lời rõ ràng là vì cú pháp tuple và dấu ngoặc đơn không rõ ràng
Charles Salvia

19
Sự khác biệt giữa việc sử dụng một sự hiểu biết và sử dụng một hàm tạo + trình tạo sẽ hơn là tinh tế nếu bạn quan tâm đến hiệu suất. Hiểu được kết quả trong việc xây dựng nhanh hơn so với việc sử dụng một máy phát được truyền cho một nhà xây dựng. Trong trường hợp sau, bạn đang tạo và thực thi các hàm và hàm rất tốn kém trong Python. [thing for thing in things]xây dựng một danh sách nhanh hơn nhiều list(thing for thing in things). Một sự hiểu biết tuple sẽ không vô dụng; tuple(thing for thing in things)có vấn đề về độ trễ và tuple([thing for thing in things])có thể có vấn đề về bộ nhớ.
Justin Turner Arthur

9
@MartijnPieters, bạn có khả năng điều chỉnh lại A list or set or dict comprehension is just syntactic sugar to use a generator expressionkhông? Nó gây ra sự nhầm lẫn bởi những người coi đây là phương tiện tương đương để kết thúc. Đó không phải là đường cú pháp về mặt kỹ thuật vì các quy trình thực sự khác nhau, ngay cả khi sản phẩm cuối cùng giống nhau.
jpp

77

Raymond Hettinger (một trong những nhà phát triển cốt lõi của Python) đã nói điều này về các bộ dữ liệu trong một tweet gần đây :

Mẹo #python: Nói chung, danh sách là để lặp; tuples cho structs. Danh sách là đồng nhất; tuples không đồng nhất. Danh sách chiều dài thay đổi.

Điều này (với tôi) hỗ trợ ý tưởng rằng nếu các mục trong chuỗi có liên quan đủ để được tạo bởi một, tốt, trình tạo, thì nó phải là một danh sách. Mặc dù một tuple có thể lặp lại và có vẻ như chỉ là một danh sách bất biến, nhưng nó thực sự tương đương với Python của cấu trúc C:

struct {
    int a;
    char b;
    float c;
} foo;

struct foo x = { 3, 'g', 5.9 };

trở thành Python

x = (3, 'g', 5.9)

26
Thuộc tính bất biến có thể là quan trọng mặc dù và thường là một lý do tốt để sử dụng một tuple khi bạn thường sử dụng một danh sách. Ví dụ: nếu bạn có một danh sách gồm 5 số mà bạn muốn sử dụng làm chìa khóa cho một lệnh, thì tuple là cách để đi.
pavon

Đó là một mẹo hay từ Raymond Hettinger. Tôi vẫn sẽ nói rằng có một trường hợp sử dụng để sử dụng hàm tạo tuple với một trình tạo, chẳng hạn như giải nén một cấu trúc khác, có lẽ lớn hơn, thành một cấu trúc nhỏ hơn bằng cách lặp lại các attrs mà bạn quan tâm để chuyển đổi thành một bản ghi tuple.
dave

2
@dave Có lẽ bạn chỉ có thể sử dụng operator.itemgettertrong trường hợp đó.
chepner

@chepner, tôi hiểu rồi. Điều đó khá gần với những gì tôi muốn nói. Nó không trả về một cuộc gọi nào, vì vậy nếu tôi chỉ cần làm điều đó một lần thì tôi không thấy nhiều chiến thắng so với việc chỉ sử dụng tuple(obj[item] for item in items)trực tiếp. Trong trường hợp của tôi, tôi đã nhúng nó vào một danh sách hiểu để lập danh sách các bản ghi tuple. Nếu tôi cần phải làm điều này nhiều lần trong suốt mã thì itemgetter trông rất tuyệt. Có lẽ itemgetter sẽ là thành ngữ hơn?
dave

Tôi thấy mối quan hệ giữa froundredet và thiết lập tương tự như của tuple và danh sách. Đó là ít hơn về tính không đồng nhất và nhiều hơn về tính bất biến - hàng chục và bộ dữ liệu có thể là chìa khóa cho từ điển, danh sách và bộ không thể do tính biến đổi của chúng.
polyglot

56

Kể từ Python 3.5 , bạn cũng có thể sử dụng *cú pháp giải nén splat để giải nén expresion của trình tạo:

*(x for x in range(10)),

2
Điều này thật tuyệt (và nó hoạt động), nhưng tôi không thể tìm thấy bất cứ nơi nào nó được ghi lại! Bạn có một liên kết?
felixphew

8
Lưu ý: Là một chi tiết triển khai, về cơ bản giống như thực hiện tuple(list(x for x in range(10)))( các đường dẫn mã giống hệt nhau , với cả hai đều xây dựng một list, với sự khác biệt duy nhất là bước cuối cùng là tạo một tupletừ listvà vứt bỏ listkhi tupleđầu ra bắt buộc). Có nghĩa là bạn không thực sự tránh một cặp tạm thời.
ShadowRanger

4
Để mở rộng nhận xét của @ShadowRanger, đây là một câu hỏi mà họ chỉ ra rằng cú pháp nghĩa đen của splat + tuple thực sự chậm hơn một chút so với việc chuyển một biểu thức trình tạo cho hàm tạo tuple.
Lucubrator

Tôi đang thử điều này trong Python 3.7.3 và *(x for x in range(10))không hoạt động. Tôi nhận được SyntaxError: can't use starred expression here. Tuy nhiên tuple(x for x in range(10))công trình.
Ryan H.

4
@RyanH. bạn cần đặt dấu phẩy cuối cùng.
czheo

27

Như một poster khác macmđã đề cập, cách nhanh nhất để tạo một bộ dữ liệu từ máy phát điện là tuple([generator]).


So sánh hiệu suất

  • Danh sách hiểu

    $ python3 -m timeit "a = [i for i in range(1000)]"
    10000 loops, best of 3: 27.4 usec per loop
  • Tuple từ danh sách hiểu:

    $ python3 -m timeit "a = tuple([i for i in range(1000)])"
    10000 loops, best of 3: 30.2 usec per loop
  • Tuple từ máy phát điện:

    $ python3 -m timeit "a = tuple(i for i in range(1000))"
    10000 loops, best of 3: 50.4 usec per loop
  • Tuple từ giải nén:

    $ python3 -m timeit "a = *(i for i in range(1000)),"
    10000 loops, best of 3: 52.7 usec per loop

Phiên bản trăn của tôi :

$ python3 --version
Python 3.6.3

Vì vậy, bạn nên luôn luôn tạo một bộ dữ liệu từ việc hiểu danh sách trừ khi hiệu suất không phải là vấn đề.


10
Lưu ý: tuplecủa listcomp yêu cầu sử dụng bộ nhớ tối đa dựa trên kích thước kết hợp của cuối cùng tuplelist. tuplecủa một genexpr, trong khi chậm hơn, có nghĩa là bạn chỉ trả tiền cho lần cuối cùng tuple, không có tạm thời list(bản thân genexpr chiếm bộ nhớ gần như cố định). Thường không có ý nghĩa, nhưng nó có thể quan trọng khi kích thước liên quan là rất lớn.
ShadowRanger

25

Hiểu được hoạt động bằng cách lặp hoặc lặp qua các mục và gán chúng vào một thùng chứa, một Tuple không thể nhận các bài tập.

Khi một Tuple được tạo, nó không thể được thêm vào, mở rộng hoặc gán cho. Cách duy nhất để sửa đổi Tuple là nếu một trong các đối tượng của nó có thể được gán cho (là một thùng chứa không tuple). Bởi vì Tuple chỉ giữ một tham chiếu đến loại đối tượng đó.

Ngoài ra - một tuple có hàm tạo riêng tuple()mà bạn có thể cung cấp cho bất kỳ trình vòng lặp nào. Điều đó có nghĩa là để tạo một tuple, bạn có thể làm:

tuple(i for i in (1,2,3))

9
Theo một số cách tôi đồng ý (về việc không cần thiết vì một danh sách sẽ làm), nhưng theo những cách khác tôi không đồng ý (về lý do là vì nó không thay đổi). Trong một số cách, nó có ý nghĩa hơn để có một sự hiểu biết cho các đối tượng bất biến. ai làm lst = [x for x in ...]; x.append()?
mgilson

@mgilson Tôi không chắc nó liên quan đến những gì tôi nói như thế nào?
Inbar Rose

2
@mgilson nếu một tuple là bất biến có nghĩa là việc triển khai cơ bản không thể "tạo ra" một tuple ("thế hệ" ngụ ý xây dựng từng mảnh một). không thay đổi có nghĩa là bạn không thể tạo một cái có 4 mảnh bằng cách thay đổi cái có 3 mảnh. thay vào đó, bạn triển khai "thế hệ" bằng cách xây dựng một danh sách, một cái gì đó được thiết kế để tạo, sau đó xây dựng bộ dữ liệu này như bước cuối cùng và loại bỏ danh sách. Ngôn ngữ phản ánh thực tế này. Hãy nghĩ về các bộ dữ liệu như các cấu trúc C.
Scott

2
mặc dù nó sẽ hợp lý khi đường cú pháp của sự hiểu biết hoạt động cho các bộ dữ liệu, vì bạn không thể sử dụng bộ dữ liệu cho đến khi sự hiểu biết được trả về. Thực tế, nó không hoạt động như có thể thay đổi, thay vào đó, một sự hiểu biết tuple có thể hoạt động giống như nối thêm chuỗi.
uchuugaka

12

Dự đoán tốt nhất của tôi là họ đã hết dấu ngoặc và không nghĩ rằng nó sẽ đủ hữu ích để cảnh báo thêm cú pháp "xấu xí" ...


1
Góc ngoặc không sử dụng.
uchuugaka

@uchuugaka - Không hoàn toàn. Chúng được sử dụng cho các toán tử so sánh. Nó có thể vẫn có thể được thực hiện mà không có sự mơ hồ, nhưng có lẽ không đáng để nỗ lực ...
mgilson

3
@uchuugaka Đáng lưu ý rằng {*()}, mặc dù xấu, hoạt động như một bộ trống rỗng theo nghĩa đen!
MI Wright

1
Ừ Từ quan điểm thẩm mỹ, tôi nghĩ tôi là một phần của set():)
mgilson

1
@QuantumMechanic: Vâng, đó là điểm chính; các khái quát giải nén đã làm cho "tập hợp" trống rỗng có thể. Lưu ý rằng {*[]}hoàn toàn kém hơn so với các tùy chọn khác; chuỗi rỗng và rỗng tuple, là bất biến, là các singletons, vì vậy không cần tạm thời để xây dựng rỗng set. Ngược lại, trống listkhông phải là một đơn vị, vì vậy bạn thực sự phải xây dựng nó, sử dụng nó để xây dựng set, sau đó phá hủy nó, mất bất kỳ lợi ích hiệu suất tầm thường nào mà người điều khiển khỉ một mắt cung cấp.
ShadowRanger

8

Tuples hiệu quả không thể được thêm vào như một danh sách.

Vì vậy, một sự hiểu biết tuple sẽ cần phải sử dụng một danh sách trong nội bộ và sau đó chuyển đổi thành một tuple.

Điều đó sẽ giống như những gì bạn làm bây giờ: tuple ([thấu hiểu])


3

Dấu ngoặc đơn không tạo ra một tuple. aka một = (hai) không phải là một tuple. Cách duy nhất là một = (hai,) hoặc một = tuple (hai). Vì vậy, một giải pháp là:

tuple(i for i in myothertupleorlistordict) 

đẹp. nó gần như giống nhau
uchuugaka

-1

Tôi tin rằng nó chỉ đơn giản là vì mục đích rõ ràng, chúng tôi không muốn làm lộn xộn ngôn ngữ với quá nhiều biểu tượng khác nhau. Ngoài ra một sự tuplehiểu biết là không bao giờ cần thiết , một danh sách chỉ có thể được sử dụng thay vì sự khác biệt tốc độ không đáng kể, không giống như một sự hiểu biết chính tả trái ngược với sự hiểu biết danh sách.


-2

Chúng ta có thể tạo các bộ dữ liệu từ một sự hiểu biết danh sách. Số sau đây thêm hai số liên tiếp vào một tuple và đưa ra một danh sách từ các số 0-9.

>>> print k
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> r= [tuple(k[i:i+2]) for i in xrange(10) if not i%2]
>>> print r
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
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.