Вопрос: Python join: почему это string.join (list) вместо list.join (string)?


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

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

Чем это:

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

Есть ли какая-то конкретная причина?


1351


источник


Ответы:


Это связано с тем, что любой итеративный может быть объединен, а не только списки, но результат и «столяр» всегда являются строками.

НАПРИМЕР:

import urllib2
print '\n############\n'.join(
    urllib2.urlopen('http://data.stackexchange.com/users/7095'))

972



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

Я согласен, что это выглядит забавно.

Видеть http://www.faqs.org/docs/diveintopython/odbchelper_join.html :

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

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


221



Это обсуждалось в Строковые методы ... наконец нить в 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, но все последовательности / iterables.
  • 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):

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


204



I agree that it's counterintuitive at first, but there's a good reason. Join can't be a method of a list because:

  • it must work for different iterables too (tuples, generators, etc.)
  • it must have different behavior between different types of strings.

There are actually two join methods (Python 3.0):

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

If join was a method of a list, then it would have to inspect its arguments to decide which one of them to call. And you can't join byte and str together, so the way they have it now makes sense.


56



Why is it string.join(list) instead of list.join(string)?

This is because join is a "string" method! It creates a string from any iterable. If we stuck the method on lists, what about when we have iterables that aren't lists?

What if you have a tuple of strings? If this were a list method, you would have to cast every such iterator of strings as a list before you could join the elements into a single string! For example:

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

Let's roll our own list join method:

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

And to use it, note that we have to first create a list from each iterable to join the strings in that iterable, wasting both memory and processing power:

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

So we see we have to add an extra step to use our list method, instead of just using the builtin string method:

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

Performance Caveat for Generators

The algorithm Python uses to create the final string with str.join actually has to pass over the iterable twice, so if you provide it a generator expression, it has to materialize it into a list first before it can create the final string.

Thus, while passing around generators is usually better than list comprehensions, str.join is an exception:

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

Nevertheless, the str.join operation is still semantically a "string" operation, so it still makes sense to have it on the str object than on miscellaneous iterables.


34



Think of it as the natural orthogonal operation to split.

I understand why it is applicable to anything iterable and so can't easily be implemented just on list.

For readability, I'd like to see it in the language but I don't think that is actually feasible - if iterability were an interface then it could be added to the interface but it is just a convention and so there's no central way to add it to the set of things which are iterable.


21



Primarily because the result of a someString.join() is a string.

The sequence (list or tuple or whatever) doesn't appear in the result, just a string. Because the result is a string, it makes sense as a method of a string.


11



- declare that you are joining a list and converting to a string.It's result oriented.(just for easy memory and understanding)

I make a exhaustive cheatsheet of methods_of_string for your reference.

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']}

0



Both are not nice.

string.join(xs, delimit) means that the string module is aware of the existence of a list, which it has no business knowing about, since the string module only works with strings.

list.join(delimit) is a bit nicer because we're so used to strings being a fundamental type(and lingually speaking, they are). However this means that join needs to be dispatched dynamically because in the arbitrary context of a.split("\n") the python compiler might not know what a is, and will need to look it up(analogously to vtable lookup), which is expensive if you do it a lot of times.

if the python runtime compiler knows that list is a built in module, it can skip the dynamic lookup and encode the intent into the bytecode directly, whereas otherwise it needs to dynamically resolve "join" of "a", which may be up several layers of inheritence per call(since between calls, the meaning of join may have changed, because python is a dynamic language).

sadly, this is the ultimate flaw of abstraction; no matter what abstraction you choose, your abstraction will only make sense in the context of the problem you're trying to solve, and as such you can never have a consistent abstraction that doesn't become inconsistent with underlying ideologies as you start gluing them together without wrapping them in a view that is consistent with your ideology. Knowing this, python's approach is more flexible since it's cheaper, it's up to you to pay more to make it look "nicer", either by making your own wrapper, or your own preprocessor.


0