Dlaczego jest to string.join (list) zamiast list.join (string)?

1761

Zawsze mnie to myliło. Wygląda na to, że byłoby to ładniejsze:

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

Od tego:

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

Czy jest jakiś konkretny powód tego?

Evan Fosmark
źródło
1
Dla łatwej pamięci i zrozumienia -oświadcza, że ​​dołączasz do listy i konwertujesz na ciąg znaków, który jest zorientowany na wyniki.
Rachunek
11
@JawSaw: To tylko bardziej myli mem.
einpoklum
32
Myślę, że krótką odpowiedzią jest to, że system typów Pythona nie jest wystarczająco silny i łatwiej było zaimplementować tę funkcjonalność raz strniż zaimplementować ją na każdym typie iterowalnym.
BallpointBen
3
Myślę, że pierwotnym pomysłem jest to, że ponieważ join () zwraca łańcuch, musiałby zostać wywołany z kontekstu łańcucha. Umieszczenie join () na liście nie ma sensu, ponieważ lista jest kontenerem obiektów i nie powinna mieć jednorazowej funkcji specyficznej tylko dla łańcuchów.
Joshua Burns

Odpowiedzi:

1247

Wynika to z faktu, że dowolną iterowalną można połączyć (np. Listę, krotkę, dict, set), ale wynik i „łącznik” muszą być ciągami.

Na przykład:

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

Użycie czegoś innego niż ciągi spowoduje zgłoszenie następującego błędu:

TypeError: element sekwencji 0: oczekiwana instancja str, znaleziono int

rekurencyjny
źródło
55
Nie zgadzam się koncepcyjnie, nawet jeśli ma to sens kodowy. list.join(string)wydaje się bardziej podejściem zorientowanym obiektowo, podczas gdy string.join(list)dla mnie brzmi o wiele bardziej proceduralnie.
Eduardo Pignatelli
21
Dlaczego więc nie jest zaimplementowany na iterowalnym?
Steen Schütt
10
@TimeSheep: Lista liczb całkowitych nie ma znaczącego łączenia, mimo że jest iterowalna.
rekurencyjny
16
Próbowałem użyć print(str.join('-', my_list))i działa, czuje się lepiej.
pimgeek
13
@TimeSheep Ponieważ iterowalny nie jest konkretnym typem, iterowalny jest interfejsem, dowolnym typem, który definiuje __iter__metodę. Wymaganie również implementacji wszystkich iteratorów joinskomplikowałoby ogólny interfejs (który obejmuje także iteracje ponad ciągami) dla bardzo szczególnego przypadku użycia. Zdefiniowanie joinna strins bocznych kroków tego problemu kosztem „nieintuicyjnego” zamówienia. Lepszym wyborem mogłoby być zachowanie tej funkcji, przy czym pierwszym argumentem jest iterowalny, a drugim (opcjonalnym) łańcuchem łączącym - ale ten statek odpłynął.
user4815162342
318

Zostało to omówione w metodach String ... w końcu wątek w Python-Dev osiągnął i został zaakceptowany przez Guido. Wątek ten rozpoczął się w czerwcu 1999 r. I str.joinzostał zawarty w języku Python 1.6, który został wydany we wrześniu 2000 r. (I obsługuje Unicode). Python 2.0 (w tym obsługiwane strmetody join) został wydany w październiku 2000 roku.

  • W tym wątku zaproponowano cztery opcje:
    • str.join(seq)
    • seq.join(str)
    • seq.reduce(str)
    • join jako wbudowana funkcja
  • Guido chciał wspierać nie tylko lists, tuples, ale wszystkie sekwencje / iterables.
  • seq.reduce(str) jest trudny dla początkujących.
  • seq.join(str) wprowadza nieoczekiwaną zależność od sekwencji do str / unicode.
  • join()jako wbudowana funkcja obsługiwałaby tylko określone typy danych. Dlatego używanie wbudowanej przestrzeni nazw nie jest dobre. Jeśli join()obsługuje wiele typów danych, utworzenie zoptymalizowanej implementacji byłoby trudne, jeśli zostało zaimplementowane przy użyciu __add__metody, to jest to O (n²).
  • Ciąg separatora ( sep) nie powinien być pomijany. Jawne jest lepsze niż niejawne.

W tym wątku nie ma innych powodów.

Oto kilka dodatkowych myśli (moich i mojego przyjaciela):

  • Nadchodziła obsługa Unicode, ale nie była ostateczna. W tym czasie najprawdopodobniej UTF-8 zastąpił UCS2 / 4. Aby obliczyć całkowitą długość bufora łańcuchów UTF-8, musi znać zasadę kodowania znaków.
  • W tym czasie Python zdecydował się już na wspólną regułę interfejsu sekwencji, w której użytkownik mógłby stworzyć klasę podobną do sekwencji (iterowalną). Ale Python nie obsługiwał rozszerzania wbudowanych typów do 2.2. W tym czasie trudno było zapewnić podstawową klasę iterowalną (o której wspomniano w innym komentarzu).

Decyzja Guido jest zapisana w historycznej przesyłce , która decyduje o str.join(seq):

Zabawne, ale wydaje się słuszne! Barry, idź na
całość ... - Guido van Rossum

Yoshiki Shibukawa
źródło
251

Ponieważ join()metoda znajduje się w klasie łańcuchowej, zamiast w klasie listy?

Zgadzam się, że to wygląda zabawnie.

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

Nota historycznaKiedy po raz pierwszy nauczyłem się Pythona, spodziewałem się, że łączyć będzie metodą listy, która będzie traktować separator jako argument. Wiele osób czuje to samo, a za metodą łączenia kryje się pewna historia. W wersjach wcześniejszych niż Python 1.6 łańcuchy nie posiadały wszystkich przydatnych metod. Był osobny moduł łańcuchowy, który zawierał wszystkie funkcje łańcuchowe; każda funkcja wzięła ciąg jako pierwszy argument. Funkcje uznano za wystarczająco ważne, aby umieścić je na samych ciągach, co miało sens dla funkcji takich jak dolna, górna i dzielona. Ale wielu hardkorowych programistów Pythona sprzeciwiło się nowej metodzie łączenia, argumentując, że powinna to być metoda z listy lub że w ogóle nie powinna się poruszać, ale po prostu pozostać częścią starego modułu łańcuchowego (który wciąż ma wiele przydatnych rzeczy).

--- Mark Pilgrim, Zanurz się w Pythonie

Bill Karwin
źródło
12
stringBiblioteka strjęzyka Python 3 usunęła wszystkie zbędne metody, więc nie można już używać string.join(). Osobiście nigdy nie myślałem, że to „zabawne”, ma to sens, ponieważ możesz dołączyć do czegoś więcej niż tylko list, ale łącznik jest zawsze ciągiem znaków!
Martijn Pieters
67

Zgadzam się, że na początku jest to sprzeczne z intuicją, ale jest dobry powód. Dołącz nie może być metodą listy, ponieważ:

  • musi działać również dla różnych iteratorów (krotek, generatorów itp.)
  • musi mieć różne zachowanie między różnymi typami ciągów.

Istnieją dwie metody łączenia (Python 3.0):

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

Jeśli join był metodą z listy, musiałby sprawdzić swoje argumenty, aby zdecydować, który z nich wywołać. I nie możesz łączyć bajtów i str razem, więc sposób, w jaki je mają, ma teraz sens.

Kiv
źródło
45

Dlaczego jest to string.join(list)zamiast list.join(string)?

Jest tak, ponieważ joinjest to metoda „ciągowa”! Tworzy ciąg z dowolnego iterowalnego. Jeśli utknęliśmy metodę na listach, co powiesz na to, kiedy mamy iteracje, które nie są listami?

Co jeśli masz krotkę sznurków? Gdyby to była listmetoda, musiałbyś rzucić każdy taki iterator ciągów, listzanim mógłbyś połączyć elementy w jeden ciąg! Na przykład:

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

Rzućmy własną metodą łączenia listy:

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

I aby go użyć, należy pamiętać, że musimy najpierw utworzyć listę z każdej iterowalnej, aby dołączyć do niej ciągi znaków, marnując zarówno pamięć, jak i moc przetwarzania:

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

Widzimy więc, że musimy dodać dodatkowy krok, aby użyć naszej metody list, zamiast po prostu wbudowanej metody ciągu:

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

Ograniczenie wydajności dla generatorów

Algorytm, którego używa Python, aby utworzyć końcowy ciąg, str.joinfaktycznie musi dwukrotnie przejść przez iterowalny ciąg , więc jeśli podasz mu wyrażenie generujące, musi najpierw zmaterializować go na liście, zanim będzie mógł utworzyć końcowy ciąg.

Tak więc, chociaż przekazywanie generatorów jest zwykle lepsze niż rozumienie list, str.joinjest wyjątkiem:

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

Niemniej jednak str.joinoperacja jest nadal semantycznie operacją „łańcuchową”, więc nadal warto mieć ją na strobiekcie niż na różnych iteracjach.

Aaron Hall
źródło
24

Pomyśl o tym jak o naturalnej ortogonalnej operacji podziału.

Rozumiem, dlaczego ma zastosowanie do wszystkiego, co można iterować, więc nie można go łatwo wdrożyć tylko na liście.

Jeśli chodzi o czytelność, chciałbym zobaczyć to w języku, ale nie sądzę, aby było to w rzeczywistości wykonalne - gdyby iterowalność była interfejsem, mogłaby być dodana do interfejsu, ale jest to tylko konwencja, więc nie ma centralnego sposobu na dodaj go do zbioru rzeczy, które można iterować.

Andy Dent
źródło
13

Przede wszystkim dlatego, że wynikiem a someString.join()jest ciąg znaków.

Sekwencja (lista, krotka lub cokolwiek innego) nie pojawia się w wyniku, tylko ciąg znaków. Ponieważ wynikiem jest łańcuch, ma on sens jako metoda łańcucha.

S.Lott
źródło
10

- w "-". join (moja_lista) deklaruje, że konwertujesz na ciąg z łączenia elementów w listę. Jest zorientowany na wyniki. (tylko dla łatwej pamięci i zrozumienia)

Przygotowuję wyczerpujący zestaw metod z metody string dla twojego odniesienia.

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']}
Rachunek różniczkowy
źródło
3

Oba nie są miłe.

string.join (xs, delimit) oznacza, że ​​moduł string jest świadomy istnienia listy, o której nie ma żadnej wiedzy biznesowej, ponieważ moduł string działa tylko z łańcuchami.

list.join (delimit) jest nieco ładniejszy, ponieważ jesteśmy tak przyzwyczajeni do ciągów znaków, które są podstawowym typem (a mówiąc językowo są). Oznacza to jednak, że sprzężenie musi być wywoływane dynamicznie, ponieważ w dowolnym kontekście a.split("\n")kompilatora python może nie wiedzieć, co to jest, i będzie musiało to sprawdzić (analogicznie do wyszukiwania w vtable), co jest drogie, jeśli wykonasz dużo czasy.

jeśli kompilator środowiska wykonawczego Python wie, że lista jest wbudowanym modułem, może pominąć wyszukiwanie dynamiczne i bezpośrednio zakodować zamiar w kodzie bajtowym, w przeciwnym razie musi dynamicznie rozwiązać „łączenie” „a”, które może składać się z kilku warstw dziedziczenia na połączenie (ponieważ między połączeniami znaczenie łączenia mogło się zmienić, ponieważ python jest językiem dynamicznym).

niestety jest to ostateczna wada abstrakcji; bez względu na to, jaką abstrakcję wybierzesz, twoja abstrakcja będzie miała sens tylko w kontekście problemu, który próbujesz rozwiązać, i jako taka nigdy nie możesz mieć spójnej abstrakcji, która nie stałaby się niezgodna z podstawowymi ideologiami, gdy zaczniesz je kleić razem bez owijania ich w pogląd zgodny z twoją ideologią. Wiedząc o tym, podejście Pythona jest bardziej elastyczne, ponieważ jest tańsze, od Ciebie zależy, czy zapłacisz więcej, aby wyglądało to „ładniej”, albo przez utworzenie własnego opakowania, albo własnego preprocesora.

Dmitry
źródło
0

Zmienne my_listi "-"oba są obiektami. W szczególności są to instancje klas listi strodpowiednio. joinFunkcja należy do klasy str. Dlatego "-".join(my_list)używana jest składnia, ponieważ obiekt "-"przyjmuje my_listdane wejściowe.

pięćdziesiąt kart
źródło