一个Python优雅的数据分块方法详解

目录
  • 1.背景
  • 2.islice
    • 2.1示例
    • 2.2只指定步长
  • 3.iter
    • 3.1常规使用
    • 3.2进阶使用
  • 4.islice 和 iter 组合使用
  • 5.总结

1.背景

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

日前在做需求过程中有一个对大量数据分块处理的场景,具体来说就是几十万量级的数据,分批处理,每次处理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。通过这两个函数的配合,完成了分块功能。下面我详细介绍这两个方法的使用。

2.islice

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

函数定义如下:

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

2.1示例

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取到的数据。

2.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

3.iter

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

3.1常规使用

常见的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

3.2进阶使用

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)

4.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根据第二个参数判断退出循环。

5.总结

分块函数的优点:

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

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

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

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

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

以上就是一个Python优雅的数据分块方法详解的详细内容,更多关于Python数据分块的资料请关注我们其它相关文章!

(0)

相关推荐

  • python多线程分块读取文件

    本文实例为大家分享了python多线程分块读取文件的具体代码,供大家参考,具体内容如下 # _*_coding:utf-8_*_ import time, threading, ConfigParser ''' Reader类,继承threading.Thread @__init__方法初始化 @run方法实现了读文件的操作 ''' class Reader(threading.Thread): def __init__(self, file_name, start_pos, end_pos):

  • python分块读取大数据,避免内存不足的方法

    如下所示: def read_data(file_name): ''' file_name:文件地址 ''' inputfile = open(file_name, 'rb') #可打开含有中文的地址 data = pd.read_csv(inputfile, iterator=True) loop = True chunkSize = 1000 #一千行一块 chunks = [] while loop: try: chunk = dcs.get_chunk(chunkSize) chunks

  • Python多进程分块读取超大文件的方法

    本文实例讲述了Python多进程分块读取超大文件的方法.分享给大家供大家参考,具体如下: 读取超大的文本文件,使用多进程分块读取,将每一块单独输出成文件 # -*- coding: GBK -*- import urlparse import datetime import os from multiprocessing import Process,Queue,Array,RLock """ 多进程分块读取文件 """ WORKERS = 4

  • 一个Python优雅的数据分块方法详解

    目录 1.背景 2.islice 2.1示例 2.2只指定步长 3.iter 3.1常规使用 3.2进阶使用 4.islice 和 iter 组合使用 5.总结 1.背景 看到这个标题你可能想一个分块能有什么难度?还值得细说吗,最近确实遇到一个有意思的分块函数,写法比较巧妙优雅,所以写一个分享. 日前在做需求过程中有一个对大量数据分块处理的场景,具体来说就是几十万量级的数据,分批处理,每次处理100个.这时就需要一个分块功能的代码,刚好项目的工具库中就有一个分块的函数.拿过函数来用,发现还挺好用

  • Python处理文本数据的方法详解

    目录 前言 用python处理文本数据 用python处理数值型数据 前言 HI,好久不见,今天是关闭朋友圈的第60天,我是野蛮成长的AC-Asteroid. 人生苦短,我用Python,通过短短两周时间自学,从基础知识到项目实践,在这个过程中深刻体会到这款语言的魅力,今天带来一个有趣的项目,用Python处理文本数据,一起来看看今天的问题吧. 用python处理文本数据 实验目的 熟悉python的基本数据结构,以及文件的输入与输出. 实验数据 利用xxxx年xx机器学习会议的评测数据和评测任

  • 对python xlrd读取datetime类型数据的方法详解

    使用xlrd读取出来的时间字段是类似41410.5083333的浮点数,在使用时需要转换成对应的datetime类型,下面代码是转换的方法: 首先需要引入xldate_as_tuple函数 from xlrd import xldate_as_tuple 使用方法如下: #d是从excel中读取出来的浮点数 xldate_as_tuple(d,0) xldate_as_tuple第二个参数有两种取值,0或者1,0是以1900-01-01为基准的日期,而1是1904-01-01为基准的日期.该函数

  • 对python抓取需要登录网站数据的方法详解

    scrapy.FormRequest login.py class LoginSpider(scrapy.Spider): name = 'login_spider' start_urls = ['http://www.login.com'] def parse(self, response): return [ scrapy.FormRequest.from_response( response, # username和password要根据实际页面的表单的name字段进行修改 formdat

  • Python实现从文件中加载数据的方法详解

    前几篇都是手动录入或随机函数产生的数据.实际有许多类型的文件,以及许多方法,用它们从文件中提取数据来图形化. 比如之前python基础(12)介绍打开文件的方式,可直接读取文件中的数据,扩大了我们的数据来源.下面,将展示几种方法. 我们将使用内置的 csv 模块加载CSV文件 CSV文件是一种特殊的文本文件,文件中的数据以逗号作为分隔符,很适合进行数据的解析.先用excle建立如下表格和数据,另存为csv格式文件,放到代码目录下. 包含在Python标准库中自带CSV 模块,我们只需要impor

  • Python对象类型及其运算方法(详解)

    基本要点: 程序中储存的所有数据都是对象(可变对象:值可以修改 不可变对象:值不可修改) 每个对象都有一个身份.一个类型.一个值 例: >>> a1 = 'abc' >>> type(a1) str 创建一个字符串对象,其身份是指向它在内存中所处的指针(在内存中的位置) a1就是引用这个具体位置的名称 使用type()函数查看其类型 其值就是'abc' 自定义类型使用class 对象的类型用于描述对象的内部表示及其支持的方法和操作 创建特定类型的对象,也将该对象称为该类

  • python对于requests的封装方法详解

    由于requests是http类接口的核心,因此封装前考虑问题比较多: 1. 对多种接口类型的支持: 2. 连接异常时能够重连: 3. 并发处理的选择: 4. 使用方便,容易维护: 当前并未全部实现,后期会不断完善.重点提一下并发处理的选择:python的并发处理机制由于存在GIL的原因,实现起来并不是很理想,综合考虑多进程.多线程.协程,在不考虑大并发性能测试的前提下使用了多线程-线程池的形式实现.使用的是 concurrent.futures模块.当前仅方便支持webservice接口. #

  • python爬虫泛滥的解决方法详解

    我们可以把互联网上搬运数据的程序看成小蚂蚁,它们需要采集不同的食物带回洞里存储.但是大家也知道白蚁泛滥的事件,在我们的网络环境里,如果爬虫都集中在某几个位置,最直接的结果就是这个网站的拥挤.对于我们这些网站访问者而言也不是好事情,首先网页的页面会被卡住.网站的管理人员面对爬虫过多,这时候就要进行一系列的限制措施了,这里小编分了两个大的应对方向,从不同的角度进 行分析爬虫过多的解决思路. 一.识别爬虫 1. HTTP请求头 这算是最基础的网络爬虫识别了,正常的网络访问者都是通过浏览器对网站进行访问

  • python与xml数据的交互详解

    目录 一 什么是XML? 二 XML语法规则 1. xml语法规则 2. xml与html的区别 三 python与xml的交互 1. 获取标签对内的数据 2. 获取标签属性值 一 什么是XML? python与json数据的交互详情 在这篇文章中我们介绍了json是一种独立于编程语言和平台的数据存储和交换方式(格式),其实xml和json基本一样,也是一种用于进行数据存储和交换的方式,并且也独立于编程语言和平台.XML可扩展标记语言(英语:Extensible Markup Language,

  • Python实现文本特征提取的方法详解

    目录 1.字典文本特征提取 DictVectorizer() 1.1 one-hot编码 1.2 字典数据转sparse矩阵 2.英文文本特征提取 3.中文文本特征提取 4. TF-IDF 文本特征提取 TfidfVectorizer() 1.字典文本特征提取 DictVectorizer() 1.1 one-hot编码 创建一个字典,观察如下数据形式的变化: import pandas as pd from sklearn.feature_extraction import DictVecto

随机推荐