Python join: ¿por qué es string.join (list) en lugar de list.join (string)?

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 sea así?

Esto se debe a que se puede unir a cualquier iterable, no solo a las listas, sino que el resultado y el “ensamblador” siempre son cadenas.

P.EJ:

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

Esto fue discutido en los métodos de Cadena … finalmente, el hilo en el progtwig Python-Dev, y fue aceptado por Guido. Este hilo comenzó en junio de 1999, y str.join se incluyó en Python 1.6, que se lanzó en septiembre de 2000 (y era compatible con Unicode). Python 2.0 (métodos str compatibles, incluida la join ) se lanzó en octubre de 2000.

  • Hubo cuatro opciones propuestas en este hilo:
    • str.join(seq)
    • seq.join(str)
    • seq.reduce(str)
    • join como una función incorporada
  • Guido quería apoyar no solo la list s, tuple s, sino todas las secuencias / iterables.
  • seq.reduce(str) es difícil para los recién llegados.
  • seq.join(str) introduce una dependencia inesperada de las secuencias a str / unicode.
  • join() como una función incorporada soportaría solo tipos de datos específicos. Así que usar un espacio de nombres integrado no es bueno. Si join() admite muchos tipos de datos, sería difícil crear una implementación optimizada. Si se implementa con el método __add__ , entonces es O (n²).
  • La cadena separadora ( 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 de Unicode venía, pero no fue definitivo. En ese momento, lo más probable era que UTF-8 reemplazara a UCS2 / 4. Para calcular la longitud total del búfer de las cadenas UTF-8 necesita conocer la regla de encoding de caracteres.
  • En ese momento, Python ya había decidido una regla de interfaz de secuencia común donde un usuario podría crear una clase de secuencia (iterable). Pero Python no admite la extensión de tipos incorporados 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 en str.join(seq) :

Divertido, pero parece correcto! Barry, ve por ello …
–Guido van Rossum

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

Estoy de acuerdo en que se ve divertido.

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

Nota historica Cuando aprendí Python por primera vez, esperaba que unirse fuera un método de una lista, lo que tomaría el delimitador como argumento. Mucha gente siente lo mismo, y hay una historia detrás del método de unió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 su primer argumento. Las funciones se consideraron lo suficientemente importantes como para ponerlas en las propias cuerdas, lo que tenía sentido para funciones como inferior, superior y división. Pero muchos progtwigdores 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 módulo de cadena antiguo (que todavía tiene muchos de cosas útiles en ella). Utilizo el nuevo método de unión exclusivamente, pero verás el código escrito de cualquier manera, y si realmente te molesta, puedes usar la antigua función string.join.

— Mark Pilgrim, Sumérgete en Python

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  >>> "".join  

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 se puede unir byte y str juntos, por lo que la forma en que lo tienen ahora tiene sentido.

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

¡Esto es porque join es un método de “cadena”! Crea una cadena de cualquier iterable. Si pegamos 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 método de list , tendría que convertir cada iterador de cadenas como una list antes de poder unir los elementos en una sola cadena! Por ejemplo:

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

Vamos a rodar nuestro propio método de unirse a la lista:

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

Y para usarlo, tenga en cuenta que primero debemos crear una lista de cada iterable para unir las cadenas en ese iterable, desperdiciando tanto la memoria como la capacidad de procesamiento:

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

Así que vemos que tenemos que agregar un paso adicional para usar nuestro método de lista, en lugar de simplemente usar el método de cadena integrado:

 >>> ' | '.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 realidad tiene que pasar la iterable dos veces, por lo que si le proporciona una expresión generadora, primero debe materializarla en una lista antes de que pueda crear la cadena final.

Por lo tanto, si bien la transmisión de generadores suele ser mejor que la comprensión de listas, str.join es 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 operación str.join sigue siendo semánticamente una operación de “cadena”, por lo que todavía tiene sentido tenerla en el objeto str que en varios iterables.

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 idioma, 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, por lo que no hay una forma central de hacerlo. Agrégalo al conjunto de cosas que son iterables.

Principalmente porque el resultado de un 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.

- 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 hoja de trucos exhaustiva de methods_of_string 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']} 

Ambos no son agradables.

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

list.join (delimitar) es un poco más agradable porque estamos muy acostumbrados a que las cadenas sean un tipo fundamental (y, en el lenguaje, sí lo son). Sin embargo, esto significa que la combinación debe enviarse dinámicamente porque en el contexto arbitrario de a.split("\n") el comstackdor de Python no sepa qué es un y deba buscarlo (de manera análoga a la búsqueda de vtable), que Es caro si lo haces muchas veces.

Si el comstackdor de Python Runtime sabe que la lista es un módulo integrado, puede omitir la búsqueda dinámica y codificar la intención en el bytecode directamente, mientras que de lo contrario debe resolver dinámicamente la “unión” de “a”, que puede estar varias capas. de herencia por llamada (ya que entre llamadas, el significado de unirse puede haber cambiado, porque Python es un lenguaje dynamic).

lamentablemente, este es el último defecto de la abstracción; independientemente de la abstracción que 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 sea 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 económico, depende de usted pagar más para que se vea “más agradable”, ya sea haciendo su propio envoltorio o su propio preprocesador.