浅谈Python响应式类库RxPy


Posted in Python onJune 14, 2021

一、基本概念

Reactive X中有几个核心的概念,先来简单介绍一下。

1.1、Observable和Observer(可观察对象和观察者)

首先是Observable和Observer,它们分别是可观察对象和观察者。Observable可以理解为一个异步的数据源,会发送一系列的值。Observer则类似于消费者,需要先订阅Observable,然后才可以接收到其发射的值。可以说这组概念是设计模式中的观察者模式和生产者-消费者模式的综合体。

1.2、Operator(操作符)

另外一个非常重要的概念就是操作符了。操作符作用于Observable的数据流上,可以对其施加各种各样的操作。更重要的是,操作符还可以链式组合起来。这样的链式函数调用不仅将数据和操作分隔开来,而且代码更加清晰可读。一旦熟练掌握之后,你就会爱上这种感觉的。

1.3、Single(单例)

在RxJava和其变体中,还有一个比较特殊的概念叫做Single,它是一种只会发射同一个值的Observable,说白了就是单例。当然如果你对Java等语言比较熟悉,那么单例想必也很熟悉。

1.4、Subject(主体)

主体这个概念非常特殊,它既是Observable又是Observer。正是因为这个特点,所以Subject可以订阅其他Observable,也可以将发射对象给其他Observer。在某些场景中,Subject会有很大的作用。

1.5、Scheduler(调度器)

默认情况下Reactive X只运行在当前线程下,但是如果有需要的话,也可以用调度器来让Reactive X运行在多线程环境下。有很多调度器和对应的操作符,可以处理多线程场景下的各种要求。

1.6、Observer和Observable

先来看看一个最简单的例子,运行的结果会依次打印这些数字。这里的of是一个操作符,可以根据给定的参数创建一个新的Observable。创建之后,就可以订阅Observable,三个回调方法在对应的时机执行。一旦Observer订阅了Observable,就会接收到后续Observable发射的各项值。

from rx import of

ob = of(1, 2, 34, 5, 6, 7, 7)
ob.subscribe(
    on_next=lambda i: print(f'Received: {i}'),
    on_error=lambda e: print(f'Error: {e}'),
    on_completed=lambda: print('Completed')

)

这个例子看起来好像很简单,并且看起来没什么用。但是当你了解了Rx的一些核心概念,就会理解到这是一个多么强大的工具。更重要的是,Observable生成数据和订阅的过程是异步的,如果你熟悉的话,就可以利用这个特性做很多事情。

1.7、操作符

在RxPy中另一个非常重要的概念就是操作符了,甚至可以说操作符就是最重要的一个概念了。几乎所有的功能都可以通过组合各个操作符来实现。熟练掌握操作符就是学好RxPy的关键了。操作符之间也可以用pipe函数连接起来,构成复杂的操作链。

from rx import of, operators as op
import rx

ob = of(1, 2, 34, 5, 6, 7, 7)
ob.pipe(
    op.map(lambda i: i ** 2),
    op.filter(lambda i: i >= 10)
).subscribe(lambda i: print(f'Received: {i}'))

在RxPy中有大量操作符,可以完成各种各样的功能。我们来简单看看其中一些常用的操作符。如果你熟悉Java8的流类库或者其他函数式编程类库的话,应该对这些操作符感到非常亲切。

1.8、创建型操作符

首先是创建Observable的操作符,列举了一些比较常用的创建型操作符。

浅谈Python响应式类库RxPy

1.9、过滤型操作符

过滤型操作符的主要作用是对Observable进行筛选和过滤。

浅谈Python响应式类库RxPy

1.10、转换型操作符

浅谈Python响应式类库RxPy

1.11、算术操作符

浅谈Python响应式类库RxPy

1.12、Subject

Subject是一种特殊的对象,它既是Observer又是Observable。不过这个对象一般不太常用,但是假如某些用途还是很有用的。所以还是要介绍一下。下面的代码,因为订阅的时候第一个值已经发射出去了,所以只会打印订阅之后才发射的值。

from rx.subject import Subject, AsyncSubject, BehaviorSubject, ReplaySubject

# Subject同时是Observer和Observable

print('--------Subject---------')
subject = Subject()
subject.on_next(1)
subject.subscribe(lambda i: print(i))
subject.on_next(2)
subject.on_next(3)
subject.on_next(4)
subject.on_completed()
# 2 3 4

另外还有几个特殊的Subject,下面来介绍一下。

1.13、ReplaySubject

ReplaySubject是一个特殊的Subject,它会记录所有发射过的值,不论什么时候订阅的。所以它可以用来当做缓存来使用。ReplaySubject还可以接受一个bufferSize参数,指定可以缓存的最近数据数,默认情况下是全部。

下面的代码和上面的代码几乎完全一样,但是因为使用了ReplaySubject,所以所有的值都会被打印。当然大家也可以试试把订阅语句放到其他位置,看看输出是否会产生变化。

# ReplaySubject会缓存所有值,如果指定参数的话只会缓存最近的几个值
print('--------ReplaySubject---------')
subject = ReplaySubject()
subject.on_next(1)
subject.subscribe(lambda i: print(i))
subject.on_next(2)
subject.on_next(3)
subject.on_next(4)
subject.on_completed()
# 1 2 3 4

1.14、BehaviorSubject

BehaviorSubject是一个特殊的Subject,它只会记录最近一次发射的值。而且在创建它的时候,必须指定一个初始值,所有订阅它的对象都可以接收到这个初始值。当然如果订阅的晚了,这个初始值同样会被后面发射的值覆盖,这一点要注意。

# BehaviorSubject会缓存上次发射的值,除非Observable已经关闭
print('--------BehaviorSubject---------')
subject = BehaviorSubject(0)
subject.on_next(1)
subject.on_next(2)
subject.subscribe(lambda i: print(i))
subject.on_next(3)
subject.on_next(4)
subject.on_completed()
# 2 3 4

1.15、AsyncSubject

AsyncSubject是一个特殊的Subject,顾名思义它是一个异步的Subject,它只会在Observer完成的时候发射数据,而且只会发射最后一个数据。因此下面的代码仅仅会输出4.假如注释掉最后一行co_completed调用,那么什么也不会输出。

# AsyncSubject会缓存上次发射的值,而且仅会在Observable关闭后开始发射
print('--------AsyncSubject---------')
subject = AsyncSubject()
subject.on_next(1)
subject.on_next(2)
subject.subscribe(lambda i: print(i))
subject.on_next(3)
subject.on_next(4)
subject.on_completed()
# 4

1.16、Scheduler

虽然RxPy算是异步的框架,但是其实它默认还是运行在单个线程之上的,因此如果使用了某些会阻碍线程运行的操作,那么程序就会卡死。当然针对这些情况,我们就可以使用其他的Scheduler来调度任务,保证程序能够高效运行。

下面的例子创建了一个ThreadPoolScheduler,它是基于线程池的调度器。两个Observable用subscribe_on方法指定了调度器,因此它们会使用不同的线程来工作。

import rx
from rx.scheduler import ThreadPoolScheduler
from rx import operators as op

import multiprocessing
import time
import threading
import random


def long_work(value):
    time.sleep(random.randint(5, 20) / 10)
    return value


pool_schedular = ThreadPoolScheduler(multiprocessing.cpu_count())

rx.range(5).pipe(
    op.map(lambda i: long_work(i + 1)),
    op.subscribe_on(pool_schedular)
).subscribe(lambda i: print(f'Work 1: {threading.current_thread().name}, {i}'))

rx.of(1, 2, 3, 4, 5).pipe(
    op.map(lambda i: i * 2),
    op.subscribe_on(pool_schedular)
).subscribe(lambda i: print(f'Work 2: {threading.current_thread().name}, {i}'))

如果你观察过各个操作符的API的话,可以发现大部分操作符都支持可选的Scheduler参数,为操作符指定一个调度器。如果操作符上指定了调度器的话,会优先使用这个调度器;其次的话,会使用subscribe方法上指定的调度器;如果以上都没有指定的话,就会使用默认的调度器。

二、应用场景

好了,介绍了一些Reactive X的知识之后,下面来看看如何来使用Reactive X。在很多应用场景下,都可以利用Reactive X来抽象数据处理,把概念简单化。

2.1、防止重复发送

很多情况下我们都需要控制事件的发生间隔,比如有一个按钮不小心按了好几次,只希望第一次按钮生效。这种情况下可以使用debounce操作符,它会过滤Observable,小于指定时间间隔的数据会被过滤掉。debounce操作符会等待一段时间,直到过了间隔时间,才会发射最后一次的数据。如果想要过滤后面的数据,发送第一次的数据,则要使用throttle_first操作符。

下面的代码可以比较好的演示这个操作符,快速按回车键发送数据,注意观察按键和数据显示之间的关系,还可以把throttle_first操作符换成debounce操作符,然后再看看输出会发生什么变化,还可以完全注释掉pipe中的操作符,再看看输出会有什么变化。

import rx
from rx import operators as op
from rx.subject import Subject
import datetime

# debounce操作符,仅在时间间隔之外的可以发射

ob = Subject()
ob.pipe(
    op.throttle_first(3)
    # op.debounce(3)
).subscribe(
    on_next=lambda i: print(i),
    on_completed=lambda: print('Completed')
)

print('press enter to print, press other key to exit')
while True:
    s = input()
    if s == '':
        ob.on_next(datetime.datetime.now().time())
    else:
        ob.on_completed()
        break

2.2、操作数据流

如果需要对一些数据进行操作,那么同样有一大堆操作符可以满足需求。当然这部分功能并不是Reactive X独有的,如果你对Java 8的流类库有所了解,会发现这两者这方面的功能几乎是完全一样的。

下面是个简单的例子,将两个数据源结合起来,然后找出来其中所有的偶数。

import rx
from rx import operators as op
from rx.subject import Subject
import datetime

# 操作数据流
some_data = rx.of(1, 2, 3, 4, 5, 6, 7, 8)
some_data2 = rx.from_iterable(range(10, 20))
some_data.pipe(
    op.merge(some_data2),
    op.filter(lambda i: i % 2 == 0),
    # op.map(lambda i: i * 2)
).subscribe(lambda i: print(i))

再或者一个利用reduce的简单例子,求1-100的整数和。

import rx
from rx import operators as op
from rx.subject import Subject
import datetime

rx.range(1, 101).pipe(
    op.reduce(lambda acc, i: acc + i, 0)
).subscribe(lambda i: print(i))

以上就是浅谈Python响应式类库RxPy的详细内容,更多关于Python响应式类库RxPy的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python笔记(叁)继续学习
Oct 24 Python
简单了解python模块概念
Jan 11 Python
django中send_mail功能实现详解
Feb 06 Python
python安装教程
Feb 28 Python
python实现人民币大写转换
Jun 20 Python
python五子棋游戏的设计与实现
Jun 18 Python
用python生成(动态彩色)二维码的方法(使用myqr库实现)
Jun 24 Python
如何使用python爬虫爬取要登陆的网站
Jul 12 Python
Python3 tkinter 实现文件读取及保存功能
Sep 12 Python
关于Python中定制类的比较运算实例
Dec 19 Python
Python3爬虫发送请求的知识点实例
Jul 30 Python
用60行代码实现Python自动抢微信红包
Feb 04 Python
Python Django ORM连表正反操作技巧
Python scrapy爬取起点中文网小说榜单
教你用Python爬取英雄联盟皮肤原画
使用qt quick-ListView仿微信好友列表和聊天列表的示例代码
python基础入门之普通操作与函数(三)
python基础入门之字典和集合
Python OpenCV实现传统图片格式与base64转换
Jun 13 #Python
You might like
BBS(php & mysql)完整版(七)
2006/10/09 PHP
php header()函数使用说明
2008/07/10 PHP
PHP 多维数组排序(usort,uasort)
2010/06/30 PHP
php实现网站文件批量压缩下载功能
2015/10/28 PHP
PHP文件系统管理(实例讲解)
2017/09/19 PHP
JQuery的Ajax跨域请求原理概述及实例
2013/04/26 Javascript
JavaScript获取图片的原始尺寸以宽度为例
2014/05/04 Javascript
JS实现网页背景颜色与select框中颜色同时变化的方法
2015/02/27 Javascript
jQuery实现带有上下控制按钮的简单多行滚屏效果代码
2015/09/04 Javascript
JavaScript实现点击单元格改变背景色的方法
2016/02/12 Javascript
表单input项使用label同时引用Bootstrap库导致input点击效果区增大问题
2016/10/11 Javascript
基于ExtJs在页面上window再调用Window的事件处理方法
2017/07/26 Javascript
讲解vue-router之命名路由和命名视图
2018/05/28 Javascript
详解Vue2 添加对scss的支持
2019/01/02 Javascript
JavaScript使用canvas绘制随机验证码
2020/02/17 Javascript
基于canvasJS在PHP中制作动态图表
2020/05/30 Javascript
使用python搭建Django应用程序步骤及版本冲突问题解决
2013/11/19 Python
详解详解Python中writelines()方法的使用
2015/05/25 Python
Python网络编程之TCP套接字简单用法示例
2018/04/09 Python
使用Anaconda3建立虚拟独立的python2.7环境方法
2018/06/11 Python
Python中fnmatch模块的使用详情
2018/11/30 Python
DES加密解密算法之python实现版(图文并茂)
2018/12/06 Python
Python数据抓取爬虫代理防封IP方法
2018/12/23 Python
PyTorch的深度学习入门教程之构建神经网络
2019/06/27 Python
python实现的登录与提交表单数据功能示例
2019/09/25 Python
Pytorch上下采样函数--interpolate用法
2020/07/07 Python
Pandas DataFrame求差集的示例代码
2020/12/13 Python
CSS3关于z-index不生效问题的解决
2020/02/19 HTML / CSS
Lulu Guinness露露·吉尼斯官网:红唇包
2019/02/03 全球购物
游戏商店:Eneba
2020/04/25 全球购物
幼儿园教师考核制度
2014/02/01 职场文书
超市员工管理制度
2015/08/06 职场文书
你会写报告?产品体验报告到底该怎么写?
2019/08/14 职场文书
导游词之广州陈家祠
2019/10/21 职场文书
教你如何使用Python下载B站视频的详细教程
2021/04/29 Python
vue项目支付功能代码详解
2022/02/18 Vue.js