11 迭代器|生成器|协程
文章目录
- 迭代器
- 可迭代对象
- 可迭代对象的本质
- iter()函数与 next()函数
- 迭代器 Iterator
- 样例
- for...in...循环的本质
- 使用的场景--斐波那契数列
- list和tuple也可以接收可迭代对象
- 生成器
- 简介
- 创建生成器
- 方法一
- 方法二
- 总结
- 使用 send 唤醒
- 协程
- 协程和线程差异
- 简单实现协程
- greenlet
- gevent
- 安装
- gevent 的使用方法
- 给程序打补丁
- gevent的常用方法
- python最新接口
- 最后的示例
- 并行下载
迭代器
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
可迭代对象
我们已经知道可以对list、tuple、str 等类型的数据使用 for…in…的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。
我们把可以通过 for…in…这类语句迭代读取一条数据供我们使用的对象称之为可迭代对象(Iterable)。
可以使用 isinstance() 判断该对象是否是一个可迭代的对象
In [50]: from collections.abc import Iterable
In [51]: isinstance([], Iterable)
Out[51]: True
In [52]: isinstance({}, Iterable)
Out[52]: True
In [53]: isinstance('abc', Iterable)
Out[53]: True
In [54]: isinstance(mylist, Iterable)
Out[54]: False
In [55]: isinstance(100, Iterable)
Out[55]: False
可迭代对象的本质
可迭代对象通过__iter__方法向我们提供一个迭代器,我们在迭代一个可迭代对
象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据. 那么也就是说,一个具备了__iter__方法的对象,就是一个可迭代对象,所以关键就在于我们要如何去重写这一个__iter__方法。
iter()函数与 next()函数
list、tuple 等都是可迭代对象,我们可以通过 iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用 next()函数来获取下一条数据。
iter()函数实际上就是调用了可迭代对象的__iter__方法
注意,当我们已经迭代完最后一个数据之后,再次调用 next()函数会抛出StopIteration 的异常,来告诉我们所有数据都已迭代完成,不用再执行 next()函数了
迭代器 Iterator
通过上面的分析,我们已经知道,迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用 next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用 next()函数的时候,调用的就是迭代器对象的__next__方法(Python3 中是对象的__next__方法)。所以,我们要想构造一个迭代器,就要实现它的__next__方法。但这还不够,python 要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可。
一个实现了__iter__方法和__next__方法的对象,就是迭代器。
样例
from collections.abc import Iterable
# 自定义可迭代对象
class MyList:def __init__(self):self.mylist = []def add(self,num):self.mylist.append(num)# 此处是关键,定义了这一个之后,就相当于是让这个对象是可迭代对象def __iter__(self):return MyIterator(self)# 自定义迭代器
class MyIterator:def __init__(self,mylist):self.mylist:MyList = mylistself.current = 0def __iter__(self):return selfdef __next__(self):if self.current > len(self.mylist.mylist) - 1:# 此处一定要实现抛出StopIteration异常raise StopIterationelse:current = self.currentself.current = self.current + 1return self.mylist.mylist[current]if __name__ == '__main__':mylist = MyList()mylist.add(1)mylist.add(2)mylist.add(3)for i in mylist:print(i)print(isinstance(mylist,Iterable))
for…in…循环的本质
for item in Iterable 循环的本质就是先通过 iter()函数获取可迭代对象 Iterable 的迭代器,然后对获取到的迭代器不断调用 next()方法来获取下一个值并将其赋值给item,当遇到 StopIteration 的异常后循环
使用的场景–斐波那契数列
class FibIterator(object):"""斐波那契数列迭代器"""def __init__(self, n):""":param n: int, 指明生成数列的前 n 个数"""self.n = n# current 用来保存当前生成到数列中的第几个数了self.current = 0# num1 用来保存前前一个数,初始值为数列中的第一个数 0self.num1 = 0# num2 用来保存前一个数,初始值为数列中的第二个数 1self.num2 = 1def __next__(self):"""被 next()函数调用来获取下一个数"""if self.current < self.n:num = self.num1self.num1, self.num2 = self.num2, self.num1+self.num2self.current += 1return numelse:raise StopIterationdef __iter__(self):"""迭代器的__iter__返回自身即可"""return selfif __name__ == '__main__':fib = FibIterator(10)for num in fib:print(num, end=" ")
list和tuple也可以接收可迭代对象
除了 for 循环能接收可迭代对象,list、tuple 等也能接收
li = list(FibIterator(15))
print(li)
tp = tuple(FibIterator(6))
print(tp)
生成器
简介
利用迭代器,我们可以在每次迭代获取数据(通过 next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。
创建生成器
方法一
实际上就是把一个列表生成式的 [ ] 改成 ( )
my_generator = ( x*2 for x in range(5))
print(my_generator)
print(next(my_generator))
print(next(my_generator))
print(next(my_generator))
print(next(my_generator))
print(next(my_generator))
对于生成器来说,我们可以按照迭代器的使用方法来使用,即可以通过 next()函数、for 循环、list()等方法进行迭代
方法二
generator 非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。
在使用生成器实现的方式中,我们将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的 return 换成了 yield,此时新定义的函数便不再是函数,而是一个生成器了。简单来说:只要在 def 中有yield 关键字的 就称为 生成器此时按照调用函数的方式( 案例中为 F = fib(5) )使用生成器就不再是执行函数体了,而是会返回一个生成器对象( 案例中为 F ),然后就可以按照使用迭代器的方式来使用生成器了。
# 含有yield的函数称为生成器
def fib(n):current = 0num1, num2 = 0, 1while current < n:num = num1num1, num2 = num2, num1 + num2current += 1# print(num,end=' ')yield numreturn 'done'# F是一个生成器,支持next
F=fib(10)print(F)# for i in F:
# print(i,end=' ')# 迭代生成
l=[ i for i in F ]
print(l)# 想要拿取return当中的值
try:next(F)
except StopIteration as e:print("\n返回值为:{}".format(e.value))
用 for 循环调用 generator 时,发现拿不到 generator 的 return 语句的返回
值。如果想要拿到返回值,必须捕获 StopIteration 错误,返回值包含在
StopIteration 的 value 中
这里还有一个小细节,读者可能回想为什么我们在使用已经做好的list时候对于list进行for in处理,可以多次处理多次拿值,但是对于我们上文写的迭代器为什么不行?原因就在于我们实际上list叫做可迭代对象,它实际上所采用的是我们最上面的那种手法,定义两个类的手法,每次迭代,都会有一个新的迭代器来运行。
总结
- 使用了 yield 关键字的函数不再是函数,而是生成器。(使用了 yield 的函数
就是生成器) - yield 关键字有两点作用:
– 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
– 将 yield 关键字后面表达式的值作为返回值返回,此时可以理解为起到了 return 的作用 - 可以使用 next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
- Python3 中的生成器可以使用 return 返回最终运行的返回值
使用 send 唤醒
我们除了可以使用 next()函数来唤醒生成器继续执行外,还可以使用 send()函数来唤醒执行。使用 send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。
def gen():i=0while i<5:temp=yield iprint(temp)i+=1g=gen()next(g)
g.send('hello')
协程
协程,又称微线程,纤程。英文名 Coroutine。
协程是 python 个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。 为啥说它是一个执行单元,因为它自带 CPU 上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU 上下文那么程序还是可以运行的。
通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。
协程和线程差异
在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU 上下文这么简单。操作系统为了程序运行的高效性每个线程都有自己缓存 Cache 等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作 CPU 的上下文,所以一秒钟切换个上百万次系统都抗的住。
简单实现协程
import timedef work1():while True:print("----work1---")yieldtime.sleep(0.5)def work2():while True:print("----work2---")yieldtime.sleep(0.5)def main():w1 = work1()w2 = work2()while True:next(w1)next(w2)if __name__ == "__main__":main()
greenlet
基本的使用方法:
from greenlet import greenlet
import timedef test1():while True:print("---A--")gr2.switch()time.sleep(0.5)def test2():while True:print("---B--")gr1.switch()time.sleep(0.5)gr1 = greenlet(test1)
gr2 = greenlet(test2)
#切换到 gr1 中运行
gr1.switch()
gevent
greenlet 已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,python 还有一个比 greenlet 更强大的并且能够自动切换任务的模块 gevent,其原理是当一个 greenlet 遇到 IO(指的是 input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的 greenlet,等到 IO 操作完成,再在适当的时候切换回来继续执行。
由于 IO 操作非常耗时,经常使程序处于等待状态,有了 gevent 为我们自动切换协程,就保证总有协程在运行,而不是等待 IO时间
参考链接
安装
pip install gevent -i https://pypi.tuna.tsinghua.edu.cn/simple
gevent 的使用方法
gevent.spawn 接口使用方法gevent.spawn(函数名,传参)
import geventdef f(n):for i in range(n):print(gevent.getcurrent(), i)#用来模拟一个耗时操作,注意不是 time 模块中的 sleepgevent.sleep(1)g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
运行结果:
<Greenlet at 0x7fa70ffa1c30: f(5)> 0
<Greenlet at 0x7fa70ffa1870: f(5)> 0
<Greenlet at 0x7fa70ffa1eb0: f(5)> 0
<Greenlet at 0x7fa70ffa1c30: f(5)> 1
<Greenlet at 0x7fa70ffa1870: f(5)> 1
<Greenlet at 0x7fa70ffa1eb0: f(5)> 1
<Greenlet at 0x7fa70ffa1c30: f(5)> 2
<Greenlet at 0x7fa70ffa1870: f(5)> 2
<Greenlet at 0x7fa70ffa1eb0: f(5)> 2
<Greenlet at 0x7fa70ffa1c30: f(5)> 3
<Greenlet at 0x7fa70ffa1870: f(5)> 3
<Greenlet at 0x7fa70ffa1eb0: f(5)> 3
<Greenlet at 0x7fa70ffa1c30: f(5)> 4
<Greenlet at 0x7fa70ffa1870: f(5)> 4
<Greenlet at 0x7fa70ffa1eb0: f(5)> 4
给程序打补丁
猴子补丁作用:
monkey patch 指的是在执行时动态替换,通常是在 startup 的时候. 用过 gevent 就会知道,会在最开头的地方 gevent.monkey.patch_all();把标准库中的 thread/socket 等给替换掉.这样我们在后面使用 socket的时候能够跟寻常一样使用,无需改动不论什么代码,可是它变成非堵塞的了
from gevent import monkey
import gevent
import random
import time# 这句话是关键
monkey.patch_all()def coroutine_work(coroutine_name):for i in range(10):print(coroutine_name, i)time.sleep(random.random())gevent.joinall([gevent.spawn(coroutine_work, "work1"),gevent.spawn(coroutine_work, "work2")
])
gevent的常用方法
常用方法 | 说明 |
---|---|
gevent.spawn() | 创建一个普通的 Greenlet 对象并切换 |
gevent.spawn_later(seconds=3) | 延时创建一个普通的 Greenlet 对象并切换 |
gevent.spawn_raw() | 创建的协程对象属于一个组 |
gevent.getcurrent() | 返回当前正在执行的 greenlet |
gevent.joinall(jobs) | 将协程任务添加到事件循环,接收一个任务列表 |
gevent.wait() | 可以替代 join 函数等待循环结束,也可以传入协程对象列表 |
gevent.kill() | 杀死一个协程 |
gevent.killall() | 杀死一个协程列表里的所有协程 |
monkey.patch_all() | 非常重要,会自动将 python 的一些标准模块替换成 gevent框架 |
python最新接口
链接
官方文档:
链接
链接
最后的示例
并行下载
from gevent import monkey
import gevent
import urllib.request# 有耗时操作时需要
monkey.patch_all()def my_downLoad(url):print('GET: %s' % url)resp = urllib.request.urlopen(url)data = resp.read()print('%d bytes received from %s.' % (len(data), url))gevent.joinall([gevent.spawn(my_downLoad, 'http://www.baidu.com/'),gevent.spawn(my_downLoad, 'http://www.cskaoyan.com/'),gevent.spawn(my_downLoad, 'http://www.qq.com/'),
])
以及
from gevent import monkey
import gevent
import urllib.request#有 IO 才做时需要这一句
monkey.patch_all()def my_downLoad(file_name, url):print('GET: %s' % url)resp = urllib.request.urlopen(url)data = resp.read()with open(file_name, "wb") as f:f.write(data)print('%d bytes received from %s.' % (len(data), url))gevent.joinall([gevent.spawn(my_downLoad,"7a082c0dde36eac2205a088397aaf295.jpg",'http://qzs.qq.com/qzone/v6/v6_config/upload/7a082c0dde36eac2205a088397aaf295.jpg'),gevent.spawn(my_downLoad,"da8e974dc_is.jpg",'https://pic1.zhimg.com/da8e974dc_is.jpg'),])
上面的 url 可以换为自己需要下载视频、音乐、图片等url
相关文章:
11 迭代器|生成器|协程
文章目录 迭代器可迭代对象可迭代对象的本质iter()函数与 next()函数迭代器 Iterator样例 for...in...循环的本质使用的场景--斐波那契数列list和tuple也可以接收可迭代对象 生成器简介创建生成器方法一方法二总结 使用 send 唤醒 协程协程和线程差异简单实现协程greenletgeven…...

“第三方支付”详解!
第三方支付是什么?第三方支付的解释 中央银行官方解释:是与产品所在国和主要外资银行签订合同、具有一定实力和信誉保障的第三方独立机构提供的交易支持平台。在通过第三方支付平台进行的交易中,买方购买货物后,买方使用第三方平台…...
Rust之泛型、trait与生命周期
泛型是具体类型或其他属性的抽象替代。在编写代码时,可以直接描述泛型的行为,或者它与其他泛型产生的联系,而无须知晓它在编译和运行代码时采用的具体类型。 1、泛型数据类型: 们可以在声明函数签名或结构体等元素时使用泛型&am…...

GPU Microarch 学习笔记 [1]
WARP GPU的线程从thread grid 到thread block,一个thread block在CUDA Core上执行时,会分成warp执行,warp的颗粒度是32个线程。比如一个thread block可能有1024个线程,分成32个warp执行。 上图的CTA(cooperative thre…...

Transformer(一)简述(注意力机制,NLP,CV通用模型)
目录 1.Encoder 1.1简单理解Attention 1.2.什么是self-attention 1.3.怎么计算self-attention 1.4.multi-headed(q,k,v不区分大小写) 1.5.位置信息表达 2.Decoder(待补充) 3.BERT 参考文献 1.Encode…...

回归预测 | MATLAB实现BiLSTM双向长短期记忆神经网络多输入多输出预测
回归预测 | MATLAB实现BiLSTM双向长短期记忆神经网络多输入多输出预测 目录 回归预测 | MATLAB实现BiLSTM双向长短期记忆神经网络多输入多输出预测预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介绍 MATLAB实现BiLSTM双向长短期记忆神经网络多输入多输出预测&#x…...

使用Dockker创建vwas容器时报错的解决方法
执行命令 docker run -it -d -p 13443:3443 --cap-add LINUX_IMMUTABLE secfa/docker-awvs没有详细看报错之前找了各种各样的解决办法,都无法解决。因此以后在看报错提示的时候耐心一点看关键词Error 后来才发现启动vwas时docker报了这个错: OSError: …...

【数据结构OJ题】链表分割
原题链接:https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?tpId8&&tqId11004&rp2&ru/activity/oj&qru/ta/cracking-the-coding-interview/question-ranking 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2…...
无感知发布
什么是无感知发布 "无感知发布"是指在软件系统或应用程序进行更新或升级时,尽可能地避免对用户或系统的正常运行产生影响或中断。这种发布方式通常采用一系列技术和策略,以确保新版本的软件可以平滑地替代旧版本,而不会造成用户的…...

C++ 虚继承
C棱形继承 在 C 中,在使用 多继承 时,如果发生了如果类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这时候就发生了菱形继承。 如果发生了菱形继承,这个时候类 A 中的 成员变量 和 成员函数 继承到类 D 中变成了两…...
git commit用法
git commit 是 Git 版本控制系统中的一个命令,用于将更改提交到本地存储库。以下是 git commit 的一些常见用法和选项: 基本用法: git commit -m "提交信息"使用 -m 选项可以直接在命令行中添加提交信息。 提交所有更改: git commit -a -m &q…...

【LeetCode】543.二叉树的直径
题目 给你一棵二叉树的根节点,返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 示例 1: 输入:root [1,2,3,4,5]…...
TypeScript教程(五)条件语句,循环,函数
一、条件语句 条件语句基于不同的条件来执行不同的动作 1.if语句:只有当指定条件为true时,使用该语句来执行代码 2.if...else语句:当条件为true时执行代码,当条件为else时执行其他代码 3.if...else if...else语句:…...

vue使用jsplumb 流程图
安装jsPlumb库:在Vue项目中使用npm或yarn安装jsPlumb库。 npm install jsplumb 创建一个Vue组件:创建一个Vue组件来容纳jsPlumb的功能和呈现。 <template><div style"margin: 20px"><div style"margin: 20px">&l…...
【BASH】回顾与知识点梳理(二十八)
【BASH】回顾与知识点梳理 二十八 二十八. 例行性工作排程(crontab)28.1 什么是例行性工作排程Linux 工作排程的种类: at, cronCentOS Linux 系统上常见的例行性工作 28.2 仅执行一次的工作排程atd 的启动at 的运作方式实际运作单一工作排程at 工作的管理batch&…...

LangChain源码逐行解密之系统(二)
LangChain源码逐行解密之系统 20.2 serapi.py源码逐行剖析 我们可以看一下Google查询的例子,在LangChain中有多种实现的方式。 如图20-5所示,在utilities的serpapi.py代码文件中实现了SerpAPIWrapper。 图20- 5 utilities的serpapi.py的SerpAPIWrapper 在langchain目录的se…...

QT的设计器介绍
设计器介绍 Qt制作 UI 界面,一般可以通过UI制作工具QtDesigner和纯代码编写两种方式来实现。纯代码实现暂时在这里不阐述了在后续布局章节详细说明,QtDesigner已经继承到开发环境中,在工程中直接双击ui文件就可以直接在QtDesigner设计器中打…...

[LitCTF 2023]Ping
因为直接ping会有弹窗。这里在火狐f12,然后f1选禁用javascript,然后ping 然后输入127.0.0.1;cat /flag 得到flag, 查看其他大佬的wp ,这里还可以抓包。但是不知道为什么我这里的burp 用不了...

Spring Cloud面试突击班1
Spring Cloud面试突击班1 1.Spring Cloud 中有哪些组件,整个项目架构中我们的重点又有哪些? Spring Cloud 是一套基于Spring Boot的微服务解决方案。 Spring Cloud生态在国内主流的分为两套,一套是以奈飞开源的Spring Cloud Netfilx 20%&a…...

线上售楼vr全景看房成为企业数字化营销工具
在房地产业中,VR全景拍摄为买家提供了虚拟看房的全新体验。买家可以通过相关设备,远程参观各个楼盘的样板间和实景,感受房屋的空间布局和环境氛围,极大地提高了购房决策的准确性。对于房地产开发商和中介机构来说,VR全…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...

UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...