Почему это string.join (список) вместо list.join (строка)?

1763

Это всегда смущало меня. Кажется, что это будет лучше

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
34
Я думаю, что короткий ответ заключается в том, что это потому, что система типов Python недостаточно сильна, и было проще реализовать эту функцию один раз, strчем реализовать ее для каждого итерируемого типа.
BallpointBen
3
Я думаю, что оригинальная идея заключается в том, что поскольку join () возвращает строку, она должна вызываться из контекста строки. Размещение в списке join () не имеет большого смысла, так как список является контейнером объектов и не должен иметь одноразовой функции, специфичной только для строк.
Джошуа Бернс

Ответы:

1248

Это потому, что любая итерация может быть объединена (например, list, tuple, dict, set), но результат и "joiner" должны быть строками.

Например:

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

Использование чего-то другого, кроме строк, вызовет следующую ошибку:

Ошибка типа: элемент последовательности 0: ожидаемый экземпляр str, int найдено

рекурсивный
источник
57
Я не согласен концептуально, даже если это имеет смысл в коде. list.join(string)кажется более объектно-ориентированным подходом, тогда как string.join(list)звучит для меня гораздо более процедурно.
Эдуардо Пиньятелли
22
Так почему же он не реализован на итерируемом?
Стин Шютт,
10
@TimeSheep: список целых чисел не имеет осмысленного объединения, хотя оно итеративное.
рекурсивный
16
Я пытался использовать, print(str.join('-', my_list))и это работает, чувствует себя лучше.
Pimgeek
13
@TimeSheep Поскольку iterable не является конкретным типом, iterable является интерфейсом, любым типом, который определяет __iter__метод. Требование также реализовать все итерируемые объекты joinусложнит общий интерфейс (который также охватывает итерируемые элементы, не относящиеся к строкам) для очень конкретного случая использования. Определяем joinна побочных шагах эту проблему ценой «неинтуитивного» порядка. Лучшим выбором могло бы быть сохранение функции как функции с первым итеративным аргументом, а вторым (необязательным) как строкой соединения - но этот корабль уже прошел.
user4815162342
319

Это обсуждалось в методах String ... наконец-то в Python-Dev achive, и было принято Гвидо. Эта тема началась в июне 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²).
  • Разделитель string ( sep) не должен быть опущен. Явное лучше, чем неявное.

В этой теме нет других причин.

Вот некоторые дополнительные мысли (мои собственные и моего друга):

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

Решение Гвидо записывается в историческом письме , решая str.join(seq):

Забавно, но это кажется правильным! Барри,
дерзай ... - Гидо ван Россум

Йошики Сибукава
источник
251

Потому что join()метод находится в строковом классе, а не в списке класса?

Я согласен, это выглядит смешно.

См. Http://www.faqs.org/docs/diveintopython/odbchelper_join.html :

Историческая справка.Когда я впервые изучил Python, я ожидал, что join будет методом списка, который будет принимать разделитель в качестве аргумента. Многие люди чувствуют то же самое, и за методом объединения есть история. До Python 1.6 у строк не было всех этих полезных методов. Был отдельный строковый модуль, который содержал все строковые функции; каждая функция взяла строку в качестве первого аргумента. Функции считались достаточно важными, чтобы помещать их в сами строки, что имело смысл для таких функций, как lower, upper и split. Но многие злостные программисты на Python возражали против нового метода соединения, утверждая, что он должен быть методом списка, или что он вообще не должен двигаться, а просто оставаться частью старого строкового модуля (в котором все еще много полезных вещей в нем).

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

Билл Карвин
источник
12
Библиотека Python 3 stringудалила все избыточные strметоды, поэтому вы больше не можете их использовать string.join(). Лично я никогда не думал, что это «смешно», это имеет смысл, так как вы можете присоединиться к гораздо большему, чем просто списки, но присоединение - это всегда строка!
Мартин Питерс
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 вместе, поэтому то, что у них есть, теперь имеет смысл.

Kiv
источник
45

Почему это string.join(list)вместо list.join(string)?

Это потому, что joinэто «строковый» метод! Создает строку из любого итератора. Если мы поместим метод в списки, что делать, когда у нас есть итерации, которые не являются списками?

Что делать, если у вас есть набор строк? Если бы это был listметод, вам пришлось бы приводить каждый такой итератор строк как 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'

Предупреждение о производительности для генераторов

Алгоритм, который 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) объявляет, что вы конвертируете в строку из соединяемых элементов список. Он ориентирован на результат (просто для удобства памяти и понимания)

Я делаю исчерпывающую таблицу для Methods_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, delimit) означает, что строковый модуль знает о существовании списка, о котором он не знает, поскольку строковый модуль работает только со строками.

list.join (delimit) немного лучше, потому что мы так привыкли к тому, что строки являются фундаментальным типом (и, говоря языком, они есть). Однако это означает, что join должен отправляться динамически, потому что в произвольном контексте a.split("\n")компилятор python может не знать, что такое a, и должен будет искать его (аналогично vtable lookup), что дорого, если вы делаете это много раз.

если компилятор времени выполнения Python знает, что список является встроенным модулем, он может пропустить динамический поиск и напрямую зашифровать намерение в байт-код, тогда как в противном случае ему необходимо динамически разрешить «соединение» из «а», которое может занимать несколько уровней наследования за вызов (поскольку между вызовами значение соединения могло измениться, потому что python является динамическим языком).

к сожалению, это абсолютный недостаток абстракции; Независимо от того, какую абстракцию вы выберете, ваша абстракция будет иметь смысл только в контексте проблемы, которую вы пытаетесь решить, и поэтому вы никогда не сможете получить последовательную абстракцию, которая не станет несовместимой с основными идеологиями, когда вы начнете склеивать их. вместе, не оборачивая их в соответствии с вашей идеологией. Зная это, подход python более гибок, поскольку он дешевле, и вам нужно платить больше, чтобы он выглядел «лучше», либо создавая свою собственную обертку, либо свой собственный препроцессор.

Дмитрий
источник
0

Переменные my_listи "-"являются объектами. В частности, они являются экземплярами классов listи str, соответственно. joinФункция принадлежит к классу str. Таким образом, синтаксис "-".join(my_list)используется, потому что объект "-"принимает my_listв качестве входных данных.

fiftytwocards
источник