작은 목록보다 작은 문자열을 반복하는 것이 왜 느린가요?


132

나는 timeit을 가지고 놀고 있었고 작은 문자열에 대한 간단한 목록 이해가 작은 단일 문자열 목록에서 동일한 작업을 수행하는 것보다 오래 걸린다는 것을 알았습니다. 어떤 설명? 거의 1.35 배나 많은 시간입니다.

>>> from timeit import timeit
>>> timeit("[x for x in 'abc']")
2.0691067844831528
>>> timeit("[x for x in ['a', 'b', 'c']]")
1.5286479570345861

이 문제를 일으키는 낮은 수준에서 어떤 일이 일어나고 있습니까?

답변:


193

TL; DR

  • Python 2의 경우 많은 오버 헤드가 제거되면 실제 속도 차이는 70 % 이상에 가깝습니다.

  • 객체 생성 에 결함 이 없습니다 . 한 문자 문자열이 캐시되므로 두 방법 모두 새 객체를 생성하지 않습니다.

  • 그 차이는 분명하지 않지만 유형 및 형식과 관련하여 문자열 인덱싱에 대한 많은 수의 검사에서 생성 될 수 있습니다. 또한 무엇을 반품해야하는지 확인해야 할 가능성이 높습니다.

  • 목록 인덱싱이 매우 빠릅니다.



>>> python3 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.388 usec per loop

>>> python3 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.436 usec per loop

이것은 당신이 찾은 것에 동의하지 않습니다 ...

그렇다면 Python 2를 사용해야합니다.

>>> python2 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.309 usec per loop

>>> python2 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.212 usec per loop

버전의 차이점을 설명하겠습니다. 컴파일 된 코드를 살펴 보겠습니다.

파이썬 3의 경우 :

import dis

def list_iterate():
    [item for item in ["a", "b", "c"]]

dis.dis(list_iterate)
#>>>   4           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f4d06b118a0, file "", line 4>)
#>>>               3 LOAD_CONST               2 ('list_iterate.<locals>.<listcomp>')
#>>>               6 MAKE_FUNCTION            0
#>>>               9 LOAD_CONST               3 ('a')
#>>>              12 LOAD_CONST               4 ('b')
#>>>              15 LOAD_CONST               5 ('c')
#>>>              18 BUILD_LIST               3
#>>>              21 GET_ITER
#>>>              22 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
#>>>              25 POP_TOP
#>>>              26 LOAD_CONST               0 (None)
#>>>              29 RETURN_VALUE

def string_iterate():
    [item for item in "abc"]

dis.dis(string_iterate)
#>>>  21           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f4d06b17150, file "", line 21>)
#>>>               3 LOAD_CONST               2 ('string_iterate.<locals>.<listcomp>')
#>>>               6 MAKE_FUNCTION            0
#>>>               9 LOAD_CONST               3 ('abc')
#>>>              12 GET_ITER
#>>>              13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
#>>>              16 POP_TOP
#>>>              17 LOAD_CONST               0 (None)
#>>>              20 RETURN_VALUE

여기서는 매번 목록을 작성하기 때문에 목록 변형이 더 느릴 수 있음을 알 수 있습니다.

이것이

 9 LOAD_CONST   3 ('a')
12 LOAD_CONST   4 ('b')
15 LOAD_CONST   5 ('c')
18 BUILD_LIST   3

부품. 문자열 변형 만

 9 LOAD_CONST   3 ('abc')

이것이 차이가 나는 것을 확인할 수 있습니다.

def string_iterate():
    [item for item in ("a", "b", "c")]

dis.dis(string_iterate)
#>>>  35           0 LOAD_CONST               1 (<code object <listcomp> at 0x7f4d068be660, file "", line 35>)
#>>>               3 LOAD_CONST               2 ('string_iterate.<locals>.<listcomp>')
#>>>               6 MAKE_FUNCTION            0
#>>>               9 LOAD_CONST               6 (('a', 'b', 'c'))
#>>>              12 GET_ITER
#>>>              13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
#>>>              16 POP_TOP
#>>>              17 LOAD_CONST               0 (None)
#>>>              20 RETURN_VALUE

이것은 단지 생산

 9 LOAD_CONST               6 (('a', 'b', 'c'))

튜플은 불변이므로 테스트:

>>> python3 -m timeit '[x for x in ("a", "b", "c")]'
1000000 loops, best of 3: 0.369 usec per loop

대단한 속도로 돌아갑니다.

파이썬 2의 경우 :

def list_iterate():
    [item for item in ["a", "b", "c"]]

dis.dis(list_iterate)
#>>>   2           0 BUILD_LIST               0
#>>>               3 LOAD_CONST               1 ('a')
#>>>               6 LOAD_CONST               2 ('b')
#>>>               9 LOAD_CONST               3 ('c')
#>>>              12 BUILD_LIST               3
#>>>              15 GET_ITER            
#>>>         >>   16 FOR_ITER                12 (to 31)
#>>>              19 STORE_FAST               0 (item)
#>>>              22 LOAD_FAST                0 (item)
#>>>              25 LIST_APPEND              2
#>>>              28 JUMP_ABSOLUTE           16
#>>>         >>   31 POP_TOP             
#>>>              32 LOAD_CONST               0 (None)
#>>>              35 RETURN_VALUE        

def string_iterate():
    [item for item in "abc"]

dis.dis(string_iterate)
#>>>   2           0 BUILD_LIST               0
#>>>               3 LOAD_CONST               1 ('abc')
#>>>               6 GET_ITER            
#>>>         >>    7 FOR_ITER                12 (to 22)
#>>>              10 STORE_FAST               0 (item)
#>>>              13 LOAD_FAST                0 (item)
#>>>              16 LIST_APPEND              2
#>>>              19 JUMP_ABSOLUTE            7
#>>>         >>   22 POP_TOP             
#>>>              23 LOAD_CONST               0 (None)
#>>>              26 RETURN_VALUE        

이상한 점은 우리가 동일한 목록의 건물을 가지고 있지만 여전히 더 빠르다는 것입니다. 파이썬 2는 이상하게 행동합니다.

이해력과 시간을 제거합시다. 는 _ =이 밖으로 최적화하기 방지하는 것입니다.

>>> python3 -m timeit '_ = ["a", "b", "c"]'
10000000 loops, best of 3: 0.0707 usec per loop

>>> python3 -m timeit '_ = "abc"'
100000000 loops, best of 3: 0.0171 usec per loop

우리는 초기화가 버전 간의 차이를 설명하기에 충분히 중요하지 않다는 것을 알 수 있습니다 (이 숫자는 적습니다)! 따라서 파이썬 3의 이해력이 느리다는 결론을 내릴 수 있습니다. 이것은 파이썬 3이 더 안전한 범위를 갖도록 이해를 변경함에 따라 의미가 있습니다.

이제 벤치 마크를 개선하십시오 (반복되지 않는 오버 헤드 만 제거함). 이것은 iterable의 건물을 미리 할당하여 제거합니다.

>>> python3 -m timeit -s 'iterable = "abc"'           '[x for x in iterable]'
1000000 loops, best of 3: 0.387 usec per loop

>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
1000000 loops, best of 3: 0.368 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"'           '[x for x in iterable]'
1000000 loops, best of 3: 0.309 usec per loop

>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
10000000 loops, best of 3: 0.164 usec per loop

호출 iter이 오버 헤드 인지 확인할 수 있습니다 .

>>> python3 -m timeit -s 'iterable = "abc"'           'iter(iterable)'
10000000 loops, best of 3: 0.099 usec per loop

>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.1 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"'           'iter(iterable)'
10000000 loops, best of 3: 0.0913 usec per loop

>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.0854 usec per loop

아닙니다. 아닙니다. 차이점은 특히 Python 3의 경우 너무 작습니다.

따라서 모든 것을 느리게하여 원치 않는 오버 헤드를 제거하십시오! 목표는 반복 시간을 길게하여 시간이 오버 헤드를 숨기는 것입니다.

>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 3.12 msec per loop

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.77 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 2.32 msec per loop

>>> python2 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.09 msec per loop

이것은 실제로 많이 바뀌지 않았지만 약간 도움이되었습니다.

따라서 이해력을 제거하십시오. 질문의 일부가 아닌 오버 헤드입니다.

>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.71 msec per loop

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 1.36 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.27 msec per loop

>>> python2 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 935 usec per loop

더 좋아! deque반복 해서 사용하면 약간 더 빨리 얻을 수 있습니다 . 기본적으로 동일하지만 더 빠릅니다 .

>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop

>>> python3 -m timeit -s 'import random; from collections import deque; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 805 usec per loop

>>> python2 -m timeit -s 'import random; from collections import deque; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 438 usec per loop

인상적인 것은 유니 코드가 바이트 문자열과 경쟁한다는 것입니다. 다음 bytes과 같이 시도 하여 명시 적으로 확인할 수 있습니다 unicode.

  • bytes

    >>> python3 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127)).encode("ascii") for _ in range(100000))' 'deque(iterable, maxlen=0)'                                                                    :(
    1000 loops, best of 3: 571 usec per loop
    
    >>> python3 -m timeit -s 'import random; from collections import deque; iterable =         [chr(random.randint(0, 127)).encode("ascii") for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 394 usec per loop
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127))                 for _ in range(100000))' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 757 usec per loop
    
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable =         [chr(random.randint(0, 127))                 for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 438 usec per loop

    여기 파이썬 3이 실제로 파이썬 2보다 빠릅니다 .

  • unicode

    >>> python3 -m timeit -s 'import random; from collections import deque; iterable = u"".join(   chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 800 usec per loop
    
    >>> python3 -m timeit -s 'import random; from collections import deque; iterable =         [   chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 394 usec per loop
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable = u"".join(unichr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 1.07 msec per loop
    
    >>> python2 -m timeit -s 'import random; from collections import deque; iterable =         [unichr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
    1000 loops, best of 3: 469 usec per loop

    다시 말하지만, 파이썬 3은 더 빠르지 만, 파이썬 3 str에서는 많은주의를 기울였습니다.

사실,이 unicode- bytes차이가 인상적이다, 매우 작습니다.

이 사례를 빠르고 편리하게 분석해 보겠습니다.

>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop

>>> python3 -m timeit -s 'import random; from collections import deque; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop

우리는 실제로 Tim Peter의 10 번 찬성 된 답변을 배제 할 수 있습니다!

>>> foo = iterable[123]
>>> iterable[36] is foo
True

이것들은 새로운 물건이 아닙니다!

그러나 인덱싱 비용 . 차이점은 인덱싱에있을 수 있으므로 반복을 제거하고 인덱스 만하십시오.

>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'iterable[123]'
10000000 loops, best of 3: 0.0397 usec per loop

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable[123]'
10000000 loops, best of 3: 0.0374 usec per loop

차이는 작지만 비용의 절반 이상 이 간접비입니다.

>>> python3 -m timeit -s 'import random; iterable =        [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable; 123'
100000000 loops, best of 3: 0.0173 usec per loop

속도 차이는 그것을 비난하기에 충분합니다. 내 생각에

그렇다면 왜 목록을 훨씬 빨리 색인화합니까?

글쎄, 나는 그것에 대해 당신에게 돌아올 것이지만, 내 추측은 interned 문자열 (또는 별도의 메커니즘 인 경우 캐시 된 문자)을 확인하는 것입니다. 이것은 최적보다 빠르지 않습니다. 그러나 나는 소스를 검사 할 것입니다 (C에서 편안하지는 않지만) :).


소스는 다음과 같습니다.

static PyObject *
unicode_getitem(PyObject *self, Py_ssize_t index)
{
    void *data;
    enum PyUnicode_Kind kind;
    Py_UCS4 ch;
    PyObject *res;

    if (!PyUnicode_Check(self) || PyUnicode_READY(self) == -1) {
        PyErr_BadArgument();
        return NULL;
    }
    if (index < 0 || index >= PyUnicode_GET_LENGTH(self)) {
        PyErr_SetString(PyExc_IndexError, "string index out of range");
        return NULL;
    }
    kind = PyUnicode_KIND(self);
    data = PyUnicode_DATA(self);
    ch = PyUnicode_READ(kind, data, index);
    if (ch < 256)
        return get_latin1_char(ch);

    res = PyUnicode_New(1, ch);
    if (res == NULL)
        return NULL;
    kind = PyUnicode_KIND(res);
    data = PyUnicode_DATA(res);
    PyUnicode_WRITE(kind, data, 0, ch);
    assert(_PyUnicode_CheckConsistency(res, 1));
    return res;
}

위에서 걷다 보면 몇 가지 점검이 있습니다. 지루하다. 그런 다음 일부 과제는 지루해야합니다. 첫 번째 흥미로운 라인은

ch = PyUnicode_READ(kind, data, index);

그러나 색인을 생성하여 연속적인 C 배열에서 읽을 때 빠른 속도를 기대 합니다. 결과 ch는 256보다 작으므로에 캐시 된 문자를 반환합니다 get_latin1_char(ch).

그래서 우리는 실행합니다 (첫 번째 검사를 삭제)

kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
return get_latin1_char(ch);

어디

#define PyUnicode_KIND(op) \
    (assert(PyUnicode_Check(op)), \
     assert(PyUnicode_IS_READY(op)),            \
     ((PyASCIIObject *)(op))->state.kind)

(어설 션은 디버그에서 무시되기 때문에 지루합니다. (그래서 빠르다는 것을 확인할 수 있습니다 ((PyASCIIObject *)(op))->state.kind)) 간접적이며 C 레벨 캐스트입니다).

#define PyUnicode_DATA(op) \
    (assert(PyUnicode_Check(op)), \
     PyUnicode_IS_COMPACT(op) ? _PyUnicode_COMPACT_DATA(op) :   \
     _PyUnicode_NONCOMPACT_DATA(op))

(매크로 ( Something_CAPITALIZED)가 모두 빠르다고 가정하면 비슷한 이유로 지루합니다 ),

#define PyUnicode_READ(kind, data, index) \
    ((Py_UCS4) \
    ((kind) == PyUnicode_1BYTE_KIND ? \
        ((const Py_UCS1 *)(data))[(index)] : \
        ((kind) == PyUnicode_2BYTE_KIND ? \
            ((const Py_UCS2 *)(data))[(index)] : \
            ((const Py_UCS4 *)(data))[(index)] \
        ) \
    ))

(인덱스를 포함하지만 실제로 느리지는 않습니다) 및

static PyObject*
get_latin1_char(unsigned char ch)
{
    PyObject *unicode = unicode_latin1[ch];
    if (!unicode) {
        unicode = PyUnicode_New(1, ch);
        if (!unicode)
            return NULL;
        PyUnicode_1BYTE_DATA(unicode)[0] = ch;
        assert(_PyUnicode_CheckConsistency(unicode, 1));
        unicode_latin1[ch] = unicode;
    }
    Py_INCREF(unicode);
    return unicode;
}

다음과 같은 의심을 확인합니다.

  • 캐시됩니다 :

    PyObject *unicode = unicode_latin1[ch];
  • 이것은 빠르다. 이 if (!unicode)실행되지 않으므로이 경우 문자 그대로 같습니다.

    PyObject *unicode = unicode_latin1[ch];
    Py_INCREF(unicode);
    return unicode;

솔직히 말해서 asserts가 빠르면 ([ C 레벨 주장에서 작동 한다고 생각 합니다 ...]), 느리게 느린 부분은 다음과 같습니다.

PyUnicode_IS_COMPACT(op)
_PyUnicode_COMPACT_DATA(op)
_PyUnicode_NONCOMPACT_DATA(op)

어느 것이 :

#define PyUnicode_IS_COMPACT(op) \
    (((PyASCIIObject*)(op))->state.compact)

(전과 같이)

#define _PyUnicode_COMPACT_DATA(op)                     \
    (PyUnicode_IS_ASCII(op) ?                   \
     ((void*)((PyASCIIObject*)(op) + 1)) :              \
     ((void*)((PyCompactUnicodeObject*)(op) + 1)))

(매크로 IS_ASCII가 빠르면 빠름)

#define _PyUnicode_NONCOMPACT_DATA(op)                  \
    (assert(((PyUnicodeObject*)(op))->data.any),        \
     ((((PyUnicodeObject *)(op))->data.any)))

(어설 션 + 간접 + 캐스트 인 경우에도 빠름).

그래서 우리는 (토끼 구멍) 아래로 향합니다.

PyUnicode_IS_ASCII

어느

#define PyUnicode_IS_ASCII(op)                   \
    (assert(PyUnicode_Check(op)),                \
     assert(PyUnicode_IS_READY(op)),             \
     ((PyASCIIObject*)op)->state.ascii)

흠 ... 그것도 빠르다 ...


글쎄요,하지만 비교해 봅시다 PyList_GetItem. (예, 팀 피터스에게 더 많은 일을 해주셔서 감사 합니다. : P)

PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
    if (!PyList_Check(op)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    if (i < 0 || i >= Py_SIZE(op)) {
        if (indexerr == NULL) {
            indexerr = PyUnicode_FromString(
                "list index out of range");
            if (indexerr == NULL)
                return NULL;
        }
        PyErr_SetObject(PyExc_IndexError, indexerr);
        return NULL;
    }
    return ((PyListObject *)op) -> ob_item[i];
}

우리는 오류가 아닌 경우에 이것이 실행될 것이라는 것을 알 수 있습니다.

PyList_Check(op)
Py_SIZE(op)
((PyListObject *)op) -> ob_item[i]

어디 PyList_Check있다

#define PyList_Check(op) \
     PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS)

( TABS! TABS !!! ) ( issue21587 ) 5 분 후에 수정 및 병합되었습니다 . 좋아 ... 그래 제길. 그들은 스키 켓을 수치스럽게 만들었다.

#define Py_SIZE(ob)             (((PyVarObject*)(ob))->ob_size)
#define PyType_FastSubclass(t,f)  PyType_HasFeature(t,f)
#ifdef Py_LIMITED_API
#define PyType_HasFeature(t,f)  ((PyType_GetFlags(t) & (f)) != 0)
#else
#define PyType_HasFeature(t,f)  (((t)->tp_flags & (f)) != 0)
#endif

따라서이 설정되어 있지 않으면 일반적으로 사소한 것입니다 (두 개의 간접 지시 및 두 개의 부울 검사) Py_LIMITED_API.

그런 다음 색인 생성과 캐스트 ( ((PyListObject *)op) -> ob_item[i])가 완료되었습니다.

따라서 목록에 대한 검사 가 확실히 으며, 작은 속도 차이로 인해 관련성이있을 수 있습니다.


나는 일반적으로 (->)유니 코드에 대한 유형 검사와 간접 지시가 더 있다고 생각 합니다. 내가 요점을 놓친 것 같지만 무엇 ?


17
코드를 자명 한 것으로 제시하고 있습니다. 스 니펫을 결론으로 ​​제시하기까지합니다. 불행히도 나를 따라갈 수는 없습니다. 잘못된 것을 찾는 당신의 접근 방식이 확실하지 않다고 말하는 것은 아니지만, 따르기 쉽다면 좋을 것입니다.
PascalVKooten

2
나는 그것을 개선하려고 노력했지만 그것을 명확하게하는 방법을 모르겠습니다. 나는 C를 쓰지 않기 때문에 이것은 코드의 고급 분석이며 전체 개념 만 중요합니다.
Veedrac

@Nit 내가 추가했습니다. 그것이 부족하다고 느끼면 알려주십시오. 불행히도 그것은 실제로 대답을 모른다는 것을 강조합니다 (* gasp *).
Veedrac

3
나는 당신의 대답을 받아들이 기 전에 또 다른 날을 줄 것입니다 (더 구체적인 팝업을보고 싶습니다). 매우 흥미롭고 잘 연구 된 답변에 감사드립니다.
Sunjay Varma

4
움직이는 타겟에서 촬영하고 있음을 주목하십시오. ;-)이 구현은 Python 2와 Python 3뿐만 아니라 다른 릴리스에서도 다릅니다. 예를 들어, 현재 개발 트렁크에서는 get_latin1_char()트릭이 더 이상 존재하지 않습니다.unicode_getitem() 하위 레벨에unicode_char . 따라서 다른 수준의 함수 호출이 있습니다. 사용되는 컴파일러 및 최적화 플래그에 따라 다릅니다. 이 수준의 세부 사항에는 신뢰할만한 답변이 없습니다. ;-)
Tim Peters

31

대부분의 컨테이너 객체 (목록, 튜플, dicts 등)를 반복하면 반복자가 컨테이너에 객체 전달합니다 .

그러나 문자열을 반복 할 때는 전달 된 각 문자에 대해 객체를 만들어야합니다. 문자열은 "컨테이너"가 아니라 목록은 컨테이너입니다. 반복에서 해당 오브젝트를 작성하기 전에 문자열의 개별 문자가 별도의 오브젝트로 존재하지 않습니다.


3
나는 이것이 사실이라고 생각하지 않습니다. 로 확인할 수 있습니다 is. 잘 들리지만 실제로는 그렇게 생각하지 않습니다.
Veedrac

@Veedrac 답변을 살펴보십시오.
Christian

3
stringobject.c것을 보여준다 __getitem__들에게 할당 비용은 한번만 발생되도록 문자열 방금 저장 한 문자 스트링 테이블에서 결과를 얻어 온다.
user2357112는 Monica

10
예, Python 2의 일반 문자열의 경우 중요한 점입니다. 파이썬 3에서 모든 문자열은 "공식적으로"유니 코드이며 더 자세한 내용이 포함되어 있습니다 (Vedrac의 답변 참조). 예를 들어, 파이썬 3의 경우 s = chr(256), s is chr(256)리턴 값을 False아는 것만으로는 충분하지 않습니다. 특별한 경우가 데이터 값을 트리거하는 커버 아래에 존재하기 때문 입니다.
Tim Peters

1

문자열의 반복자를 작성하기 위해 발생하고 오버 헤드가 발생할 수 있습니다. 인스턴스화시 배열에는 이미 반복자가 포함되어 있습니다.

편집하다:

>>> timeit("[x for x in ['a','b','c']]")
0.3818681240081787
>>> timeit("[x for x in 'abc']")
0.3732869625091553

이것은 2.7을 사용하여 실행되었지만 내 Mac Book Pro i7에서 실행되었습니다. 시스템 구성 차이로 인한 결과 일 수 있습니다.


직선 반복자를 사용하더라도 문자열은 여전히 ​​상당히 느립니다. timeit ( "[x의 x에 대한 x]", "it = iter ( 'abc')") = 0.34543599384033535; timeit ( "[x x x in]]", "it = iter (list ( 'abc'))") = 0.2791691380446508
Sunjay Varma
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.