Perché è string.join (list) invece di list.join (string)?


1762

Questo mi ha sempre confuso. Sembra che questo sarebbe più bello:

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

Di questo:

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

C'è un motivo specifico per cui è così?


1
Per una facile memoria e comprensione, -dichiara che stai unendo un elenco e la conversione in una stringa. È orientato ai risultati.
Calcolo

11
@JawSaw: Questo confonde di più mem.
einpoklum,

34
Penso che la risposta breve sia che è perché il sistema di tipi di Python non è abbastanza forte, ed è stato più facile implementare questa funzionalità una volta strpiuttosto che implementarla su ogni tipo iterabile.
BallpointBen,

3
Penso che l'idea originale sia che poiché join () restituisce una stringa, dovrebbe essere chiamata dal contesto della stringa. Inserire join () in un elenco non ha molto senso in quanto un elenco è un contenitore di oggetti e non dovrebbe avere una funzione una tantum specifica solo per le stringhe.
Joshua Burns,

Risposte:


1248

È perché qualsiasi iterabile può essere unito (ad esempio, elenco, tupla, dettatura, impostazione), ma il risultato e il "joiner" devono essere stringhe.

Per esempio:

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

L'uso di qualcosa di diverso dalle stringhe genererà il seguente errore:

TypeError: elemento sequenza 0: istanza str prevista, int trovata


57
Non concordo concettualmente, anche se ha senso per il codice. list.join(string)sembra più un approccio orientato agli oggetti mentre string.join(list)per me sembra molto più procedurale.
Eduardo Pignatelli,

22
Quindi perché non è implementato su iterable?
Steen Schütt,

10
@TimeSheep: un elenco di numeri interi non ha un join significativo, anche se è iterabile.
ricorsivo il

16
Ho provato a usare print(str.join('-', my_list))e funziona, mi sento meglio.
pimgeek,

13
@TimeSheep Poiché iterable non è un tipo concreto, iterable è un'interfaccia, qualsiasi tipo che definisce un __iter__metodo. Richiedere l'implementazione di tutti gli iterable joincomplicherebbe un'interfaccia generale (che copre anche iterable su non stringhe) per un caso d'uso molto particolare. La definizione joinsu strins affronta questo problema a scapito di un ordine "non intuitivo". Una scelta migliore potrebbe essere stata quella di mantenerla una funzione con il primo argomento che è iterabile e il secondo (facoltativo) è la stringa di joiner - ma quella nave ha navigato.
user4815162342

319

Questo è stato discusso nei metodi String ... infine, il thread nell'achive Python-Dev, ed è stato accettato da Guido. Questo thread è iniziato nel giugno 1999 ed è str.joinstato incluso in Python 1.6, rilasciato nel settembre 2000 (e supportato Unicode). Python 2.0 ( strcompresi i metodi supportati join) è stato rilasciato nell'ottobre 2000.

  • In questa discussione sono state proposte quattro opzioni:
    • str.join(seq)
    • seq.join(str)
    • seq.reduce(str)
    • join come funzione integrata
  • Guido voleva supportare non solo lists, tuples, ma tutte le sequenze / iterabili.
  • seq.reduce(str) è difficile per i nuovi arrivati.
  • seq.join(str) introduce una dipendenza imprevista dalle sequenze a str / unicode.
  • join()come funzione integrata supporterebbe solo tipi di dati specifici. Quindi usare uno spazio dei nomi integrato non è buono. Se join()supporta molti tipi di dati, la creazione di un'implementazione ottimizzata sarebbe difficile, se implementata utilizzando il __add__metodo, allora è O (n²).
  • La stringa di separazione ( sep) non deve essere omessa. Esplicito è meglio che implicito.

Non ci sono altri motivi offerti in questa discussione.

Ecco alcuni pensieri aggiuntivi (i miei e quelli dei miei amici):

  • Il supporto Unicode stava arrivando, ma non era definitivo. A quel tempo UTF-8 era il più probabile in procinto di sostituire UCS2 / 4. Per calcolare la lunghezza totale del buffer delle stringhe UTF-8, è necessario conoscere la regola di codifica dei caratteri.
  • A quel tempo, Python aveva già deciso una regola di interfaccia di sequenza comune in cui un utente poteva creare una classe simile a sequenza (iterabile). Ma Python non supportava l'estensione dei tipi predefiniti fino alla 2.2. A quel tempo era difficile fornire una classe iterabile di base (che è menzionata in un altro commento).

La decisione di Guido è registrata in un messaggio storico , decidendo di str.join(seq):

Divertente, ma sembra giusto! Barry,
provaci ... - Guido van Rossum


251

Perché il join()metodo si trova nella classe stringa, anziché nella classe elenco?

Sono d'accordo che sembra divertente.

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

Nota storica.Quando ho imparato Python per la prima volta, mi aspettavo che join fosse un metodo di un elenco, che avrebbe considerato il delimitatore come argomento. Molte persone si sentono allo stesso modo, e c'è una storia dietro il metodo join. Prima di Python 1.6, le stringhe non avevano tutti questi metodi utili. C'era un modulo di stringa separato che conteneva tutte le funzioni di stringa; ogni funzione ha preso una stringa come primo argomento. Le funzioni erano ritenute abbastanza importanti da mettere sulle stringhe stesse, il che aveva senso per funzioni come inferiore, superiore e divisa. Ma molti programmatori Python hard-core si sono opposti al nuovo metodo join, sostenendo che dovrebbe essere invece un metodo dell'elenco o che non dovrebbe spostarsi affatto, ma semplicemente rimanere una parte del vecchio modulo stringa (che ha ancora molti di cose utili in esso).

--- Mark Pilgrim, Dive into Python


12
La stringlibreria Python 3 ha rimosso tutti i strmetodi ridondanti , quindi non è più possibile utilizzarli string.join(). Personalmente, non l'ho mai pensato "divertente", ha perfettamente senso, dato che puoi unirti a molto più di una semplice lista, ma il joiner è sempre una stringa!
Martijn Pieters

67

Sono d'accordo che all'inizio è controintuitivo, ma c'è una buona ragione. Unire non può essere un metodo di un elenco perché:

  • deve funzionare anche per diversi iterabili (tuple, generatori, ecc.)
  • deve avere un comportamento diverso tra i diversi tipi di stringhe.

Esistono in realtà due metodi di join (Python 3.0):

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

Se join fosse un metodo di un elenco, dovrebbe controllare i suoi argomenti per decidere quale di essi chiamare. E non puoi unire byte e str insieme, quindi ora hanno senso.


45

Perché string.join(list)invece di list.join(string)?

Questo perché joinè un metodo "stringa"! Crea una stringa da qualsiasi iterabile. Se abbiamo bloccato il metodo sugli elenchi, che dire di quando abbiamo iterabili che non sono elenchi?

E se hai una tupla di stringhe? Se questo fosse un listmetodo, dovresti eseguire il cast di ogni iteratore di stringhe come listprima di poter unire gli elementi in un'unica stringa! Per esempio:

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

Tiriamo il nostro metodo di join elenco:

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

E per usarlo, nota che dobbiamo prima creare un elenco da ogni iterabile per unire le stringhe in quell'iterabile, sprecando sia la memoria che la potenza di elaborazione:

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

Quindi vediamo che dobbiamo aggiungere un ulteriore passaggio per usare il nostro metodo list, invece di usare semplicemente il metodo stringa incorporato:

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

Avvertenza sulle prestazioni per i generatori

L'algoritmo utilizzato da Python per creare la stringa finale str.joindeve in realtà passare due volte sull'iterabile, quindi se si fornisce un'espressione di generatore, deve materializzarla in un elenco prima di poter creare la stringa finale.

Pertanto, mentre passare in giro per i generatori di solito è meglio della comprensione dell'elenco, str.joinè un'eccezione:

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

Tuttavia, l' str.joinoperazione è ancora semanticamente un'operazione "stringa", quindi ha ancora senso averla strsull'oggetto piuttosto che su vari oggetti.


24

Pensalo come la naturale operazione ortogonale da dividere.

Capisco perché è applicabile a tutto ciò che è iterabile e quindi non può essere facilmente implementato solo sulla lista.

Per leggibilità, mi piacerebbe vederlo nella lingua, ma non penso che sia effettivamente fattibile - se l'iterabilità fosse un'interfaccia, potrebbe essere aggiunta all'interfaccia ma è solo una convenzione e quindi non esiste un modo centrale per aggiungilo all'insieme di cose che sono iterabili.


13

Principalmente perché il risultato di a someString.join()è una stringa.

La sequenza (elenco o tupla o altro) non appare nel risultato, solo una stringa. Poiché il risultato è una stringa, ha senso come metodo di una stringa.


10

- in "-". join (my_list) dichiara che stai convertendo in una stringa unendo elementi a un elenco. È orientato ai risultati (solo per una facile memoria e comprensione)

Faccio un completo cheatsheet di method_of_string come riferimento.

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

Entrambi non sono carini.

string.join (xs, delimit) significa che il modulo stringa è a conoscenza dell'esistenza di un elenco, di cui non ha alcuna conoscenza, dal momento che il modulo stringa funziona solo con le stringhe.

list.join (delimit) è un po 'più bello perché siamo così abituati che le stringhe sono un tipo fondamentale (e in termini linguistici, lo sono). Tuttavia, ciò significa che l'unione deve essere inviata in modo dinamico perché nel contesto arbitrario dia.split("\n") compilatore python potrebbe non sapere cosa sia un, e dovrà cercarlo (analogamente alla ricerca di vtable), che è costoso se lo fai molto volte.

se il compilatore runtime python sa che l'elenco è un modulo integrato, può saltare la ricerca dinamica e codificare direttamente l'intento nel bytecode, mentre in caso contrario deve risolvere dinamicamente "join" di "a", che può essere composto da più livelli di ereditarietà per chiamata (poiché tra le chiamate, il significato di join potrebbe essere cambiato, poiché python è un linguaggio dinamico).

purtroppo, questo è l'ultimo difetto dell'astrazione; non importa quale astrazione scegliate, la vostra astrazione avrà senso solo nel contesto del problema che state cercando di risolvere, e come tale non potete mai avere un'astrazione coerente che non diventi incoerente con le ideologie sottostanti quando iniziate ad incollarle insieme senza avvolgerli in una visione coerente con la tua ideologia. Sapendo questo, l'approccio di Python è più flessibile poiché è più economico, spetta a te pagare di più per renderlo "più bello", sia realizzando il tuo wrapper o il tuo preprocessore.


0

Le variabili my_liste "-"sono entrambi oggetti. Nello specifico, sono istanze delle classi liste str, rispettivamente. La joinfunzione appartiene alla classe str. Pertanto, la sintassi "-".join(my_list)viene utilizzata perché l'oggetto "-"sta assumendo my_listcome input.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.