Tại sao lại là string.join (list) thay vì list.join (string)?


1762

Điều này luôn làm tôi bối rối. Có vẻ như điều này sẽ đẹp hơn:

my_list = ["Hello", "world"]
print(my_list.join("-"))
# Produce: "Hello-world"

Hơn cái này:

my_list = ["Hello", "world"]
print("-".join(my_list))
# Produce: "Hello-world"

Có một lý do cụ thể nó là như thế này?


1
Để dễ nhớ và dễ hiểu, hãy -tuyên bố rằng bạn đang tham gia một danh sách và chuyển đổi thành một chuỗi. Đó là định hướng kết quả.
Giải tích

11
@JawSaw: Điều đó chỉ khiến mem bối rối hơn.
einpoklum

34
Tôi nghĩ rằng câu trả lời ngắn gọn là bởi vì hệ thống loại của Python không đủ mạnh và việc thực hiện chức năng này một lần dễ dàng strhơn so với việc thực hiện nó trên mọi loại lặp.
BallpointBen

3
Tôi nghĩ ý tưởng ban đầu là bởi vì jo () trả về một chuỗi, nên nó sẽ phải được gọi từ ngữ cảnh chuỗi. Đặt tham gia () vào danh sách sẽ không có ý nghĩa gì trong đó danh sách là nơi chứa các đối tượng và không nên có chức năng một lần chỉ dành riêng cho các chuỗi.
Joshua Bỏng

Câu trả lời:


1248

Đó là bởi vì bất kỳ lần lặp nào cũng có thể được tham gia (ví dụ: list, tuple, dict, set), nhưng kết quả và "tham gia" phải là các chuỗi.

Ví dụ:

'_'.join(['welcome', 'to', 'stack', 'overflow'])
'_'.join(('welcome', 'to', 'stack', 'overflow'))
'welcome_to_stack_overflow'

Sử dụng một cái gì đó khác với chuỗi sẽ gây ra lỗi sau:

TypeError: chuỗi mục 0: ví dụ str dự kiến, int được tìm thấy


57
Tôi không đồng ý về mặt khái niệm ngay cả khi nó có ý nghĩa mã hóa. list.join(string)xuất hiện nhiều hơn một cách tiếp cận hướng đối tượng trong khi tôi string.join(list)nghe có vẻ thủ tục hơn nhiều.
Eduardo Pignatelli

22
Vậy tại sao nó không được thực hiện trên iterable?
Steen Schütt

10
@TimeSheep: Danh sách các số nguyên không có một phép nối có ý nghĩa, mặc dù nó có thể lặp lại.
đệ quy

16
Tôi đã cố gắng sử dụng print(str.join('-', my_list))và nó hoạt động, cảm thấy tốt hơn.
pimgeek

13
@TimeSheep Vì iterable không phải là một loại cụ thể, iterable là một giao diện, bất kỳ loại nào xác định một __iter__phương thức. Yêu cầu tất cả các lần lặp cũng phải thực hiện joinsẽ làm phức tạp một giao diện chung (cũng bao gồm các lần lặp trên các chuỗi không phải chuỗi) cho một trường hợp sử dụng rất cụ thể. Xác định joincác bước bên cạnh vấn đề này với chi phí của thứ tự "không trực quan". Một lựa chọn tốt hơn có thể là giữ cho nó một hàm với đối số đầu tiên là lặp và thứ hai (tùy chọn) là chuỗi liên kết - nhưng con tàu đó đã đi thuyền.
dùng4815162342

319

Điều này đã được thảo luận trong các phương thức String ... cuối cùng là luồng trong Python-Dev achive và được Guido chấp nhận. Chuỗi này bắt đầu vào tháng 6 năm 1999 và str.joinđược đưa vào Python 1.6, được phát hành vào tháng 9 năm 2000 (và được hỗ trợ Unicode). Python 2.0 ( strcác phương thức được hỗ trợ bao gồm join) đã được phát hành vào tháng 10 năm 2000.

  • Có bốn lựa chọn được đề xuất trong chủ đề này:
    • str.join(seq)
    • seq.join(str)
    • seq.reduce(str)
    • join như một chức năng tích hợp
  • Guido muốn hỗ trợ không chỉ lists, tuples, mà tất cả các chuỗi / lần lặp.
  • seq.reduce(str) là khó khăn cho người mới đến.
  • seq.join(str) giới thiệu sự phụ thuộc bất ngờ từ chuỗi đến str / unicode.
  • join()vì một hàm tích hợp sẽ chỉ hỗ trợ các loại dữ liệu cụ thể. Vì vậy, sử dụng một không gian tên được xây dựng là không tốt. Nếu join()hỗ trợ nhiều kiểu dữ liệu, việc tạo triển khai được tối ưu hóa sẽ khó khăn, nếu được triển khai bằng __add__phương thức thì đó là O (n²).
  • Không sepnên bỏ qua chuỗi phân cách ( ). Rõ ràng là tốt hơn so với ngầm.

Không có lý do khác được cung cấp trong chủ đề này.

Dưới đây là một số suy nghĩ bổ sung (của riêng tôi và của bạn tôi):

  • Hỗ trợ Unicode đã đến, nhưng nó không phải là cuối cùng. Vào thời điểm đó, UTF-8 có khả năng thay thế UCS2 / 4 nhiều nhất. Để tính tổng chiều dài bộ đệm của chuỗi UTF-8, cần biết quy tắc mã hóa ký tự.
  • Vào thời điểm đó, Python đã quyết định một quy tắc giao diện trình tự chung, nơi người dùng có thể tạo một lớp giống như trình tự (lặp lại). Nhưng Python không hỗ trợ mở rộng các loại tích hợp cho đến 2.2. Vào thời điểm đó, rất khó để cung cấp lớp lặp cơ bản (được đề cập trong một bình luận khác).

Quyết định của Guido được ghi lại trong một thư lịch sử , quyết định str.join(seq):

Hài hước, nhưng có vẻ đúng! Barry, đi cho nó ... -
Guido van Rossum


251

Bởi vì join()phương thức này nằm trong lớp chuỗi, thay vì lớp danh sách?

Tôi đồng ý nó trông buồn cười.

Xem http://www.faqs.org/docs/diveintopython/odbchelper_join.html :

Ghi chú lịch sử.Khi tôi lần đầu tiên học Python, tôi dự kiến ​​tham gia sẽ là một phương thức của một danh sách, nó sẽ lấy dấu phân cách làm đối số. Rất nhiều người cảm thấy như vậy, và có một câu chuyện đằng sau phương pháp tham gia. Trước Python 1.6, các chuỗi không có tất cả các phương thức hữu ích này. Có một mô-đun chuỗi riêng biệt chứa tất cả các hàm chuỗi; mỗi hàm lấy một chuỗi làm đối số đầu tiên của nó. Các chức năng được coi là đủ quan trọng để tự đặt lên các chuỗi, điều này có ý nghĩa đối với các chức năng như thấp hơn, trên và tách. Nhưng nhiều lập trình viên Python lõi cứng đã phản đối phương thức nối mới, cho rằng nó nên là một phương thức của danh sách, hoặc nó không nên di chuyển chút nào mà chỉ đơn giản là một phần của mô-đun chuỗi cũ (vẫn còn rất nhiều những thứ hữu ích trong đó).

--- Mark Pilgrim, Lặn vào Python


12
stringThư viện Python 3 đã loại bỏ tất cả các strphương thức dự phòng , do đó bạn không còn có thể sử dụng string.join(). Cá nhân, tôi chưa bao giờ nghĩ nó 'buồn cười', nó có ý nghĩa hoàn hảo, vì bạn có thể tham gia nhiều hơn chỉ là danh sách, nhưng người tham gia luôn là một chuỗi!
Martijn Pieters

67

Tôi đồng ý rằng ban đầu nó phản trực giác, nhưng có một lý do chính đáng. Tham gia không thể là một phương pháp của danh sách vì:

  • nó cũng phải hoạt động cho các lần lặp khác nhau (bộ dữ liệu, bộ tạo, v.v.)
  • nó phải có hành vi khác nhau giữa các loại chuỗi khác nhau.

Thực tế, có hai phương thức nối (Python 3.0):

>>> b"".join
<built-in method join of bytes object at 0x00A46800>
>>> "".join
<built-in method join of str object at 0x00A28D40>

Nếu tham gia là một phương thức của một danh sách, thì nó sẽ phải kiểm tra các đối số của nó để quyết định nên gọi một trong số chúng. Và bạn không thể nối byte và str với nhau, vì vậy cách họ có nó bây giờ có ý nghĩa.


45

Tại sao nó string.join(list)thay vì list.join(string)?

Điều này là do joinmột phương pháp "chuỗi"! Nó tạo ra một chuỗi từ bất kỳ lặp đi lặp lại. Nếu chúng ta mắc kẹt phương thức trong danh sách, vậy còn khi chúng ta có các lần lặp không có danh sách thì sao?

Điều gì nếu bạn có một chuỗi các chuỗi? Nếu đây là một listphương thức, bạn sẽ phải truyền mọi chuỗi lặp như vậy listtrước khi bạn có thể nối các phần tử thành một chuỗi! Ví dụ:

some_strings = ('foo', 'bar', 'baz')

Hãy cuộn phương pháp tham gia danh sách của riêng chúng tôi:

class OurList(list): 
    def join(self, s):
        return s.join(self)

Và để sử dụng nó, lưu ý rằng trước tiên chúng ta phải tạo một danh sách từ mỗi lần lặp để nối các chuỗi trong lần lặp đó, gây lãng phí cả bộ nhớ và khả năng xử lý:

>>> l = OurList(some_strings) # step 1, create our list
>>> l.join(', ') # step 2, use our list join method!
'foo, bar, baz'

Vì vậy, chúng tôi thấy chúng tôi phải thêm một bước nữa để sử dụng phương thức danh sách của mình, thay vì chỉ sử dụng phương thức chuỗi dựng sẵn:

>>> ' | '.join(some_strings) # a single step!
'foo | bar | baz'

Hiệu suất Caveat cho máy phát điện

Thuật toán Python sử dụng để tạo chuỗi cuối cùng str.jointhực sự phải vượt qua lần lặp hai lần, vì vậy nếu bạn cung cấp cho nó một biểu thức trình tạo, nó phải cụ thể hóa nó thành một danh sách trước khi có thể tạo chuỗi cuối cùng.

Vì vậy, trong khi truyền xung quanh máy phát điện thường tốt hơn so với hiểu danh sách, str.joinlà một ngoại lệ:

>>> import timeit
>>> min(timeit.repeat(lambda: ''.join(str(i) for i in range(10) if i)))
3.839168446022086
>>> min(timeit.repeat(lambda: ''.join([str(i) for i in range(10) if i])))
3.339879313018173

Tuy nhiên, str.joinhoạt động vẫn là một hoạt động "chuỗi" về mặt ngữ nghĩa, do đó, nó vẫn có ý nghĩa để có nó trên strđối tượng hơn trên các công cụ lặp lại linh tinh.


24

Hãy nghĩ về nó như là hoạt động trực giao tự nhiên để phân chia.

Tôi hiểu lý do tại sao nó có thể áp dụng cho bất cứ điều gì có thể lặp lại và vì vậy không thể dễ dàng thực hiện chỉ trong danh sách.

Để dễ đọc, tôi muốn thấy nó bằng ngôn ngữ nhưng tôi không nghĩ điều đó thực sự khả thi - nếu tính lặp lại là một giao diện thì nó có thể được thêm vào giao diện nhưng nó chỉ là một quy ước và vì vậy không có cách nào trung tâm để thêm nó vào tập hợp những thứ có thể lặp lại.


13

Chủ yếu bởi vì kết quả của a someString.join()là một chuỗi.

Chuỗi (danh sách hoặc tuple hoặc bất cứ điều gì) không xuất hiện trong kết quả, chỉ là một chuỗi. Bởi vì kết quả là một chuỗi, nó có ý nghĩa như là một phương thức của chuỗi.


10

- trong "-". tham gia (my_list) tuyên bố rằng bạn đang chuyển đổi thành một chuỗi từ tham gia các phần tử một danh sách. Đó là định hướng kết quả. (chỉ để dễ nhớ và hiểu)

Tôi tạo một bộ quần áo đầy đủ các phương thức_of_ chuỗi để bạn tham khảo.

string_methonds_44 = {
    'convert': ['join','split', 'rsplit','splitlines', 'partition', 'rpartition'],
    'edit': ['replace', 'lstrip', 'rstrip', 'strip'],
    'search': ['endswith', 'startswith', 'count', 'index', 'find','rindex', 'rfind',],
    'condition': ['isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isnumeric','isidentifier',
                  'islower','istitle', 'isupper','isprintable', 'isspace', ],
    'text': ['lower', 'upper', 'capitalize', 'title', 'swapcase',
             'center', 'ljust', 'rjust', 'zfill', 'expandtabs','casefold'],
    'encode': ['translate', 'maketrans', 'encode'],
    'format': ['format', 'format_map']}

3

Cả hai đều không đẹp.

string.join (xs, delimit) có nghĩa là mô-đun chuỗi nhận thức được sự tồn tại của một danh sách mà nó không có doanh nghiệp biết về, vì mô-đun chuỗi chỉ hoạt động với các chuỗi.

list.join (delimit) đẹp hơn một chút vì chúng ta đã quá quen với các chuỗi là một loại cơ bản (và nói theo ngôn ngữ, chúng là như vậy). Tuy nhiên, điều này có nghĩa là tham gia cần phải được gửi đi một cách linh hoạt bởi vì trong bối cảnh tùy ý của a.split("\n")trình biên dịch python có thể không biết a là gì và sẽ cần phải tìm kiếm nó (tương tự như tra cứu vtable), rất tốn kém nếu bạn làm điều đó rất nhiều lần

nếu trình biên dịch thời gian chạy python biết rằng danh sách đó là một mô-đun tích hợp, nó có thể bỏ qua việc tra cứu động và mã hóa ý định trực tiếp vào mã byte, trong khi đó, nó cần phải tự động giải quyết "tham gia" của "a", có thể lên một vài lớp về tính kế thừa trên mỗi cuộc gọi (vì giữa các cuộc gọi, ý nghĩa của phép nối có thể đã thay đổi, vì python là ngôn ngữ động).

đáng buồn thay, đây là lỗ hổng cuối cùng của sự trừu tượng; cho dù bạn chọn cách trừu tượng nào, sự trừu tượng của bạn sẽ chỉ có ý nghĩa trong bối cảnh của vấn đề bạn đang cố gắng giải quyết, và như vậy bạn không bao giờ có thể có một sự trừu tượng nhất quán mà không trở nên không phù hợp với các ý thức hệ cơ bản khi bạn bắt đầu dán chúng cùng nhau mà không gói chúng trong một quan điểm phù hợp với ý thức hệ của bạn. Biết được điều này, cách tiếp cận của python linh hoạt hơn vì nó rẻ hơn, bạn phải trả nhiều tiền hơn để khiến nó trông "đẹp hơn", bằng cách tạo ra trình bao bọc của riêng bạn hoặc bộ xử lý trước của riêng bạn.


0

Các biến my_list"-"là cả hai đối tượng. Cụ thể, chúng là các thể hiện của các lớp liststr, tương ứng. Các joinchức năng thuộc về lớp str. Do đó, cú pháp "-".join(my_list)được sử dụng vì đối tượng "-"đang lấy my_listlàm đầu vào.

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.