developer tip

파이썬 합계, 왜 문자열이 아닌가?

copycodes 2020. 11. 30. 17:58
반응형

파이썬 합계, 왜 문자열이 아닌가?


Python에는 다음과 같은 기능이 내장되어 있습니다 sum.

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

문자열을 제외한 모든 유형의 매개 변수에 대해. 예를 들어 숫자와 목록에 대해 작동합니다.

 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

문자열이 특별히 생략 된 이유는 무엇입니까?

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

이유 때문에 파이썬 목록에있는 토론을 기억하는 것 같으므로 설명이나 스레드에 대한 링크가 좋을 것입니다.

편집 : 표준 방법은 수행하는 것 "".join입니다. 내 질문은 문자열에 합계를 사용하는 옵션이 금지되고 목록에 대한 금지가없는 이유입니다.

편집 2 : 내가 얻은 모든 좋은 대답을 감안할 때 이것이 필요하지 않다고 생각하지만 질문은 : 왜 합계가 숫자를 포함하는 반복 가능 또는 목록을 포함하는 반복 가능에서 작동하지만 문자열을 포함하는 반복 가능에서 작동합니까?


파이썬은 문자열을 "합산"하지 못하도록합니다. 당신은 그들과 함께해야합니다 :

"".join(list_of_strings)

훨씬 빠르며 메모리를 훨씬 적게 사용합니다.

빠른 벤치 마크 :

$ 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

편집 (OP의 편집에 답하기 위해) : 문자열이 분명히 "단일화"된 이유에 관해서는 단순히 일반적인 경우에 최적화하고 모범 사례를 적용하는 문제라고 생각합니다. ''로 문자열을 훨씬 더 빠르게 조인 할 수 있습니다. join, 그래서 명시 적으로 금지하는 문자열은 sum초보자에게 이것을 지적 할 것입니다.

BTW,이 제한은 "영원히"적용되었습니다. 즉,이 ( sum가) 내장 함수로 추가 되었기 때문입니다 ( rev. 32347 ).


sum(..)적절한 시작 개체를 사용하면 실제로을 사용 하여 문자열을 연결할 수 있습니다 ! 물론 여기까지 가면 "".join(..)어차피 사용할만큼 충분히 이해 한 것 입니다 ..

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

소스는 다음과 같습니다. http://svn.python.org/view/python/trunk/Python/bltinmodule.c?revision=81029&view=markup

builtin_sum 함수에는 다음과 같은 코드가 있습니다.

     /* 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);
    }

그래서 .. 그게 당신의 대답입니다.

코드에서 명시 적으로 확인되고 거부됩니다.


에서 워드 프로세서 :

문자열 시퀀스를 연결하는 선호되는 빠른 방법은 ''.join (sequence)를 호출하는 것입니다.

sum문자열 작업을 거부 함으로써 Python은 올바른 방법을 사용하도록 권장했습니다.


짧은 대답 : 효율성.

긴 대답 : sum함수는 각 부분 합계에 대해 객체를 만들어야합니다.

Assume that the amount of time required to create an object is directly proportional to the size of its data. Let N denote the number of elements in the sequence to sum.

doubles are always the same size, which makes sum's running time O(1)×N = O(N).

int (formerly known as long) is arbitary-length. Let M denote the absolute value of the largest sequence element. Then sum's worst-case running time is lg(M) + lg(2M) + lg(3M) + ... + lg(NM) = N×lg(M) + lg(N!) = O(N log N).

For str (where M = the length of the longest string), the worst-case running time is M + 2M + 3M + ... + NM = M×(1 + 2 + ... + N) = O(N²).

Thus, summing strings would be much slower than summing numbers.

str.join does not allocate any intermediate objects. It preallocates a buffer large enough to hold the joined strings, and copies the string data. It runs in O(N) time, much faster than sum.


The Reason Why

@dan04 has an excellent explanation for the costs of using sum on large lists of strings.

The missing piece as to why str is not allowed for sum is that many, many people were trying to use sum for strings, and not many use sum for lists and tuples and other O(n**2) data structures. The trap is that sum works just fine for short lists of strings, but then gets put in production where the lists can be huge, and the performance slows to a crawl. This was such a common trap that the decision was made to ignore duck-typing in this instance, and not allow strings to be used with sum.


Edit: Moved the parts about immutability to history.

Basically, its a question of preallocation. When you use a statement such as

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

and expect it to work similar to a reduce statement, the code generated looks something like

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("$")

In each of these steps a new string is created, which for one might give some copying overhead as the strings are getting longer and longer. But that’s maybe not the point here. What’s more important, is that every new string on each line must be allocated to it’s specific size (which. I don’t know it it must allocate in every iteration of the reduce statement, there might be some obvious heuristics to use and Python might allocate a bit more here and there for reuse – but at several points the new string will be large enough that this won’t help anymore and Python must allocate again, which is rather expensive.

A dedicated method like join, however has the job to figure out the real size of the string before it starts and would therefore in theory only allocate once, at the beginning and then just fill that new string, which is much cheaper than the other solution.


I dont know why, but this works!

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

참고URL : https://stackoverflow.com/questions/3525359/python-sum-why-not-strings

반응형