我试图理解我在今天早些时候写过的an answer中发现的一个怪癖。基本上,我正在从包含itertools.groupby
的生成器函数中产生组。我发现有趣的是,如果我在赋值的左侧解包生成器,则生成器的最后一个元素仍然存在。例如:
# 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))
在Cpython上,结果是:
$ python3 ~/sandbox/test_gb.py
[]
[(True, 9)]
CPython2.7和CPython3.5就属于这种情况。
在PyPy上,它会导致:
$ pypy ~/sandbox/test_gb.py
[]
[]
在这两种情况下,第一个空列表(" a
")很容易解释 - 来自itertools
的组会在下一个元素被消耗后立即消耗需要。由于我们没有将这些值保存在任何地方,因此他们会失去以太。
在我看来,PyPy
版本对于第二个空列表(" b
")也是有意义的...当解压缩时,我们消耗{{1}也是因为python 需要来查找之后的内容,以确保它不应该为错误数量的项目抛出b
来解包。但是出于某种原因,ValueError
版本保留了输入可迭代的最后一个元素......任何人都可以解释为什么会这样吗?
修改
这可能或多或少显而易见,但我们也可以将其写成:
CPython
并获得相同的结果......
答案 0 :(得分:2)
这是因为groupby
对象处理簿记,而grouper
对象只引用了他们的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;
由于您在解包grouper
对象时未对groupby
对象进行迭代,因此我暂时忽略它们。那么当您在groupby
上调用next
时,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->currvalue
,gbo->tgtkey
和NULL
都没有设置为return NULL
,他们仍会指向最后遇到的值(迭代器的最后一项),因为PyIter_Next(gbo->it) == NULL
时只有grouper
。
完成此操作后,您将拥有两个tgtvalue
个对象。第一个是False
True
,第二个是next
。让我们来看看当您在这些grouper
上致电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
,因此第一个tgtkey
分支并不感兴趣。对于您的第一个石斑鱼,它会比较grouper
和groupby
对象的return NULL
,看到它们不同,它会立即tgtkey
。所以你有一个空列表。
对于第二个迭代器,currvalue
是相同的,因此将返回groupby
对象的currvalue
(这是最后遇到的值)迭代器!),但这一次它会将currkey
对象的groupby
和NULL
设置为grouper
。
切换回python:如果tgtkey
的{{1}}与groupby
中的最后一组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)
[]
相同,则会发生真正有趣的怪癖:
g1
tgtkey
中的一个元素根本不属于第一个组 - 但是因为第一个石斑鱼对象的False
是tgtkey
而最后一个False
是groupby
第一个石斑鱼人认为它属于第一组。它还使clientSchema.methods.paginate = function(pageNo, callback){
var limit = 10;
var skip = pageNo * (limit - 1);
var totalCount;
//count documents
this.count({}, function(err, count)){
if(err){
totalCount = 0;
}
else{
totalCount = count;
}
}
if(totalCount == 0){
return callback('No Document in Database..', null);
}
//get paginated documents
this.find().skip(skip).limit(limit).exec(function(err, docs){
if(err){
return callback('Error Occured', null);
}
else if(!docs){
return callback('Docs Not Found', null);
}
else{
var result = {
"totalRecords" : totalCount,
"page": pageNo,
"nextPage": pageNo + 1,
"result": docs
};
return callback(null, result);
}
});
});
const Client = module.exports = mongoose.model('clients',clientSchema);
对象无效,因此第三组现在为空。
所有代码均来自the Python source code但已缩短。