2017-12-27 195 views
-1

有人可以解释groupby操作和this SO帖子上使用的lambda函数吗?带有itertools count()和groupby()的lambda函数

key=lambda k, line=count(): next(line) // chunk

import tempfile 
from itertools import groupby, count 

temp_dir = tempfile.mkdtemp() 

def tempfile_split(filename, temp_dir, chunk=4000000): 
    with open(filename, 'r') as datafile: 

    # The itertools.groupby() function takes a sequence and a key function, 
    # and returns an iterator that generates pairs. 

    # Each pair contains the result of key_function(each item) and 
    # another iterator containing all the items that shared that key result. 

     groups = groupby(datafile, key=lambda k, line=count(): next(line) // chunk) 
     for k, group in groups: 

      print(key, list(group)) 

      output_name = os.path.normpath(os.path.join(temp_dir + os.sep, "tempfile_%s.tmp" % k)) 
      for line in group: 
       with open(output_name, 'a') as outfile: 
        outfile.write(line) 

编辑:我花了一段时间来回绕与GROUPBY使用lambda函数我的头。我不认为我很了解他们中的任何一个。

Martijn解释得很好,但我有一个后续问题。为什么line=count()每次都作为lambda函数的参数传递?我试图在变量的外部分配变量linecount()一次。

line = count() 
    groups = groupby(datafile, key=lambda k, line: next(line) // chunk) 

和它导致TypeError: <lambda>() missing 1 required positional argument: 'line'

另外,直接在λ表达式呼吁count()next,导致在输入文件中的所有行得到聚集在一起,即由groupby函数生成的单个密钥。

groups = groupby(datafile, key=lambda k: next(count()) // chunk) 

我自己学习Python,所以任何帮助或指向参考材料/ PyCon会谈的指针都非常感谢。任何真的!

回答

3

itertools.count()是一个递增整数的无限迭代器。

lambda将实例存储为关键字参数,所以每次调用lambda时本地变量line都会引用该对象。 next()前进一个迭代,检索下一个值:

>>> from itertools import count 
>>> line = count() 
>>> next(line) 
0 
>>> next(line) 
1 
>>> next(line) 
2 
>>> next(line) 
3 

所以next(line)检索序列中的下一个计数,并且通过chunk(只取除法的整数部分)除以该值。 k参数被忽略。

因为使用了整数除法,所以lambda的结果将是chunk重复递增的整数;如果chunk是3,那么你得到0三次,然后1三次,然后2三次,等:

>>> chunk = 3 
>>> l = lambda k, line=count(): next(line) // chunk 
>>> [l('ignored') for _ in range(10)] 
[0, 0, 0, 1, 1, 1, 2, 2, 2, 3] 
>>> chunk = 4 
>>> l = lambda k, line=count(): next(line) // chunk 
>>> [l('ignored') for _ in range(10)] 
[0, 0, 0, 0, 1, 1, 1, 1, 2, 2] 

正是这导致值groupby()datafile迭代的,生产的chunk线组。

当通过groupby()结果与for k, group in groups:循环,k在于lambda产生,结果按分组的数目;代码中的for循环忽略了这一点。 group是来自datafile的行的迭代,并且将始终包含chunk行。

+0

谢谢。感谢你的帮助! – theguyoverthere

1

为了响应更新的OP ...

itertools.groupby迭代器提供了将项目组合在一起的方法,在定义键函数时提供更多控制。请参阅how itertools.groupby() works

lambda函数是编写常规函数的功能性的简写方式。例如:

>>> keyfunc = lambda k, line=count(): next(line) 

等效于此常规功能:

>>> def keyfunc(k, line=count()): 
...  return next(line) // chunk 

关键词:迭代器,函数式编程,匿名函数


详细

为什么line=count()每次都作为参数传递给lambda函数?

原因与普通功能相同。 line参数本身是一个位置参数。当赋值时,它将成为默认关键字参数。查看更多关于positional vs. keyword arguments

您仍然可以通过分配结果关键字参数定义line=count()之外的功能:

>>> chunk = 3 
>>> line=count() 
>>> keyfunc = lambda k, line=line: next(line) // chunk  # make `line` a keyword arg 
>>> [keyfunc("") for _ in range(10)] 
[0, 0, 0, 1, 1, 1, 2, 2, 2, 3] 
>>> [keyfunc("") for _ in range(10)] 
[3, 3, 4, 4, 4, 5, 5, 5, 6, 6]        # note `count()` continues 

...直接在lambda表达式调用next上count(),导致了所有行输入文件中获取聚集在一起,即是由groupby函数生成一个密钥...

尝试以下实验count()

>>> numbers = count() 
>>> next(numbers) 
0 
>>> next(numbers) 
1 
>>> next(numbers) 
2 

正如预期的那样,你会发现next()count()迭代器产生的下一个项目。 (一个类似的函数被称为迭代器,用for循环迭代)。这里唯一的是发电机不会重置 - next()只是给出该行中的下一项(如前例所示)。

@Martijn Pieters指出next(line) // chunk计算一个Flooring整数,groupby使用该整数来标识每条线(将相似的ID集中在一起的相似线条),这也是预期的。有关groupby如何工作的更多信息,请参阅参考资料。

参考

+1

非常感谢您的澄清! – theguyoverthere