[Python]Extended Iterable Unpacking

dj-yang·2021년 7월 13일
0

python

목록 보기
2/4
post-thumbnail

서론

네이버 블로그에서 velog로 옮기면서 괜찮은 내용이거나 아니면 한번 더 살펴봐야할 포스팅을 옮기게 되었다

이 글에서는 pep 3132에 대해서 다룬다.

해당 내용은 순환 가능 요소의 언패킹에 *(star-expr)를 사용하는 것을 권장하는 이유에 대해서 설명한다.


문제점

언패킹에 있어서 많은 알고리즘에서 기존에 사용하는 방식은 아래와 같았다.

first, last = seq[0], seq[1:]

위의 방식처럼 index를 이용한 slicing 방법을 사용했지만 이는 다음과 같은 문제점이 있었다.

  • 셋과 같이 리스트는 아니지만 순회 가능한 요소의 경우 다음과 같은 과정을 거쳐야해서 성능을 저하시켰다.
it = iter(seq)
first = it.next()
rest = list(it)
  • 불필요한 인덱싱을 사용해야 한다.

권장 사용

위의 문제점을 해결하기 위한 방법으로 아래와 같은 방식을 권장한다.

first, *last = seq
# 아래와 같은 방식도 가능
a, * b, c = seq 
[a, * b, c] = seq
* a, = seq
a, * b in [(1, 2, 3), (4, 5, 6, 7)]:
    print (b)

# 불가능한 방식
* a = seq # 단일 사용

*를 사용하면 아래와 같은 방식으로 동작한다.

  • all items for mandatory targets before the starred one
  • collect all remaining items from the iterable in a list
  • pop items for mandatory targets after the starred one from the list
  • push the single items and the resized list on the stack

위의 설명대로 예시를 들자면 아래의 코드를 실행했을 때:

a, *b, c = [1, 2, 3, 4, 5]

다음과 같이 동작한다.

a = 1

b = [2, 3, 4, 5]

c = b.pop()

정확한 동작 소스 코드(CPython)

unpack_iterable(PyObject *v, int argcnt, int argcntafter, PyObject **sp)
{
    int i = 0, j = 0;
    Py_ssize_t ll = 0;
    PyObject *it;  /* iter(v) */
    PyObject *w;
    PyObject *l = NULL; /* variable list */
    assert(v != NULL);
    it = PyObject_GetIter(v);
    if (it == NULL)
        goto Error;
    for (; i < argcnt; i++) {
        w = PyIter_Next(it);
        if (w == NULL) {
            /* Iterator done, via error or exhaustion. */
            if (!PyErr_Occurred()) {
                if (argcntafter == -1) {
                    PyErr_Format(PyExc_ValueError,
                        "not enough values to unpack (expected %d, got %d)",
                        argcnt, i);
                }
                else {
                    PyErr_Format(PyExc_ValueError,
                        "not enough values to unpack "
                        "(expected at least %d, got %d)",
                        argcnt + argcntafter, i);
                }
            }
            goto Error;
        }
        *--sp = w;
    }
    if (argcntafter == -1) {
        /* We better have exhausted the iterator now. */
        w = PyIter_Next(it);
        if (w == NULL) {
            if (PyErr_Occurred())
                goto Error;
            Py_DECREF(it);
            return 1;
        }
        Py_DECREF(w);
        PyErr_Format(PyExc_ValueError,
            "too many values to unpack (expected %d)",
            argcnt);
        goto Error;
    }
    l = PySequence_List(it);
    if (l == NULL)
        goto Error;
    *--sp = l;
    i++;
    ll = PyList_GET_SIZE(l);
    if (ll < argcntafter) {
        PyErr_Format(PyExc_ValueError,
            "not enough values to unpack (expected at least %d, got %zd)",
            argcnt + argcntafter, argcnt + ll);
        goto Error;
    }
    /* Pop the "after-variable" args off the list. */
    for (j = argcntafter; j > 0; j--, i++) {
        *--sp = PyList_GET_ITEM(l, ll - j);
    }
    /* Resize the list. */
    Py_SIZE(l) = ll - argcntafter;
    Py_DECREF(it);
    return 1;
Error:
    for (; i > 0; i--, sp++)
        Py_DECREF(*sp);
    Py_XDECREF(it);
    return 0;
}

고찰

영어 공부 좀 하자..

profile
비전공자가 고통받으며 개발합니다

0개의 댓글