背景

看到这个标题你可能想一个分块能有什么难度?还值得细说吗,最近确实遇到一个有意思的分块函数,写法比较巧妙优雅,所以写一个分享。

日前在做需求过程中有一个对大量数据分块处理的场景,具体来说就是几十万量级的数据,分批处理,每次处理100个。这时就需要一个分块功能的代码,刚好项目的工具库中就有一个分块的函数。拿过函数来用,发现还挺好用的,传入列表和分块大小,然后就能遍历取出分好的数据。调用方式如下:

from xxx import chunk_fun

chunk_list = chunk_fun(arr, 100) # 对数据进行分块,指定块的大小为100

for chunk in chunk_list:
print(chunk)

然后我就对这个分块函数产生了兴趣,想看看这个小功能是如何实现的。如果让我来写一个分块函数,我知道Python中range函数可以指定步长,用这个特性就完全可以优雅的实现分块功能。

arr = [1,2,3,4,5,6,7,8,9,10]

step = 3
for i in range(0, len(arr), step):
chunk = arr[i:i+step]
print(chunk)
>>>
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]

没想到看到源码竟然才用了3行代码就实现了分块,不仅支持列表等线性结构的分块,而且还支持集合这种非线性结构的分块。这让我感到震撼,这3行代码不是最优雅的分块方法,也是接近最优雅的分块方法了。废话不多说,先上代码:

from itertools import islice

def chunk_list(it, limit):
it = iter(it)
return iter(lambda: list(islice(it, limit)), [])

对于这3行代码,有多少人第一眼没看出功能的呢?反正我第一眼看的是一脸懵逼,有种不明觉厉的感觉。

首先来看一下这个分块函数的使用。

set_num = {1,2,3,4,5,6,7}

for temp_list in chunk_list(set_num, 2):
print(temp_list)
>>>
[1, 2]
[3, 4]
[5, 6]
[7]

完全没有使用显示循环就把分块这件事安排的明明白白的,而且才用了3行代码,不包括函数的定义就只剩下2行代码就搞定了。这是我见过最优雅的分块方法。然后我就花一点时间搞明白代码是如何工作的。

那么这个分块功能是如何实现的呢?主要有两个知识点:迭代器切片islice+迭代器生成函数iter。通过这两个函数的配合,完成了分块功能。下面我详细介绍这两个方法的使用。

islice

islice是python内置模块itertool中的一个函数,功能是对迭代器切片,传入一个迭代器,返回从迭代器中的start位置到stop位置的元素,可缺省起始位置。

函数定义如下:

islice(iterable, [start, ] stop [, step])
  • iterable 可迭代对象
  • start 切片开始位置
  • stop 切片结束位置
  • step 步长

示例

from itertools import islice
from collections import Iterator iter_list = iter([1,2,3,4,5,6,7])
slice = islice(iter_list, 0, 7, 2) print(slice)
>>>
<itertools.islice object at 0x7fc864e5aef8> print(isinstance(slice, Iterator))
>>>
True print(list(slice))
>>>
[1, 3, 5, 7]

指定start为0,stop为7,step2,得到一个新的迭代器,元素是从1开始的步长为2取到的数据。

只指定步长

islice可以只传入步长参数,当没有start和stop时,默认从start为起点,stop为终点。

from itertools import islice

iter_list = iter([1,2,3,4,5,6,7])

slice = islice(iter_list, 2)
print(list(slice)) slice = islice(iter_list, 2)
print(list(slice)) slice = islice(iter_list, 2)
print(list(slice)) slice = islice(iter_list, 2)
print(list(slice)) slice = islice(iter_list, 2)
print(list(slice)) slice = islice(iter_list, 2)
print(list(slice)) slice = islice(iter_list, 2)
print(list(slice))
>>>
[1, 2]
[3, 4]
[5, 6]
[7]
[]
[]
[]

除了获得切片之外,以上代码还说明了两个非常重要的特征,是否有留意?

第一个:那就是切片能够保留位置信息,多次调用切片功能,当前取值是从上一次结尾的地方开始的。比如第一次取值1、2,结尾位置是3;第二次就从3开始取到了3、4;第三次从5开始取到5、6。原因islice是对迭代器切片,迭代器取值会记住位置信息。

第二个:当迭代完所有的元素之后,返回空数组。将原始列表迭代完之后不会报错,而是一直返回空数组。

有了上面这种使用方法就为分块提供了可能性,如果要使用islice来分块,只需要在一个死循环里调用islice取值,当取值为[]时退出循环即可。可通过如下方法实现:

from itertools import islice

def chunk(it, limit):
it = iter(it)
while True:
temp = list(islice(it, limit))
if temp == []:
break
yield temp iter_list = iter([1,2,3,4,5,6,7]) for temp_list in chunk(iter_list, 2):
print(temp_list)
>>>
[1, 2]
[3, 4]
[5, 6]
[7]

这样就完成了使用islice就完成了分块的功能,但是看上可不是很优雅,又有while循环,又有yield关键值。



不优雅关键在于需要循环调用切片函数而且还需要判断跳出循环的条件。那么有没有一个既可以循环调用又能判断结束条件的函数呢?还真的有的,那就是iter

iter

iter()方法用来创建迭代器,iter()本质上就是调用可迭代对象的__iter__方法,返回一个迭代器对象。关于iter的常规使用,可参见另一篇文章一篇文章讲清楚迭代器和生成器

常规使用

常见的iter的使用方法是,对一个可迭代对象调用iter方法,让其变成一个迭代器,可以通过next取值。

list = [1,2,3,4,5,6,7]
iter_list = iter(list) print(next(iter_list))
print(next(iter_list))
print(next(iter_list))
>>>
1
2
3

进阶使用

iter还有一种不常用的方法,来看iter函数的定义

iter(object[, sentinel])
  • object -- 支持迭代的集合对象。
  • sentinel -- 如果传递了第二个参数,则参数 object 必须是一个可调用的对象(如,函数),此时,iter 创建了一个迭代器对象,每次调用这个迭代器对象的__next__()方法时,都会调用 object。

也就是说如果iter函数如果传了第二个参数,那么第一个参数就必须是一个可调用对象,每一次调用next函数时,实际上就是调用第一个参数,如果结果等于第二个参数,那就是迭代完成了。

听起来有点弯弯绕,跑一个示例就清楚了。

import random

def get_random():
return random.randint(1,5) demo = iter(get_random, 4) print(next(demo))
print(next(demo))
print(next(demo))
print(next(demo))
print(next(demo))
print(next(demo))
print(next(demo))
>>>
3
2
1
2
Traceback (most recent call last):
File "islice_demo.py", line 62, in <module>
print(next(demo))
StopIteration

iter传入第一个参数是一个函数get_random,函数的功能是获取1-5之间的随机数,第二个参数是4,也就是说如果函数返回的数值是4,那算迭代完成。每一次调用next取值就会调用get_random函数,直到结果为4。当迭代完成之后,会抛出一个StopIteration的异常。

上面是通过next调用,如果是通过for循环调用,就不会抛出异常,for循环会捕获异常。

import random

def get_random():
return random.randint(1,5) demo = iter(get_random, 4) for i in demo:
print(i)
>>>
1
5

这个功能刚好可以实现调用某一个函数,又能判断退出条件,如果现在再把分块的代码摆上来,能否实现优雅的分块呢?

from itertools import islice

def chunk(it, limit):
it = iter(it)
while True:
temp = list(islice(it, limit))
if temp == []:
break
yield temp iter_list = iter([1,2,3,4,5,6,7]) for temp_list in chunk(iter_list, 2):
print(temp_list)

islice 和 iter 组合使用

islice 提供分块功能,iter 提供循环调用islice的功能和判断退出的功能,最后在两个函数的的配合使用下,完成了优雅的分块。

便于理解的示例:

from itertools import islice

def chunk_list(it, limit):
it = iter(it)
# 实现分块的内函数
def iter_fun():
return list(islice(it, limit)) return iter(iter_fun, []) it = [1,2,3,4,5,6,7]
chunk = chunk_list(it, 2) print(next(chunk))
print(next(chunk))
print(next(chunk))
print(next(chunk))
print(next(chunk))
>>>
[1, 2]
[3, 4]
[5, 6]
[7]
Traceback (most recent call last):
File "chunk_demo.py", line 44, in <module>
print(next(chunk))
StopIteration

最终的示例:

from itertools import islice

def chunk_list(it, limit):
it = iter(it)
return iter(lambda: list(islice(it, limit)), [])

iter 第一个参数传入lambda表达式,有一个更贴合场景的叫法是无头函数。 lambda: list(islice(it, limit))。没有传入参数,函数体是islice(it, limit);

第二个参数是空列表[],作为迭代退出的判断。

工作原理

当使用for循环遍历分块函数时,每循环一次就通过iter调用islice一次,将分块结果list处理,然后返回。直到islice返回空列表,iter根据第二个参数判断退出循环。

总结

分块函数的优点

  1. 实现很优雅
  2. 支持的分块的数据类型丰富。不单是列表,只要能够迭代的都可以。

分块的实现主要有两个思路

  1. 使用islice来完成迭代器切片,实现分块的功能。但是需要多次调用islice直到迭代完成
  2. iter 提供调用功能,并判断迭代退出条件

有兴趣的读者可看看iter的实现,能够明白为什么迭代器能记住位置,这是本文分块的一个核心知识点。

这一个简单的代码让我感受到Python的奇妙,两个函数默契的配合,十分优雅的完成了分块功能。同时我明白Python语言的宗旨是简易优雅,但是简易并不简单,想要实现优雅需要扎实的基础和深厚的知识储备。追求Pythonic,需要学习理解的还有很多。

一个Python中优雅的数据分块方法的更多相关文章

  1. python统计一个文本中重复行数的方法

    python统计一个文本中重复行数的方法 这篇文章主要介绍了python统计一个文本中重复行数的方法,涉及针对Python中dict对象的使用及相关本文的操作,具有一定的借鉴价值,需要的朋友可以参考下 ...

  2. Navicat的使用与python中使用MySQL的基本方法

    Navicat的使用与python中使用MySQL的基本方法 Navicat的下载及安装 下载地址 http://www.navicat.com.cn/download/navicat-premium ...

  3. 清空SQL Server数据库中所有表数据的方法(转)

    清空SQL Server数据库中所有表数据的方法 其实删除数据库中数据的方法并不复杂,为什么我还要多此一举呢,一是我这里介绍的是删除数据库的所有数据,因为数据之间可能形成相互约束关系,删除操作可能陷入 ...

  4. Python中optionParser模块的使用方法[转]

    本文以实例形式较为详尽的讲述了Python中optionParser模块的使用方法,对于深入学习Python有很好的借鉴价值.分享给大家供大家参考之用.具体分析如下: 一般来说,Python中有两个内 ...

  5. 清空SQL Server数据库中所有表数据的方法

    原文:清空SQL Server数据库中所有表数据的方法 其实删除数据库中数据的方法并不复杂,为什么我还要多此一举呢,一是我这里介绍的是删除数据库的所有数据,因为数据之间可能形成相互约束关系,删除操作可 ...

  6. Python中防止sql注入的方法详解

    SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编程时的疏忽,通过SQL语句,实现无帐号登录,甚至篡改数据库.下面这篇文章主要给大家介绍了关于Python中 ...

  7. python中文件操作的其他方法

    前面介绍过Python中文件操作的一般方法,包括打开,写入,关闭.本文中介绍下python中关于文件操作的其他比较常用的一些方法. 首先创建一个文件poems: p=open('poems','r', ...

  8. Laravel5.4框架中视图共享数据的方法详解

    本文实例讲述了Laravel5.4框架中视图共享数据的方法.分享给大家供大家参考,具体如下: 每个人都会遇到这种情况:某些数据还在每个页面进行使用,比如用户信息,或者菜单数据,最基本的做法是在每个视图 ...

  9. python中验证码连通域分割的方法详解

    python中验证码连通域分割的方法详解 这篇文章主要给大家介绍了关于python中验证码连通域分割的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用python具有一定的参考学习价值,需 ...

随机推荐

  1. PRODUCER配置加载

    1.入口 Kafka通过new一个KafkaProducer将配置项进行加载.将用户定义的properties作为参数,构造成一个ProducerConfig对象. public KafkaProdu ...

  2. Zookeeper Watcher 机制 -- 数据变更通知 ?

    Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务 端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通 知来实现分布式的通知功 ...

  3. 机器学习之近邻算法模型(KNN)

    1..导引 如何进行电影分类 众所周知,电影可以按照题材分类,然而题材本身是如何定义的?由谁来判定某部电影属于哪 个题材?也就是说同一题材的电影具有哪些公共特征?这些都是在进行电影分类时必须要考虑的问 ...

  4. docker学习-01-安装docker

    [root@localhost firstDocker]# cat /etc/centos-release CentOS Linux release 7.6.1810 (Core) [root@loc ...

  5. 攻防世界 web_php_include

    Web_php_include 进入题目源码直接出来了 <?php show_source(__FILE__); echo $_GET['hello']; $page=$_GET['page'] ...

  6. H5复制粘贴双端适配的解决方案(终极版)

    前言 最终适配所有机型的方案基于clipboardjs官网https://clipboardjs.com/ 这个库由几个不同的CDN提供商托管.选择你最喜欢的:) jsDelivr <scrip ...

  7. Android项目如何打包成安装包.apk

    1.Build -> Generate Signed APK 2.APK -> Next 3.输入Key store path以及密码 ->next(如果是第一次则需要新建,新建教程 ...

  8. formData请求接口传递参数格式

    element ui组件方法的传递 //引入 组件. <el-upload class="avatar-uploader" :action="action" ...

  9. js知识梳理5:关于函数的要点梳理(1)

    写在前面 注:这个系列是本人对js知识的一些梳理,其中不少内容来自书籍:Javascript高级程序设计第三版和JavaScript权威指南第六版,感谢它们的作者和译者.有发现什么问题的,欢迎留言指出 ...

  10. python——如何import包目录

    文件位置 文件所在位置包括 , 源根目录的位置 该文件位置(也可以叫相对位置). 导入包的时候会从文件位置进行查找,并导入. 导入包 1. 什么是包? pycharm中包的图片 其中文件夹上有个圆点的 ...