You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Telethon中contextvars跨事件传递变量失败的问题求助

Telethon机器人ContextVar跨事件传递失败问题及解决方法

错误信息

ERROR - Unhandled exception on show_selected_tags
Traceback (most recent call last):
File "/home/kozorez/.cache/pypoetry/virtualenvs/content-telegram-bot-4LA9pChh-py3.10/lib/python3.10/site-packages/telethon/client/updates.py", line 570, in _dispatch_update
await callback(event)
File "/mnt/c/Users/kozor/PycharmProjects/content-telegram-bot/bot/bot.py", line 77, in show_selected_tags
some_var = tags_cv.get()
LookupError: <ContextVar name='tags' at 0x7f7e3e222de0>

问题描述

使用Telethon开发Telegram机器人时,尝试将send_tag_list()函数中通过contextvars.ContextVar设置的tags_cv值传递到show_selected_tags()函数,但触发上述LookupError。推测原因是Telethon的事件回调运行在不同的异步上下文,导致ContextVar无法跨任务共享数据。

相关代码如下:

bot.py

"""Логика взаимодействия с ботом"""

import logging
import contextvars
from telethon.sync import TelegramClient, events

from services.channel import get_channel_data, add_channel_to_db
from services.post import parse_posts
from services.tag import get_tags_list
from services.keyboard import create_tags_keyboard

from config import (
    api_id,
    api_hash,
    bot_token
)


bot = TelegramClient(
    'bot',
    api_id,
    api_hash).start(bot_token=bot_token)


channel_data_cv = contextvars.ContextVar('channel_data')
tags_cv = contextvars.ContextVar('tags')


@bot.on(events.NewMessage(pattern='/start'))
async def send_welcome(event) -> None:
    """Отправка приветственного сообщения"""

    await event.reply('Привет! Я — бот, который поможет тебе составить пост с навигацией по твоему телеграм-каналу. \
                      \nПросто пришли мне ссылку на свой телеграм-канал.')
    

@bot.on(events.NewMessage(pattern='https://t\.me/(\S+)'))
async def send_tag_list(event) -> None:
    """Анализируем посты из канала и возвращаем клавиатуру с тегами"""

    channel_link = event.text
    tags = None

    try:
        channel_data = await get_channel_data(channel_link)
        channel_data_cv.set(channel_data)
    except ValueError as error:
        if 'Cannot get entity from a channel' in str(error):
            await event.reply('Канал должен быть публичным')
        logging.error(error)

    try:
        await add_channel_to_db(channel_data)
    except Exception as error:
        logging.error(error)
        
    try:     
        await event.reply('Посты анализируются, ожидайте')
        await parse_posts(channel_link, channel_data)
    except ValueError as error:
        logging.error(error)
        await event.reply('К сожалению, мне не удалось найти ни одного тега')

    try:
        tags = await get_tags_list(channel_data)
        tags_cv.set(tags)
    except Exception as error:
        logging.error(error)

    tags_keyboard = await create_tags_keyboard(tags)

    await event.respond("Выберите теги", buttons=tags_keyboard)


@bot.on(events.CallbackQuery())
async def show_selected_tags(event) -> None:
    some_var = tags_cv.get()
    print(some_var)

run.py

"""Запуск бота"""

import asyncio
import logging

from bot import bot

from db.models import *

from config import (
    engine,
    client
    )



loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)



async def init_db() -> None:
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)
        logging.info('База данных инициализирована')


async def start() -> None:
    await init_db()

    async with bot:
        await bot.run_until_disconnected()
        logging.info('Бот запущен')

    async with client:
        await client.run_until_disconnected()
        logging.info('Клиент запущен')


if __name__ == "__main__":
    loop.create_task(start())
    loop.run_forever()

问题原因

Telethon的每个事件回调(比如send_tag_list和show_selected_tags)是独立的异步任务,不属于同一个上下文链。ContextVar仅能在同一个协程或其派生的子协程中共享数据,无法跨独立任务传递值,因此会触发LookupError。

解决方法

方法1:用用户/聊天ID关联全局字典(推荐)

利用用户的chat_id作为唯一标识,将数据存储在全局字典中,实现跨事件共享:

# 替换原contextvars定义
user_data = {}  # 结构: {chat_id: {"tags": [], "channel_data": {}}}

# 修改send_tag_list函数存储数据
async def send_tag_list(event) -> None:
    # ... 原有代码 ...
    try:
        tags = await get_tags_list(channel_data)
        # 用当前聊天ID作为键存储数据
        user_data[event.chat_id] = {
            "tags": tags,
            "channel_data": channel_data
        }
    except Exception as error:
        logging.error(error)

# 修改show_selected_tags函数获取数据
async def show_selected_tags(event) -> None:
    chat_id = event.chat_id
    if chat_id not in user_data:
        await event.respond("请先发送频道链接")
        return
    some_var = user_data[chat_id]["tags"]
    print(some_var)

注意:多用户场景下可添加过期清理逻辑,避免内存溢出。

方法2:数据库临时存储

将用户临时数据存入数据库,添加user_session表,字段包含user_id、tags、channel_data、expire_time。在send_tag_list中写入数据,show_selected_tags中读取,定时清理过期记录,适合需要持久化数据的场景。

方法3:缓存服务(如Redis)

分布式部署场景下,用Redis以用户ID为键存储临时数据,并设置过期时间,避免数据堆积,同时支持多实例共享状态。

方法4:嵌入数据到回调按钮Payload

将需要传递的数据编码为JSON字符串,作为回调按钮的data参数,在show_selected_tags中解析:

# 修改create_tags_keyboard函数
async def create_tags_keyboard(tags):
    import json
    from telethon.tl.types import KeyboardButtonCallback
    buttons = []
    for tag in tags:
        # 将标签和全量tags数据编码为JSON
        payload = json.dumps({"action": "select_tag", "tag": tag, "all_tags": tags})
        buttons.append([KeyboardButtonCallback(text=tag, data=payload.encode())])
    return buttons

# 修改show_selected_tags函数
async def show_selected_tags(event) -> None:
    import json
    try:
        payload = json.loads(event.data.decode())
        some_var = payload["all_tags"]
        print(some_var)
    except json.JSONDecodeError:
        await event.respond("无效操作")

此方案无需依赖全局状态,适合数据量较小的场景。

内容的提问来源于stack exchange,提问作者Kozorez-V

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 16:05:57