¿Por qué es string.join (list) en lugar de list.join (string)?

1762

Esto siempre me ha confundido. Parece que esto sería mejor:

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

Que esto:

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

¿Hay alguna razón específica por la que es así?

Evan Fosmark
fuente
1
Para facilitar la memoria y la comprensión, -declara que se está uniendo a una lista y convirtiendo a una cadena. Está orientada a resultados.
Cálculo
11
@JawSaw: Eso solo confunde a mem más.
einpoklum
34
Creo que la respuesta breve es que es porque el sistema de tipos de Python no es lo suficientemente fuerte, y fue más fácil implementar esta funcionalidad una vez strque implementarla en cada tipo iterable.
BallpointBen
3
Creo que la idea original es que, dado que join () devuelve una cadena, debería llamarse desde el contexto de la cadena. Poner join () en una lista no tiene mucho sentido en el sentido de que una lista es un contenedor de objetos y no debería tener una función única específica solo para cadenas.
Joshua Burns

Respuestas:

1248

Se debe a que se puede unir cualquier iterable (por ejemplo, list, tuple, dict, set), pero el resultado y la "unión" deben ser cadenas.

Por ejemplo:

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

Usar algo más que cadenas provocará el siguiente error:

TypeError: elemento de secuencia 0: instancia str esperada, int encontrado

recursivo
fuente
57
No estoy de acuerdo conceptualmente, incluso si tiene sentido en código. list.join(string)Parece más un enfoque orientado a objetos, mientras que me string.join(list)parece mucho más procedimental.
Eduardo Pignatelli
22
Entonces, ¿por qué no se implementa en iterable?
Steen Schütt
10
@TimeSheep: una lista de enteros no tiene una unión significativa, aunque sea iterable.
recursivo
16
He intentado usar print(str.join('-', my_list))y funciona, se siente mejor.
pimgeek
13
@TimeSheep Debido a que iterable no es un tipo concreto, iterable es una interfaz, cualquier tipo que defina un __iter__método. Requerir que todos los iterables también se implementen joincomplicaría una interfaz general (que también cubre iterables sobre no cadenas) para un caso de uso muy particular. La definición joinde los pasos secundarios de este problema a costa de la orden "no intuitiva". Una mejor opción podría haber sido mantener una función con el primer argumento como iterable y el segundo (opcional) como la cadena de unión, pero ese barco ha navegado.
user4815162342
319

Esto se discutió en los métodos de String ... finalmente el hilo en Python-Dev se logró, y fue aceptado por Guido. Este hilo comenzó en junio de 1999 y str.joinse incluyó en Python 1.6, que se lanzó en septiembre de 2000 (y era compatible con Unicode). Python 2.0 ( strmétodos compatibles incluidos join) se lanzó en octubre de 2000.

  • Había cuatro opciones propuestas en este hilo:
    • str.join(seq)
    • seq.join(str)
    • seq.reduce(str)
    • join como una función incorporada
  • Guido quería soportar no solo lists, tuples, sino todas las secuencias / iterables.
  • seq.reduce(str) Es difícil para los recién llegados.
  • seq.join(str) introduce dependencia inesperada de secuencias a str / unicode.
  • join()como función incorporada solo admitiría tipos de datos específicos. Por lo tanto, usar un espacio de nombres incorporado no es bueno. Si join()admite muchos tipos de datos, crear una implementación optimizada sería difícil, si se implementa utilizando el __add__método, entonces es O (n²).
  • La cadena de separación ( sep) no debe omitirse. Explícito es mejor que implícito.

No hay otras razones ofrecidas en este hilo.

Aquí hay algunos pensamientos adicionales (los míos y los de mi amigo):

  • El soporte Unicode se acercaba, pero no era definitivo. En ese momento, UTF-8 era el más probable para reemplazar UCS2 / 4. Para calcular la longitud total del búfer de las cadenas UTF-8, necesita conocer la regla de codificación de caracteres.
  • En ese momento, Python ya había decidido una regla de interfaz de secuencia común donde un usuario podía crear una clase de secuencia (iterable). Pero Python no admitió extender los tipos integrados hasta 2.2. En ese momento, era difícil proporcionar una clase iterable básica (que se menciona en otro comentario).

La decisión de Guido se registra en un correo histórico , decidiendo sobrestr.join(seq) :

Divertido, pero parece correcto! Barry, adelante ...
Guido van Rossum

Yoshiki Shibukawa
fuente
251

Porque el join() método está en la clase de cadena, en lugar de la clase de lista?

Estoy de acuerdo en que se ve divertido.

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

Nota historica.Cuando aprendí Python por primera vez, esperaba que join fuera un método de una lista, que tomaría el delimitador como argumento. Mucha gente siente lo mismo, y hay una historia detrás del método de combinación. Antes de Python 1.6, las cadenas no tenían todos estos métodos útiles. Había un módulo de cadena separado que contenía todas las funciones de cadena; cada función tomó una cadena como primer argumento. Las funciones se consideraron lo suficientemente importantes como para colocarlas en las propias cadenas, lo que tenía sentido para funciones como inferior, superior y división. Pero muchos programadores de Python se opusieron al nuevo método de unión, argumentando que debería ser un método de la lista, o que no debería moverse en absoluto, sino simplemente permanecer como parte del antiguo módulo de cadena (que todavía tiene muchos de cosas útiles en él).

--- Mark Pilgrim, Sumérgete en Python

Bill Karwin
fuente
12
La stringbiblioteca Python 3 ha eliminado todos los strmétodos redundantes , por lo que ya no puede usarlos string.join(). Personalmente, nunca pensé que fuera 'gracioso', tiene mucho sentido, ya que puedes unirte a mucho más que solo listas, ¡pero el carpintero siempre es una cadena!
Martijn Pieters
67

Estoy de acuerdo en que es contradictorio al principio, pero hay una buena razón. Unirse no puede ser un método de una lista porque:

  • también debe funcionar para diferentes iterables (tuplas, generadores, etc.)
  • debe tener un comportamiento diferente entre los diferentes tipos de cadenas.

En realidad, hay dos métodos de unión (Python 3.0):

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

Si unirse era un método de una lista, entonces tendría que inspeccionar sus argumentos para decidir a cuál de ellos llamar. Y no puedes unir byte y unir, por lo que la forma en que lo tienen ahora tiene sentido.

Kiv
fuente
45

¿Por qué es en string.join(list)lugar de list.join(string)?

¡Esto se debe a que joines un método de "cadena"! Crea una cadena de cualquier iterable. Si atascamos el método en las listas, ¿qué pasa cuando tenemos iterables que no son listas?

¿Qué pasa si tienes una tupla de cuerdas? Si este fuera un listmétodo, ¡tendría que convertir cada iterador de cadenas como listantes para poder unir los elementos en una sola cadena! Por ejemplo:

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

Vamos a rodar nuestro propio método de unión de lista:

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

Y para usarlo, tenga en cuenta que primero tenemos que crear una lista de cada iterable para unir las cadenas en ese iterable, desperdiciando memoria y poder de procesamiento:

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

Entonces vemos que tenemos que agregar un paso adicional para usar nuestro método de lista, en lugar de solo usar el método de cadena incorporado:

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

Advertencia de rendimiento para generadores

El algoritmo que Python usa para crear la cadena final con str.join realmente tiene que pasar dos veces sobre el iterable, por lo que si le proporciona una expresión generadora, primero debe materializarlo en una lista antes de que pueda crear la cadena final.

Por lo tanto, aunque pasar generadores suele ser mejor que las listas de comprensión, str.joines una excepción:

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

Sin embargo, la str.joinoperación sigue siendo semánticamente una operación de "cadena", por lo que aún tiene sentido tenerla en el strobjeto que en varios iterables.

Aaron Hall
fuente
24

Piense en ello como la operación ortogonal natural para dividir.

Entiendo por qué es aplicable a cualquier cosa iterable y, por lo tanto, no se puede implementar fácilmente solo en la lista.

Para facilitar la lectura, me gustaría verlo en el lenguaje, pero no creo que sea realmente factible: si la iterabilidad fuera una interfaz, podría agregarse a la interfaz, pero es solo una convención y, por lo tanto, no hay una forma central de agréguelo al conjunto de cosas que son iterables.

Andy Dent
fuente
13

Principalmente porque el resultado de a someString.join()es una cadena.

La secuencia (lista o tupla o lo que sea) no aparece en el resultado, solo una cadena. Como el resultado es una cadena, tiene sentido como método de una cadena.

S.Lott
fuente
10

- en "-". join (my_list) declara que está convirtiendo a una cadena de elementos de unión en una lista. Está orientado a resultados. (solo para facilitar la memoria y la comprensión)

Hago una exhaustiva hoja de trucos de métodos_de_cadena para su referencia.

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

Ambos no son agradables.

string.join (xs, delimit) significa que el módulo de cadena es consciente de la existencia de una lista, de la cual no tiene ningún conocimiento, ya que el módulo de cadena solo funciona con cadenas.

list.join (delimit) es un poco mejor porque estamos tan acostumbrados a que las cadenas sean un tipo fundamental (y lingüísticamente hablando, lo son). Sin embargo, esto significa que la unión debe enviarse dinámicamente porque en el contexto arbitrario dea.split("\n") compilador de Python, es posible que no sepa qué es y tendrá que buscarlo (de forma análoga a la búsqueda de Vtable), lo cual es costoso si lo hace mucho veces.

si el compilador de tiempo de ejecución de Python sabe que la lista es un módulo integrado, puede omitir la búsqueda dinámica y codificar la intención en el código de bytes directamente, mientras que de lo contrario debe resolver dinámicamente la "unión" de "a", que puede estar en varias capas de herencia por llamada (ya que entre llamadas, el significado de unirse puede haber cambiado, porque python es un lenguaje dinámico).

lamentablemente, este es el último defecto de la abstracción; No importa qué abstracción elija, su abstracción solo tendrá sentido en el contexto del problema que está tratando de resolver, y como tal nunca podrá tener una abstracción consistente que no se vuelva inconsistente con las ideologías subyacentes a medida que comience a pegarlas. juntos sin envolverlos en una vista que sea consistente con su ideología. Sabiendo esto, el enfoque de Python es más flexible ya que es más barato, depende de usted pagar más para que se vea "más agradable", ya sea haciendo su propio envoltorio o su propio preprocesador.

Dmitry
fuente
0

Las variables my_listy "-"son ambos objetos. Específicamente, son instancias de las clases listy str, respectivamente. La joinfunción pertenece a la clase str. Por lo tanto, la sintaxis "-".join(my_list)se usa porque el objeto "-"se toma my_listcomo entrada.

cincuenta y dos tarjetas
fuente