Чому це string.join (список) замість list.join (рядок)?


1761

Це завжди мене бентежило. Здається, це було б приємніше:

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

Чим це:

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

Чи є конкретна причина, що вона така?


1
Для легкої пам’яті та розуміння -заявляєте, що ви приєднуєтесь до списку та переходите на рядок. Орієнтований на результат.
Обчислення

11
@JawSaw: Це просто плутає пам'ять більше.
einpoklum

32
Я думаю, що коротка відповідь полягає в тому, що система типів Python недостатньо сильна, і було простіше реалізувати цю функцію один раз, strніж впроваджувати її на кожному ітерабельному типі.
BallpointBen

3
Я думаю, що початкова ідея полягає в тому, що, оскільки join () повертає рядок, його потрібно було б викликати з контексту рядка. Якщо розміщувати join () у списку, немає сенсу в тому, що список є контейнером об'єктів і не повинен мати разової функції, характерної лише для рядків.
Джошуа Бернс

Відповіді:


1247

Це тому, що можна приєднати будь-який ітерабельний (наприклад, список, кортеж, диктант, встановити), але результат і "столяр" повинні бути рядками.

Наприклад:

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

Використання чогось іншого, ніж рядки, призведе до такої помилки:

TypeError: елемент послідовності 0: очікуваний екземпляр str, int знайдено


56
Я не згоден концептуально, навіть якщо це має сенс кодирувати. list.join(string)видається більше об'єктно-орієнтованим підходом, тоді як string.join(list)для мене це звучить набагато більш процедурно.
Едуардо Піньятеллі

21
То чому він не реалізований на iterable?
Стівен Шютт

10
@TimeSheep: Список цілих чисел не має значущого приєднання, навіть якщо він є доступним.
рекурсивна

16
Я намагався використовувати, print(str.join('-', my_list))і це працює, відчуває себе краще.
pimgeek

13
@TimeSheep Оскільки ітерабельний не є конкретним типом, ітерабельним є інтерфейс, будь-який тип, який визначає __iter__метод. Потрібна також реалізація всіх ітерабелів joinускладнюватиме загальний інтерфейс (який також охоплює ітерабелі над нерядками) для дуже конкретного випадку використання. Визначення joinна рядках побічних кроків цієї проблеми ціною "неінтуїтивного" замовлення. Кращим вибором могло б стати функція, коли перший аргумент був ітерабельним, а другий (необов'язковий) - столярним рядком - але це судно відпливло.
user4815162342

318

Про це йшлося в методах String ... нарешті, нитка в архіві Python-Dev і була прийнята Гвідо. Цей потік розпочався в червні 1999 року і str.joinбув включений в Python 1.6, який був випущений у вересні 2000 року (і підтримується Unicode). Python 2.0 ( strвключаючи підтримувані методи join) був випущений у жовтні 2000 року.

  • У цій темі запропоновано чотири варіанти:
    • str.join(seq)
    • seq.join(str)
    • seq.reduce(str)
    • join як вбудована функція
  • Гвідо хотів підтримувати не тільки lists, tuples, але й усі послідовності / ітерабелі.
  • seq.reduce(str) новачкам важко.
  • seq.join(str) вводить несподівану залежність від послідовностей до str / unicode.
  • join()як вбудована функція підтримувала б лише конкретні типи даних. Тому використовувати вбудований простір імен - це не добре. Якщо join()підтримується багато типів даних, створити оптимізовану реалізацію буде складно, якщо реалізувати за допомогою __add__методу, то це O (n²).
  • Рядок розділювача ( sep) не слід опускати. Явне краще, ніж неявне.

Немає інших причин, пропонованих у цій темі.

Ось кілька додаткових думок (мої власні та мої друзі):

  • Підтримка Unicode надходила, але вона не була остаточною. У той час UTF-8, швидше за все, збирався замінити UCS2 / 4. Для обчислення загальної довжини буфера рядків UTF-8 необхідно знати правило кодування символів.
  • У той час Python вже зважився на загальне правило інтерфейсу послідовностей, де користувач може створити подібний до послідовностей (ітерабельний) клас. Але Python не підтримував розширення вбудованих типів до 2.2. На той час було важко забезпечити базовий ітерабельний клас (про який йдеться в іншому коментарі).

Рішення Гідо записується в історичну пошту , приймаючи рішення про str.join(seq):

Смішно, але це здається правильно! Баррі, піди на це ... -
Гуйдо ван Россум


251

Тому що join()метод знаходиться в рядковому класі замість класу list?

Я згоден, це виглядає смішно.

Дивіться http://www.faqs.org/docs/diveintopython/odbchelper_join.html :

Історична записка.Коли я вперше дізнався Python, я очікував, що приєднання буде методом списку, який буде брати роздільник як аргумент. Багато людей почуваються так само, і за методом приєднання стоїть історія. До Python 1.6, рядки не мали всіх цих корисних методів. Існував окремий рядковий модуль, який містив усі функції рядка; кожна функція приймала рядок як свій перший аргумент. Функції вважалися достатньо важливими для розміщення на самих рядках, що мало сенс для таких функцій, як нижня, верхня та роздвоєна. Але багато жорстоких програмістів Python заперечували проти нового методу приєднання, стверджуючи, що він повинен бути методом списку, або що він взагалі не повинен рухатися, а просто залишатися частиною старого рядкового модуля (у якого ще багато корисних речей у ньому).

--- Марк Пілігрим, занурись у Пітон


12
Бібліотека Python 3 stringвидалила всі зайві strметоди, тому ви більше не можете користуватися string.join(). Особисто я ніколи не думав, що це "смішно", це має ідеальний сенс, оскільки ви можете приєднатись набагато більше, ніж просто списки, але столяр - це завжди рядок!
Martijn Pieters

67

Я погоджуюся, що спочатку це контрінтуїтивно, але є вагомі причини. Приєднання не може бути методом списку, оскільки:

  • він також повинен працювати для різних ітерабелів (кортежі, генератори тощо)
  • він повинен мати різну поведінку між різними типами струн.

Насправді є два способи приєднання (Python 3.0):

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

Якщо приєднання було методом списку, тоді доведеться перевірити його аргументи, щоб вирішити, кого з них викликати. І ви не можете об'єднати байт і str разом, тому спосіб, який вони мають зараз, має сенс.


45

Чому це string.join(list)замість list.join(string)?

Це тому join, що це "рядковий" метод! Він створює рядок з будь-якого ітерабельного. Якщо ми закріпили метод у списках, що робити, коли у нас є ітерабелі, які не є списками?

Що робити, якщо у вас є кортеж струн? Якби це listметод, вам слід було б передати кожен такий ітератор рядків як a, listперш ніж ви зможете з'єднати елементи в єдиний рядок! Наприклад:

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

Розгорнемо власний метод приєднання до списку:

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

І щоб скористатись цим, зауважте, що ми повинні спершу створити список з кожного ітерабельного, щоб приєднати рядки до цього ітерабельного, витрачаючи і пам'ять, і потужність обробки:

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

Отже, ми бачимо, що ми повинні додати додатковий крок для використання нашого списку, а не просто вбудованого рядкового методу:

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

Продуктивність Caveat для генераторів

Алгоритм, який Python використовує для створення остаточного рядка, str.joinфактично повинен передати ітерабельний двічі, тож якщо ви надаєте йому вираз генератора, він повинен спочатку його матеріалізувати до списку, перш ніж він зможе створити остаточний рядок.

Таким чином, проходження генераторів, як правило, краще, ніж розуміння списку, str.joinє винятком:

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

Тим не менш, str.joinоперація все ще семантично є "рядковою" операцією, тому все одно має сенс мати її на strоб'єкті, ніж на різних ітерабелях.


24

Розгляньте це як природну ортогональну операцію з розколу.

Я розумію, чому це застосовно до всього, що можна змінити, і тому його не можна легко реалізувати лише у списку.

Для читабельності я хотів би бачити це мовою, але не думаю, що це реально реально - якщо ітерабельність була інтерфейсом, то її можна було б додати до інтерфейсу, але це лише умова, і тому немає центрального способу додайте його до набору речей, які є ітерабельними.


13

Передусім тому, що результат а someString.join()є рядком.

Послідовність (список, кортеж чи будь-що інше) не з’являється в результаті, а лише рядок. Оскільки результат - рядок, він має сенс як метод рядка.


10

- в "-". join (my_list) заявляє, що ви перетворюєтесь на рядок із приєднання елементів до списку. Орієнтований на результат. (просто для зручної пам'яті та розуміння)

Я роблю вичерпну схему методів_of_string для вашої довідки.

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

І те й інше не приємно.

string.join (xs, розмежувати) означає, що рядковий модуль знає про існування списку, про який він не знає діла, оскільки модуль рядка працює лише з рядками.

list.join (розмежувати) трохи приємніше, тому що ми так звикли, що струни є основним типом (і в мовному плані вони є). Однак це означає, що приєднання потрібно динамічно відправляти, оскільки в довільному контексті a.split("\n")компілятор python може не знати, що таке a, і йому потрібно буде шукати його (аналогічно пошуку Vtable), що дорого, якщо ви робите це багато разів.

якщо компілятор виконання пітону знає, що список є вбудованим модулем, він може пропустити динамічний пошук і кодувати наміри безпосередньо в байт-код, тоді як в іншому випадку йому потрібно динамічно вирішити "приєднання" "а", яке може бути до декількох шарів спадкування за викликом (оскільки між дзвінками значення приєднання може змінитися, тому що python - це динамічна мова).

на жаль, це головна вада абстракції; незалежно від того, яку абстракцію ви виберете, ваша абстракція матиме сенс лише в контексті проблеми, яку ви намагаєтеся вирішити, і як така ви ніколи не зможете мати послідовну абстракцію, яка не буде суперечити основним ідеологіям, коли ви почнете їх склеювати. разом, не загортаючи їх у погляд, який відповідає вашій ідеології. Знаючи це, підхід python є більш гнучким, оскільки він дешевший, вам належить заплатити більше, щоб він виглядав "приємніше", або виготовивши власну обгортку, або власний препроцесор.


0

Змінні my_listі "-"є обома об'єктами. Зокрема, це екземпляри класів listі str, відповідно. joinФункція належить до класу str. Тому синтаксис "-".join(my_list)використовується тому, що об'єкт "-"береться my_listза вхід.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.