Suma de Python, ¿por qué no cadenas?

Python tiene una sum funciones incorporada, que es efectivamente equivalente a:

 def sum2(iterable, start=0): return start + reduce(operator.add, iterable) 

Para todo tipo de parámetros excepto cadenas. Funciona para números y listas, por ejemplo:

  sum([1,2,3], 0) = sum2([1,2,3],0) = 6 #Note: 0 is the default value for start, but I include it for clarity sum({888:1}, 0) = sum2({888:1},0) = 888 

¿Por qué las cuerdas fueron especialmente excluidas?

  sum( ['foo','bar'], '') # TypeError: sum() can't sum strings [use ''.join(seq) instead] sum2(['foo','bar'], '') = 'foobar' 

Parezco recordar discusiones en la lista de Python por la razón, por lo que una explicación o un enlace a un hilo que lo explique estaría bien.

Edit : soy consciente de que la forma estándar es hacer "".join . Mi pregunta es por qué se prohibió la opción de usar sum para cadenas, y no hubo prohibición para, por ejemplo, listas.

Edit 2 : Aunque creo que esto no es necesario dadas todas las buenas respuestas que obtuve, la pregunta es: ¿Por qué la sum funciona en un iterable que contiene números o en un iterable que contiene listas pero no en un iterable que contiene cadenas?

Python intenta desalentarte de “sumr” cadenas. Se supone que debes unirte a ellos:

 "".join(list_of_strings) 

Es mucho más rápido y usa menos memoria.

Un punto de referencia rápido:

 $ python -m timeit -s 'import operator; strings = ["a"]*10000' 'r = reduce(operator.add, strings)' 100 loops, best of 3: 8.46 msec per loop $ python -m timeit -s 'import operator; strings = ["a"]*10000' 'r = "".join(strings)' 1000 loops, best of 3: 296 usec per loop 

Editar (para responder a la edición de OP): En cuanto a por qué las cadenas aparentemente se “destacaron”, creo que es simplemente una cuestión de optimización para un caso común, así como de hacer cumplir las mejores prácticas: puede unirse a las cadenas mucho más rápido con “”. unirse, por lo que prohibir explícitamente las cadenas en la sum señalará esto a los novatos.

Por cierto, esta restricción se ha implementado “para siempre”, es decir, ya que la sum se agregó como una función incorporada ( rev. 32347 )

De hecho, puede usar la sum(..) para concatenar cadenas, si usa el objeto de inicio apropiado. Por supuesto, si vas tan lejos, ya has entendido lo suficiente como para usar "".join(..) todos modos ..

 >>> class ZeroObject(object): ... def __add__(self, other): ... return other ... >>> sum(["hi", "there"], ZeroObject()) 'hithere' 

Aquí está la fuente: http://svn.python.org/view/python/trunk/Python/bltinmodule.c?revision=81029&view=markup

En la función builtin_sum tenemos este bit de código:

  /* reject string values for 'start' parameter */ if (PyObject_TypeCheck(result, &PyBaseString_Type)) { PyErr_SetString(PyExc_TypeError, "sum() can't sum strings [use ''.join(seq) instead]"); Py_DECREF(iter); return NULL; } Py_INCREF(result); } 

Entonces … esa es tu respuesta.

Se comprueba explícitamente en el código y se rechaza.

De los documentos :

La forma rápida y preferida de concatenar una secuencia de cadenas es llamando a ” .join (secuencia).

Al hacer que la sum niegue a operar con cadenas, Python lo alentó a usar el método correcto.

Respuesta corta: Eficiencia.

Respuesta larga: la función de sum debe crear un objeto para cada sum parcial.

Suponga que la cantidad de tiempo requerido para crear un objeto es directamente proporcional al tamaño de sus datos. Sea N el número de elementos en la secuencia a sumr.

double s son siempre del mismo tamaño, lo que hace que el tiempo de ejecución de la sum O (1) × N = O (N) .

int (anteriormente conocido como long ) es de longitud arbitraria. Sea M el valor absoluto del elemento de secuencia más grande. Entonces, el tiempo de ejecución del caso más desfavorable de la sum es lg (M) + lg (2M) + lg (3M) + … + lg (NM) = N × lg (M) + lg (N!) = O (N registro N) .

Para str (donde M = la longitud de la cadena más larga), el tiempo de ejecución en el peor de los casos es M + 2M + 3M + … + NM = M × (1 + 2 + … + N) = O (N² ) .

Por lo tanto, sum cadenas sería mucho más lento que sum números.

str.join no asigna ningún objeto intermedio. Preasigna un búfer lo suficientemente grande como para contener las cadenas unidas y copia los datos de la cadena. Se ejecuta en tiempo O (N) , mucho más rápido que la sum .

La razón por la cual

@ dan04 tiene una excelente explicación de los costos de usar la sum en grandes listas de cadenas.

La parte faltante de por qué no se permite la sum para la sum es que muchas, muchas personas intentaron usar la sum para las cadenas, y no mucha la sum para las listas y tuplas y otras estructuras de datos O (n ** 2). La trampa es que la sum funciona bien para listas cortas de cadenas, pero luego se pone en producción donde las listas pueden ser enormes, y el rendimiento se ralentiza. Esta fue una trampa tan común que se tomó la decisión de ignorar la tipificación de pato en esta instancia, y no permitir que las cadenas se usen con la sum .

Editar: Movió las partes sobre la inmutabilidad a la historia.

Básicamente, es una cuestión de preasignación. Cuando usas una statement como

 sum(["a", "b", "c", ..., ]) 

y espere que funcione de manera similar a una statement de reduce , el código generado parece algo así como

 v1 = "" + "a" # must allocate v1 and set its size to len("") + len("a") v2 = v1 + "b" # must allocate v2 and set its size to len("a") + len("b") ... res = v10000 + "$" # must allocate res and set its size to len(v9999) + len("$") 

En cada uno de estos pasos se crea una nueva cadena, que por un lado podría generar cierta sobrecarga de copia a medida que las cadenas se hacen cada vez más largas. Pero tal vez ese no sea el punto aquí. Lo que es más importante, es que cada nueva cadena en cada línea debe asignarse a su tamaño específico (lo cual. No lo sé, debe asignarse en cada iteración de la statement de reduce , podría haber algunas heurísticas obvias para usar y Python podría asigne un poco más aquí y allá para su reutilización, pero en varios puntos la nueva cadena será lo suficientemente grande como para que esto no ayude más y Python debe volver a asignarse, lo cual es bastante costoso.

Sin embargo, un método dedicado como join , sin embargo, tiene el trabajo de averiguar el tamaño real de la cadena antes de que comience y, por lo tanto, en teoría, solo se asignará una vez, al principio y luego solo se rellenará esa nueva cadena, que es mucho más barata que la otra solución .

No sé por qué, pero esto funciona!

 import operator def sum_of_strings(list_of_strings): return reduce(operator.add, list_of_strings)