Python:需要在不中断循环的情况下调用 API



我有一个用Python编写的程序,它使用Spotipy库调用Spotify API来获取用户当前播放的歌曲和有问题的歌曲的节奏。然后,它使用该信息,串行连接和Arduino来运行一些灯。

我遇到的问题是,我希望程序检查 API,频率,看看歌曲是否发生了变化,但每次 API 调用都需要我的网络大约 0.15 秒,所以在调用太多之后,它会弄乱灯光的时间。

下面是为速度调用 API 的函数和一个示例 while 循环。如果你想看到完整的项目和完整的代码,这里是github的链接 - https://github.com/eboyce452/lightshow.git:

def check_bpm():
global seconds_per_beat
global current_track
current_track = spotify.current_user_playing_track()
if current_track is None:
seconds_per_beat = 0.5
else:
current_track = json_normalize(current_track)
current_track = str(current_track['item.id'].iloc[0])
features = json_normalize(spotify.audio_features(current_track))
tempo = float(features['tempo'].iloc[0])
seconds_per_beat = 60/tempo
while True:
check_bpm()
pin2.write(1)
time.sleep(seconds_per_beat)
pin2.write(0)
time.sleep(seconds_per_beat)

因此,在一个完美的世界中,我正在寻找一种让check_bpm((函数在后台运行的方法,以便灯光可以保持节拍,然后当歌曲发生变化时,中断循环(像继续或其他什么(并更新seconds_per_beat变量。

我真的不知道这是否可能,所以请随时权衡一下。但我最好奇的是实现一种常见的并发形式,以便在 check_bpm(( 函数等待 API 调用完成时,它继续执行 while 循环的其余部分,这样灯光就不会不同步。我一直在阅读很多关于 asyncio 的信息,但我对它非常陌生,以至于任何可以提供的帮助都值得赞赏。

非常感谢!您也可以随时查看该项目的github,并留下您想要的任何评论或批评。

是的- 由于您有一个表示主循环的"while True",并且更改的变量只写在其中一端,而在另一端读取,因此可以轻松移植到使用 asyncio 或线程。

在多线程中,你将让引脚发送函数和 API 读取函数在单独的线程中连续工作——就你而言,它们并行运行——而普通变量在 Python 中是线程安全的,这就是它的全部内容。

您可以在单独的线程中启动 API 读取:

from time import sleep
from threading import Thread
...

def check_bpm():
global seconds_per_beat
global current_track
while True:
current_track = spotify.current_user_playing_track()
if current_track is None:
seconds_per_beat = 0.5
else:
current_track = json_normalize(current_track)
current_track = str(current_track['item.id'].iloc[0])
features = json_normalize(spotify.audio_features(current_track))
tempo = float(features['tempo'].iloc[0])
seconds_per_beat = 60/tempo
# (if you don't care making another api call right away, just leave
# here with no pause. Otherwise, insert a reasonable time.sleep here 
# (inside the while loop))
def main():
api_thread = Thread(target=check_bpm)
api_thread.start()
while True:
pin2.write(1)
time.sleep(seconds_per_beat)
pin2.write(0)
time.sleep(seconds_per_beat)
main()

另一种途径是使用 asyncio - 它将采用不同的语法 - 但在一天结束时,您必须将 API 调用委托给另一个线程,除非您使用的spotify库也支持 asyncio。
即使是他们,"增益"更多的是"感知代码优雅",而不是任何真实的东西 - 如果您在具有不支持多线程的精简操作系统/CPU 设置的设备 PC 上运行此代码,也许它可以工作。

它看起来像这样:

from time import sleep
import asyncio
...

async def check_bpm():
global seconds_per_beat
global current_track
loop = asyncio.get_event_loop()
while True:
current_track = await loop.run_in_executor(spotify.current_user_playing_track)
if current_track is None:
seconds_per_beat = 0.5
else:
current_track = json_normalize(current_track)
current_track = str(current_track['item.id'].iloc[0])
features = json_normalize(spotify.audio_features(current_track))
tempo = float(features['tempo'].iloc[0])
seconds_per_beat = 60/tempo
# here we better leave some spacing for the other
# functions to run, besides the 0.15s duration
# of the API call
await asyncio.sleep(1)
async def manage_pins():
while True:
pin2.write(1)
await asyncio.sleep(seconds_per_beat)
pin2.write(0)
await asyncio.sleep(seconds_per_beat)

def main():
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(manage_pins(), check_bpm()))
main()

因此,在这里,Python 代码被重写,以便它自愿暂停执行以等待"其他事情发生"——这发生在"等待"语句上。
与带有线程的代码不同,全局变量将 仅在 pin-control 等待 "asyncio.sleep" 时更改 - 而在线程代码中,变量更改可以随时发生。

而且由于在 Spotify API 上阻止的调用不是 异步(它是一个普通函数,不是用async def(,我们使用loop.run_in_executor命令调用它 - 这将为我们创建和管理线程。这里的区别在于,在等待 api 调用时,主代码 pin 代码可以自由运行。

相关内容

最新更新