2017-05-11 14 views
2

오늘 내가 쓴 an answer에서 발견 한 버크 (quirk)를 이해하려고합니다. 기본적으로, 나는 itertools.groupby을 감싸는 생성기 함수로부터 그룹을 만들었다. 제가 발견 한 흥미로운 점은 과제의 왼쪽에 생성기의 압축을 풀면 생성기의 마지막 요소가 남아 있다는 것입니다. 예를 들어`groupby` 구현과 인수 풀기

$ python3 ~/sandbox/test_gb.py 
[] 
[(True, 9)] 

이 CPython2.7 및 CPython3.5 모두의 경우 : CPython의 온

# test_gb.py 
from itertools import groupby 
from operator import itemgetter 

inputs = ((x > 5, x) for x in range(10)) 

def make_groups(inputs): 
    for _, group in groupby(inputs, key=itemgetter(0)): 
     yield group 

a, b = make_groups(inputs) 
print(list(a)) 
print(list(b)) 

,이 결과. PyPy에

, 그것은 결과 :

두 경우 모두
$ pypy ~/sandbox/test_gb.py 
[] 
[] 

, 첫 번째 빈 목록 ("a")는 설명하기 매우 쉬운 - itertools에서 그룹은 다음 요소로 빨리 소모됩니다 필요합니다. 우리가 그 값을 어디서나 저장하지 않았기 때문에, 그들은 에테르로 잃어 버렸습니다.

는 포장을 풀 때 파이썬은 있는지 확인 후 무엇을 찾아이 필요하기 때문에 너무 ... 우리가 (너무 b를 소비하는 PyPy 버전의 두 번째 빈리스트 ("b")에 대한 의미가 나에게 보인다 포장을 풀기 위해 잘못된 수의 항목에 대해 ValueError을 던져서는 안됩니다.) 그러나 어떤 이유로, CPython 버전은 입력 iterable에서 마지막 요소를 보유하고 있습니다 ... 아무도 왜이있을 수도 설명 할 수 있을까요?

편집

이 아마 더 또는 덜 분명하지만, 우리는 또한으로 작성할 수 있습니다

inputs = ((x > 5, x) for x in range(10)) 
(_, a), (_, b) = groupby(inputs, key=itemgetter(0)) 
print(list(a)) 
print(list(b)) 

과 같은 결과를 얻을 수 ...

+1

PyPy를 CPython처럼 동작하도록 변경했습니다. 필자는 이것이 왜 "왜"라는 질문에 실제로 대답하지 않는다는 것을 알고 있습니다. docy에서 재구성 된 버전을 사용하는 대신 CPython에서 정확한 알고리즘을 PyPy로 복사하는 것만으로도 동일한 결과를 얻을 수 있습니다. –

+1

동작이 약간 손상된 것 같습니다. CPython 문제 보고서에서이 질문을하고 싶을 것 같습니다. 아마 다음 3.7 릴리스에서 수정 될 것입니다. 또는 어쩌면 누군가 현재의 행동이 더 나은 이유에 대한 이유를 발견 할 것입니다. 또는 귀하의 문제는 영원히 열려있을 것입니다. –

+1

관심있는 다른 사람들을 위해 [이 커밋] (https://bitbucket.org/pypy/pypy/commits/6093ff1a44e6b17f09db83aa80aea562a738c286)에서 변경된 pypy처럼 보입니다. – mgilson

답변

2

그것은 년대 groupby 때문에 개체가 부기를 처리하고 개체는 key 개체와 부모 개체 인 groupby을 참조합니다.

typedef struct { 
    PyObject_HEAD 
    PyObject *it;   /* iterator over the input sequence */ 
    PyObject *keyfunc;  /* the second argument for the groupby function */ 
    PyObject *tgtkey;  /* the key for the current "grouper" */ 
    PyObject *currkey;  /* the key for the current "item" of the iterator*/ 
    PyObject *currvalue; /* the plain value of the current "item" */ 
} groupbyobject; 

typedef struct { 
    PyObject_HEAD 
    PyObject *parent;  /* the groupby object */ 
    PyObject *tgtkey;  /* the key value for this grouper object. */ 
} _grouperobject; 

groupby 개체를 압축 해제 할 때 grouper 개체를 반복하지 않으므로 무시해 보겠습니다. 그래서 흥미로운 것은 당신이 그것에 next를 호출 할 때 groupby에서 일어나는 것입니다 :

static PyObject * 
groupby_next(groupbyobject *gbo) 
{ 
    PyObject *newvalue, *newkey, *r, *grouper; 

    /* skip to next iteration group */ 
    for (;;) { 
     if (gbo->currkey == NULL) 
      /* pass */; 
     else if (gbo->tgtkey == NULL) 
      break; 
     else { 
      int rcmp; 

      rcmp = PyObject_RichCompareBool(gbo->tgtkey, gbo->currkey, Py_EQ); 
      if (rcmp == 0) 
       break; 
     } 

     newvalue = PyIter_Next(gbo->it); 
     if (newvalue == NULL) 
      return NULL; /* just return NULL, no invalidation of attributes */ 
     newkey = PyObject_CallFunctionObjArgs(gbo->keyfunc, newvalue, NULL); 

     gbo->currkey = newkey; 
     gbo->currvalue = newvalue; 
    } 
    gbo->tgtkey = gbo->currkey; 

    grouper = _grouper_create(gbo, gbo->tgtkey); 
    r = PyTuple_Pack(2, gbo->currkey, grouper); 
    return r; 
} 

내가 모든 관련이없는 예외 처리 코드 제거 또는 단순화 된 순수한 참조 카운팅 물건을 제거했습니다. 여기서 흥미로운 점은 gbo->currkey, gbo->currvaluegbo->tgtkey이 이터레이터의 마지막에 도달하면 NULL으로 설정되지 않고 PyIter_Next(gbo->it) == NULL 일 때 return NULL이기 때문에 마지막으로 발생한 값 (반복기의 마지막 항목)을 계속 가리 킵니다. .

이 작업을 마친 후에는 두 개의 grouper 개체가 있습니다. 첫 번째 파일은 tgtvalueFalse이고 두 번째 파일이 True입니다.당신이이 grouper들에 next를 호출 할 때의 어떤 일이 발생 살펴 보자 :

static PyObject * 
_grouper_next(_grouperobject *igo) 
{ 
    groupbyobject *gbo = (groupbyobject *)igo->parent; 
    PyObject *newvalue, *newkey, *r; 
    int rcmp; 

    if (gbo->currvalue == NULL) { 
     /* removed because irrelevant. */ 
    } 

    rcmp = PyObject_RichCompareBool(igo->tgtkey, gbo->currkey, Py_EQ); 
    if (rcmp <= 0) 
     /* got any error or current group is end */ 
     return NULL; 

    r = gbo->currvalue; /* this accesses the last value of the groupby object */ 
    gbo->currvalue = NULL; 
    gbo->currkey = NULL; 

    return r; 
} 

그래서 currvalue하지NULL, 그래서 첫 번째 if 가지 재미없는 기억. 귀하의 첫 번째 그룹에 대해 그것은 groupergroupby 개체의 tgtkey을 비교하여 서로 다르다는 것을 확인하고 즉시 return NULL이됩니다. 그래서 빈 목록이 있습니다. tgtkey S가 동일한 제 2 반복자

, 그래서 groupby 객체 (이터레이터 마지막 ​​발생 값인!)의 currvalue을 반환하지만, 이번에는 currvaluecurrkey의 설정 것 groupby의 개체는 NULL입니다. g1의 한 요소가 속하지 않은

import itertools 

>>> inputs = [(x > 5, x) for x in range(10)] + [(False, 10)] 
>>> (_, g1), (_, g2), (_, g3) = itertools.groupby(inputs, key=lambda x: x[0]) 
>>> list(g1) 
[(False, 10)] 
>>> list(g3) 
[] 

그건 : 파이썬으로 다시 전환


: 당신이 당신의 groupby의 마지막 그룹과 같은 tgtkeygrouper을하면 정말 재미있는 단점이 발생 첫 번째 그룹화 객체의 tgtkeyFalse이고 마지막 tgtkeyFalse이므로 첫 번째 그룹은 첫 번째 그룹이 첫 번째 그룹에 속한다고 생각했습니다. 또한 groupby 개체를 무효화하여 세 번째 그룹이 비어있게되었습니다.


모든 코드는 the Python source code에서 가져 왔지만 짧아졌습니다.