Mengapa itu string.join (daftar) alih-alih list.join (string)?

1762

Ini selalu membingungkan saya. Sepertinya ini akan lebih baik:

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

Dari ini:

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

Apakah ada alasan khusus seperti ini?

Evan Fosmark
sumber
1
Untuk memori dan pemahaman yang mudah, -nyatakan bahwa Anda bergabung dengan daftar dan mengonversi ke string. Berorientasi pada hasil.
Kalkulus
11
@ JawSaw: Itu hanya mem membingungkan.
einpoklum
34
Saya pikir jawaban singkatnya adalah karena sistem tipe Python tidak cukup kuat, dan lebih mudah untuk mengimplementasikan fungsi ini sekali strdaripada menerapkannya pada setiap jenis iterable.
BallpointBen
3
Saya pikir ide aslinya adalah karena join () mengembalikan sebuah string, itu harus dipanggil dari konteks string. Menempatkan bergabung () pada daftar tidak masuk akal karena daftar adalah wadah objek dan tidak boleh memiliki fungsi satu kali khusus untuk string saja.
Joshua Burns

Jawaban:

1248

Itu karena setiap iterable dapat bergabung (misalnya, daftar, tuple, dict, set), tetapi hasil dan "joiner" harus berupa string.

Sebagai contoh:

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

Menggunakan sesuatu selain string akan memunculkan kesalahan berikut:

TypeError: item urutan 0: str contoh yang diharapkan, int ditemukan

rekursif
sumber
57
Saya tidak setuju secara konseptual bahkan jika itu masuk akal codewise. list.join(string)muncul lebih pendekatan berorientasi objek sedangkan string.join(list)terdengar jauh lebih prosedural bagi saya.
Eduardo Pignatelli
22
Jadi mengapa itu tidak diterapkan di iterable?
Steen Schütt
10
@TimeSheep: Daftar bilangan bulat tidak memiliki gabungan yang berarti, meskipun iterable.
rekursif
16
Saya sudah mencoba menggunakan print(str.join('-', my_list))dan berfungsi, terasa lebih baik.
pimgeek
13
@ TimeSheep Karena iterable bukan tipe beton, iterable adalah antarmuka, tipe apa pun yang mendefinisikan __iter__metode. Membutuhkan semua iterables untuk diimplementasikan juga joinakan mempersulit antarmuka umum (yang juga mencakup iterables atas non-string) untuk kasus penggunaan yang sangat khusus. Menentukan joinpada strins langkah-langkah masalah ini dengan mengorbankan pesanan "tidak intuitif". Pilihan yang lebih baik mungkin tetap berfungsi dengan argumen pertama menjadi iterable dan yang kedua (opsional) menjadi string joiner - tetapi kapal telah berlayar.
user4815162342
319

Ini dibahas dalam metode String ... akhirnya utas dengan Python-Dev, dan diterima oleh Guido. Utas ini dimulai pada Juni 1999, dan str.jointermasuk dalam Python 1.6 yang dirilis pada Sep 2000 (dan mendukung Unicode). Python 2.0 ( strtermasuk metode yang didukung join) dirilis pada Oktober 2000.

  • Ada empat opsi yang diusulkan di utas ini:
    • str.join(seq)
    • seq.join(str)
    • seq.reduce(str)
    • join sebagai fungsi bawaan
  • Guido ingin mendukung tidak hanya lists, tuples, tetapi semua urutan / iterables.
  • seq.reduce(str) sulit bagi pendatang baru.
  • seq.join(str) memperkenalkan ketergantungan yang tak terduga dari urutan ke str / unicode.
  • join()sebagai fungsi bawaan hanya akan mendukung tipe data tertentu. Jadi menggunakan namespace bawaan tidak baik. Jika join()mendukung banyak tipe data, membuat implementasi yang dioptimalkan akan sulit, jika diimplementasikan menggunakan __add__metode maka O (n²).
  • String pemisah ( sep) tidak boleh dihilangkan. Eksplisit lebih baik daripada implisit.

Tidak ada alasan lain yang ditawarkan di utas ini.

Berikut adalah beberapa pemikiran tambahan (milik saya, dan teman saya):

  • Dukungan Unicode datang, tetapi itu belum final. Pada saat itu UTF-8 adalah yang paling mungkin untuk menggantikan UCS2 / 4. Untuk menghitung panjang buffer total string UTF-8 perlu diketahui aturan pengkodean karakter.
  • Pada saat itu, Python sudah memutuskan aturan antarmuka urutan umum di mana pengguna dapat membuat kelas seperti urutan (iterable). Tapi Python tidak mendukung perluasan tipe bawaan sampai 2.2. Pada saat itu sulit untuk menyediakan kelas iterable dasar (yang disebutkan dalam komentar lain).

Keputusan Guido dicatat dalam surat historis , memutuskan str.join(seq):

Lucu, tapi sepertinya itu benar! Barry, lakukan itu ... -
Guido van Rossum

Yoshiki Shibukawa
sumber
251

Karena join()metode ini di kelas string, bukan kelas daftar?

Saya setuju itu terlihat lucu.

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

Catatan sejarah.Ketika saya pertama kali belajar Python, saya berharap bergabung menjadi metode daftar, yang akan mengambil pembatas sebagai argumen. Banyak orang merasakan hal yang sama, dan ada cerita di balik metode bergabung. Sebelum Python 1.6, string tidak memiliki semua metode yang bermanfaat ini. Ada modul string terpisah yang berisi semua fungsi string; setiap fungsi mengambil string sebagai argumen pertama. Fungsi-fungsi itu dianggap cukup penting untuk dimasukkan ke dalam string sendiri, yang masuk akal untuk fungsi-fungsi seperti lebih rendah, atas, dan split. Tetapi banyak programmer Python hard-core keberatan dengan metode bergabung baru, dengan alasan bahwa itu harus menjadi metode daftar, atau bahwa itu tidak boleh bergerak sama sekali tetapi tetap tinggal bagian dari modul string lama (yang masih memiliki banyak barang berguna di dalamnya).

--- Mark Pilgrim, Selami Python

Bill Karwin
sumber
12
stringPustaka Python 3 telah menghapus semua strmetode yang berlebihan , sehingga Anda tidak bisa lagi menggunakan string.join(). Secara pribadi, saya tidak pernah berpikir itu 'lucu', itu masuk akal, karena Anda dapat bergabung lebih dari sekadar daftar, tetapi joiner selalu menjadi string!
Martijn Pieters
67

Saya setuju bahwa itu berlawanan dengan intuisi pada awalnya, tetapi ada alasan bagus. Bergabung tidak dapat menjadi metode daftar karena:

  • itu harus bekerja untuk iterables yang berbeda juga (tupel, generator, dll.)
  • itu harus memiliki perilaku yang berbeda antara berbagai jenis string.

Sebenarnya ada dua metode bergabung (Python 3.0):

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

Jika bergabung adalah metode daftar, maka ia harus memeriksa argumennya untuk memutuskan yang mana dari mereka yang akan dihubungi. Dan Anda tidak dapat bergabung dengan byte dan str bersama-sama, jadi cara mereka sekarang masuk akal.

Kiv
sumber
45

Kenapa string.join(list)bukan list.join(string)?

Ini karena joinmetode "string"! Ini menciptakan string dari setiap iterable. Jika kita terjebak metode pada daftar, bagaimana ketika kita memiliki iterables yang bukan daftar?

Bagaimana jika Anda memiliki tuple string? Jika ini adalah listmetode, Anda harus melemparkan setiap iterator string seperti listsebelum Anda dapat bergabung dengan elemen menjadi satu string! Sebagai contoh:

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

Mari kita roll metode daftar bergabung kita sendiri:

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

Dan untuk menggunakannya, perhatikan bahwa kita harus terlebih dahulu membuat daftar dari setiap iterable untuk bergabung dengan string di iterable, membuang-buang memori dan kekuatan pemrosesan:

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

Jadi kami melihat kami harus menambahkan langkah ekstra untuk menggunakan metode daftar kami, daripada hanya menggunakan metode string bawaan:

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

Peringatan Kinerja untuk Generator

Algoritma yang digunakan Python untuk membuat string terakhir dengan str.joinbenar - benar harus melewati iterable dua kali, jadi jika Anda memberikannya ekspresi generator, itu harus terwujud menjadi daftar terlebih dahulu sebelum dapat membuat string terakhir.

Dengan demikian, sementara membagikan generator biasanya lebih baik daripada daftar pemahaman, str.joinadalah pengecualian:

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

Namun demikian, str.joinoperasi ini masih semantik operasi "string", sehingga masih masuk akal untuk memilikinya pada strobjek daripada pada iterables lain-lain.

Aaron Hall
sumber
24

Anggap saja sebagai operasi ortogonal alami untuk membelah.

Saya mengerti mengapa itu berlaku untuk apa pun yang dapat diubah sehingga tidak dapat dengan mudah diimplementasikan hanya pada daftar.

Untuk keterbacaan, saya ingin melihatnya dalam bahasa tetapi saya tidak berpikir itu benar-benar layak - jika iterability adalah sebuah antarmuka maka itu dapat ditambahkan ke antarmuka tetapi itu hanya sebuah konvensi dan jadi tidak ada cara sentral untuk tambahkan ke set hal-hal yang dapat diubah.

Andy Dent
sumber
13

Terutama karena hasil dari a someString.join()adalah string.

Urutan (daftar atau tupel atau apa pun) tidak muncul dalam hasil, hanya string. Karena hasilnya adalah string, masuk akal sebagai metode string.

S.Lott
sumber
10

- di "-". join (my_list) menyatakan bahwa Anda mengonversi string dari bergabung dengan elemen daftar. Berorientasi pada hasil (hanya untuk memori dan pemahaman yang mudah)

Saya membuat cheatsheet yang lengkap tentang methods_of_string untuk referensi Anda.

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']}
Kalkulus
sumber
3

Keduanya tidak baik.

string.join (xs, delimit) berarti bahwa modul string menyadari keberadaan daftar, yang tidak diketahui oleh bisnis, karena modul string hanya bekerja dengan string.

list.join (delimit) sedikit lebih bagus karena kita sudah terbiasa menggunakan string sebagai tipe fundamental (dan secara bahasa, mereka). Namun ini berarti bahwa join harus dikirim secara dinamis karena dalam konteks arbitrera.split("\n") kompiler python mungkin tidak tahu apa itu, dan akan perlu mencarinya (analog dengan vtable lookup), yang mahal jika Anda melakukannya banyak waktu.

jika kompiler runtime python tahu bahwa daftar adalah modul bawaan, ia dapat melewatkan pencarian dinamis dan menyandikan maksud ke dalam bytecode secara langsung, sedangkan sebaliknya ia perlu secara dinamis menyelesaikan "join" dari "a", yang mungkin terdiri atas beberapa lapisan dari inheritence per panggilan (karena di antara panggilan, arti bergabung mungkin telah berubah, karena python adalah bahasa yang dinamis).

Sayangnya, ini adalah kelemahan utama dari abstraksi; tidak peduli apa pun abstraksi yang Anda pilih, abstraksi Anda hanya akan masuk akal dalam konteks masalah yang Anda coba selesaikan, dan dengan demikian Anda tidak akan pernah bisa memiliki abstraksi yang konsisten yang tidak menjadi tidak konsisten dengan ideologi yang mendasari ketika Anda mulai menempelkannya bersama-sama tanpa membungkusnya dalam pandangan yang konsisten dengan ideologi Anda. Mengetahui hal ini, pendekatan python lebih fleksibel karena lebih murah, terserah Anda untuk membayar lebih banyak agar terlihat "lebih bagus", baik dengan membuat bungkus sendiri, atau preprosesor Anda sendiri.

Dmitry
sumber
0

Variabel my_listdan "-"keduanya objek. Secara khusus, mereka adalah contoh dari kelas listdan str, masing-masing. The joinFungsi milik kelas str. Oleh karena itu, sintaks "-".join(my_list)digunakan karena objek "-"mengambil my_listsebagai input.

lima puluh kartu
sumber