Por que é string.join (lista) em vez de list.join (string)?

1762

Isso sempre me confundiu. Parece que isso seria melhor:

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

Do que isso:

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

Existe uma razão específica para ser assim?

Evan Fosmark
fonte
1
Para facilitar a memória e o entendimento, -declara que você está ingressando em uma lista e convertendo em uma string.
Cálculo
11
@JawSaw: Isso apenas confunde mais mem.
einpoklum
34
Penso que a resposta curta é que o sistema de tipos do Python não é suficientemente forte e foi mais fácil implementar essa funcionalidade uma vez do strque implementá-la em todos os tipos iteráveis.
usar o seguinte
3
Eu acho que a ideia original é que, porque join () retorna uma string, ela deveria ser chamada do contexto da string. Colocar join () em uma lista não faz muito sentido, pois uma lista é um contêiner de objetos e não deve ter uma função única específica apenas para seqüências de caracteres.
Joshua Burns

Respostas:

1248

É porque qualquer iterável pode ser associado (por exemplo, lista, tupla, ditado, conjunto), mas o resultado e o "marceneiro" devem ser cadeias de caracteres.

Por exemplo:

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

Usar algo diferente de strings gerará o seguinte erro:

TypeError: item de sequência 0: instância str esperada, int encontrado

recursivo
fonte
57
Não concordo conceitualmente, mesmo que faça sentido em termos de código. list.join(string)parece mais uma abordagem orientada a objetos, enquanto me string.join(list)parece muito mais processual.
Eduardo Pignatelli 14/01
22
Então, por que não é implementado em iterável?
Steen Schütt
10
@TimeSheep: Uma lista de números inteiros não possui uma associação significativa, mesmo que seja iterável.
recursivo
16
Eu tentei usar print(str.join('-', my_list))e funciona, se sente melhor.
Pimgeek
13
@TimeSheep Como iterável não é um tipo concreto, iterável é uma interface, qualquer tipo que define um __iter__método. Exigir que todos os iterables também implementem joincomplicaria uma interface geral (que também abrange iterables sobre não-strings) para um caso de uso muito particular. A definição joinde strins evita esse problema ao custo da ordem "não intuitiva". Uma escolha melhor poderia ter sido manter uma função, com o primeiro argumento sendo iterável e o segundo (opcional) sendo a sequência de marcadores - mas esse navio navegou.
user4815162342
319

Isso foi discutido nos métodos String ... finalmente encadeados no Python-Dev achive, e foi aceito por Guido. Esse encadeamento começou em junho de 1999 e str.joinfoi incluído no Python 1.6, lançado em setembro de 2000 (e com suporte a Unicode). O Python 2.0 ( strincluindo métodos suportados join) foi lançado em outubro de 2000.

  • Havia quatro opções propostas neste segmento:
    • str.join(seq)
    • seq.join(str)
    • seq.reduce(str)
    • join como uma função embutida
  • Guido queria apoiar não apenas lists, tuples, mas todas as seqüências / iteráveis.
  • seq.reduce(str) é difícil para os novatos.
  • seq.join(str) introduz dependência inesperada de seqüências para str / unicode.
  • join()como uma função interna suportaria apenas tipos de dados específicos. Portanto, usar um espaço para nome interno não é bom. Se join()suportar muitos tipos de dados, seria difícil criar uma implementação otimizada, se implementada usando o método__add__ método, será O (n²).
  • A sequência separadora ( sep) não deve ser omitida. Explícito é melhor que implícito.

Não há outros motivos oferecidos neste segmento.

Aqui estão alguns pensamentos adicionais (meus e dos meus amigos):

  • O suporte a Unicode estava chegando, mas não era final. Naquela época, o UTF-8 era o mais provável para substituir o UCS2 / 4. Para calcular o comprimento total do buffer de cadeias UTF-8, é necessário conhecer a regra de codificação de caracteres.
  • Naquela época, o Python já havia decidido por uma regra comum de interface de sequência em que um usuário poderia criar uma classe (iterável) do tipo sequência. Mas o Python não suportava a extensão de tipos internos até a versão 2.2. Naquela época, era difícil fornecer classe iterável básica (mencionada em outro comentário).

A decisão de Guido é registrada em um correio histórico , decidindo str.join(seq):

Engraçado, mas parece certo! Barry, vá em frente ...
--Guido van Rossum

Yoshiki Shibukawa
fonte
251

Como o join()método está na classe string, em vez da classe list?

Eu concordo que parece engraçado.

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

Nota histórica.Quando eu aprendi Python pela primeira vez, esperava que o join fosse um método de lista, que levaria o delimitador como argumento. Muitas pessoas se sentem da mesma maneira, e há uma história por trás do método de junção. Antes do Python 1.6, as strings não tinham todos esses métodos úteis. Havia um módulo de string separado que continha todas as funções de string; cada função recebeu uma string como seu primeiro argumento. As funções foram consideradas importantes o suficiente para serem colocadas nas próprias strings, o que fazia sentido para funções como inferior, superior e dividida. Mas muitos programadores de Python do núcleo duro se opuseram ao novo método de junção, argumentando que deveria ser um método da lista ou que não deveria se mover, mas simplesmente permanecer parte do antigo módulo de strings (que ainda tem muito de coisas úteis nele).

--- Mark Pilgrim, Mergulhe no Python

Bill Karwin
fonte
12
A stringbiblioteca Python 3 removeu todos os strmétodos redundantes , para que você não possa mais usá-lo string.join(). Pessoalmente, nunca achei "engraçado", faz todo o sentido, pois você pode participar de muito mais do que apenas listas, mas o marceneiro é sempre uma string!
Martijn Pieters
67

Concordo que é contra-intuitivo no começo, mas há uma boa razão. A junção não pode ser um método de lista porque:

  • também deve funcionar para iterables diferentes (tuplas, geradores etc.)
  • deve ter um comportamento diferente entre os diferentes tipos de cadeias.

Na verdade, existem dois métodos de junção (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 a junção fosse o método de uma lista, ele teria que inspecionar seus argumentos para decidir qual deles chamar. E você não pode unir byte e str, então o modo como eles o fazem agora faz sentido.

Kiv
fonte
45

Por que é isso em string.join(list)vez de list.join(string)?

Isso ocorre porque joiné um método "string"! Ele cria uma string a partir de qualquer iterável. Se colocamos o método nas listas, e quando temos iterables que não são listas?

E se você tiver uma tupla de strings? Se esse fosse um listmétodo, você teria que converter todos os iteradores de strings como a listantes de poder juntar os elementos em uma única string! Por exemplo:

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

Vamos lançar nosso próprio método de junção de lista:

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

E, para usá-lo, observe que precisamos primeiro criar uma lista de cada iterável para unir as strings nessa iterável, desperdiçando tanto a memória quanto o poder de processamento:

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

Então, vemos que precisamos adicionar uma etapa extra para usar nosso método de lista, em vez de apenas usar o método de string incorporado:

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

Advertência de desempenho para geradores

O algoritmo que o Python usa para criar a sequência final str.joinprecisa passar pela iterável duas vezes; portanto, se você fornecer uma expressão de gerador, precisará materializá-la em uma lista antes de poder criar a sequência final.

Assim, embora distribuir geradores geralmente seja melhor do que a compreensão de lista, str.joiné uma exceção:

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

No entanto, a str.joinoperação ainda é semanticamente uma operação "string", por isso ainda faz sentido tê-la no strobjeto do que em iterables diversos.

Aaron Hall
fonte
24

Pense nisso como a operação ortogonal natural a ser dividida.

Entendo por que é aplicável a qualquer coisa iterável e, portanto, não pode ser facilmente implementado apenas na lista.

Para facilitar a leitura, eu gostaria de vê-lo na linguagem, mas não acho que seja realmente viável - se a iterabilidade fosse uma interface, ela poderia ser adicionada à interface, mas é apenas uma convenção e, portanto, não há uma maneira central de adicione-o ao conjunto de coisas que são iteráveis.

Andy Dent
fonte
13

Principalmente porque o resultado de a someString.join()é uma string.

A sequência (lista ou tupla ou o que for) não aparece no resultado, apenas uma sequência. Como o resultado é uma sequência, faz sentido como método de uma sequência.

S.Lott
fonte
10

- join "-". join (my_list) declara que você está convertendo para uma string de unir elementos a uma lista. É orientado a resultados. (apenas para facilitar a memória e o entendimento)

Eu faço uma cheatsheet exaustiva de methods_of_string para sua referência.

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']}
Cálculo
fonte
3

Ambos não são bons.

string.join (xs, delimit) significa que o módulo de string está ciente da existência de uma lista, da qual não tem negócios, pois o módulo de string funciona apenas com strings.

list.join (delimit) é um pouco melhor porque estamos acostumados a que as strings sejam um tipo fundamental (e lingualmente falando, elas são). Entretanto, isso significa que a junção precisa ser despachada dinamicamente, porque no contexto arbitrário dea.split("\n") compilador python, talvez você não saiba o que é, e precisará procurá-la (analogicamente à pesquisa em vtable), o que é caro se você o fizer muitas vezes. vezes.

se o compilador de tempo de execução python souber que a lista é um módulo interno, poderá ignorar a pesquisa dinâmica e codificar a intenção diretamente no bytecode, enquanto, caso contrário, precisará resolver dinamicamente a "junção" de "a", que pode estar em várias camadas de herança por chamada (já que, entre as chamadas, o significado de associação pode ter mudado, porque python é uma linguagem dinâmica).

infelizmente, essa é a derradeira falha da abstração; não importa qual abstração você escolher, sua abstração só fará sentido no contexto do problema que você está tentando resolver e, como tal, você nunca poderá ter uma abstração consistente que não se torne inconsistente com as ideologias subjacentes quando começar a colá-las. juntos, sem agrupá-los em uma visão que seja consistente com sua ideologia. Sabendo disso, a abordagem do python é mais flexível, uma vez que é mais barato, cabe a você pagar mais para torná-la "mais agradável", criando seu próprio wrapper ou seu próprio pré-processador.

Dmitry
fonte
0

As variáveis my_liste "-"são ambos objetos. Especificamente, são instâncias das classes liste str, respectivamente. A joinfunção pertence à classe str. Portanto, a sintaxe "-".join(my_list)é usada porque o objeto "-"está sendo usado my_listcomo entrada.

fiftytwocards
fonte