Код не выходит из функций

есть проблема, программа не выходит из функций когда к ней нет обращения, например если я обращусь к функций для показа погоды и после её показа, то бот будет продолжать реагировать на название города даже если не будет обращения к функций

import logging
from aiogram import Bot, Dispatcher, executor, types
import datetime
import requests
import pyowm
from bs4 import BeautifulSoup as BS
bot = Bot(token="***")
WEATHER_SERVICE_API_KEY = ("***")
dp = Dispatcher(bot)
logging.basicConfig(level=logging.INFO)
now = datetime.datetime.now()

@dp.message_handler(commands="start")
async def hello(message):
    keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
    buttons = ["❄ Погода", "? Курс","? Акций "]
    keyboard.add(*buttons)

    if now.hour >= 6 and now.hour < 12:
        await bot.send_message(message.chat.id,text="Доброе утро", reply_markup=keyboard)
    elif now.hour >= 12 and now.hour < 18:
        await bot.send_message(message.chat.id,text="Добрый день", reply_markup=keyboard)
    elif now.hour >= 18 and now.hour < 23:
        await bot.send_message(message.chat.id,text="Добрый вечер", reply_markup=keyboard)
    else:
        await bot.send_message(message.chat.id,text="Доброй ночи", reply_markup=keyboard)
        
@dp.message_handler(lambda message: message.text ==  "? Курс")
async def conv(message: types.Message):
    data = requests.get('https://www.cbr-xml-daily.ru/daily_json.js').json()
    USD = data['Valute']['USD']['Value']
    USDP = data['Valute']['USD']['Previous']
    EUR = data['Valute']['EUR']['Value']
    EURP = data['Valute']['EUR']['Previous']
    await message.reply(f"Курс рубля по данным ЦБ:\n<b>"\
                        f"К доллару {USD}</b> ?\n<u>"\
                        f"Предыдущий курс доллара {USDP}</u>\n<b>"\
                        f"К евро {EUR}</b> ?\n<u>"\
                        f"Предыдущий курс евро {EURP}</u>",parse_mode='html')

@dp.message_handler(lambda message: message.text == "❄ Погода")
async def location(message):
    await message.reply('напиши название города и я пришлю сводку погоды')
    @dp.message_handler(lambda message: message)
    async def get_weather(message):
        try:
            weather = message.text
            r = requests.get(f"http://api.openweathermap.org/data/2.5/weather?q={weather}&appid={WEATHER_SERVICE_API_KEY}&units=metric&lang=ru")
            data = r.json()
            city = data["name"]
            cur_weather = data['main']['temp']
            humidity = data['main']['humidity']
            pressure = data['main']['pressure']
            wind = data['wind']['speed']
            await message.answer(f"<u>{datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}</u>\n"
            f'<b>Погода в городе:</b>  {city}\n<b>Температура:</b>  {cur_weather}Celsius\n'
            f'<b>Влажность:</b>  {humidity}%\n<b>Давление:</b>  {pressure} мм.рт.ст \n<b>Ветер:</b>  {wind} м/с\n'
            f'***Хорошего Дня)***', parse_mode='html')
        except:
            await message.answer("ошибка")
    
@dp.message_handler(lambda message: message.text == "? Акций")
async def ticket (message: types.Message):
    await message.reply('напиши индекс акций и я пришлю нынешнюю цену ')
    @dp.message_handler(lambda message: message)
    async def get_ticket(message: types.Message):
        try:
            tickets = message.text
            ticket = (f"https://www.tinkoff.ru/invest/stocks/{tickets}/")
            #заголовки для URL запроса.(добавляется к ссылке при URL запросе)
            headers = {'user agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0"}
            html = requests.get(ticket, headers)
            soup = BS(html.content, 'html.parser')
            convert = soup.findAll('span', {'class': 'SecurityInvitingScreen__priceValue_c1Ah9'})
            price = (convert[0].text[0:])
            await message.answer(f"Цена данной акций: {price}")
            return tickets
        except:
            print("не работает")
if __name__ == "__main__":
    executor.start_polling(dp, skip_updates=True)

Ответы (1 шт):

Автор решения: oleksandrigo

Эхх, ещё один участник культа "любителей вставить хендлер внутрь другого хендлера", которых развелось очень много в последнее время. Скажите мне пожалуйста кто вам сказал так кодить? Кто он? Я серьезно.
Попробую объяснить почему у вас это все дело не работает.
Итак юзер жмет кнопку старт, и получает реплай клавиатуру. Жмет на "Погода", запускается хендлер с этим фильтром. Пока всё ок. Пока. Однако, внутри этого хендлера у вас ещё один, который по факту работает как? Ну он должен быть последним в списке регистрируемых хендлеров, так как создался только что, но это создает проблемы, как минимуму, для кнопки "Акции" у которой всё ровно так же.
Так вот, юзер жмакнул на погоду и ввел город. Всё еще все будет работать. Однако хендлер то уже зарегистрирован и по факту юзер может дальше вводить названия городов и хендлер будет срабатывать. Почему?
У вас есть вот эти 4 хендлера, который регистрируются при запуске бота. Остальные он не видит, так как они внутри других. И вот у вас создался внутренний список с этими хендлерами у которых есть свои фильтры, которые просто отвратительны.


Пояснение по фильтрам: вот это - lambda message: message.text == "? Акций" меняйте на `text="? Акций". Зачем вам лямбда, если есть такие красивые встроенные фильтры?


После того как юзер жмакнул на погоду, появился новый хендлер, но с пустым фильтром (по умолчанию принимает только текст), который впитывает в себя всё что напишет юзер, и что не было отфильтровано вышестоящими хендлерами.
И если вдруг появится хендлер с любым другим фильтром, который реагирует на текст, то он будет проигнорирован, так как есть хендлер выше.

Короче, к главному. Как фиксить? Для начала перестаньте вписывать один хендлер внутрь другого. Сделали? Круто.
Теперь, вам пора знакомиться с FSM. Это легко, но думаю информации в интернете предостаточно. Сюда кину как бы я это сделал.

import datetime

from aiogram import Bot, Dispatcher, executor, types
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher import FSMContext
from aiogram.dispatcher.filters import Text
from aiogram.dispatcher.filters.state import StatesGroup, State

import requests

from data import config

bot = Bot(token=config.BOT_TOKEN)
storage = MemoryStorage()
dp = Dispatcher(bot, storage=storage)

WEATHER_SERVICE_API_KEY = "___"
now = datetime.datetime.now()

# создаем класс стейтов
# каждая переменная сделанная таким же способом, будет отдельным стейтом
# вы можете называть их как захотите
class FSM_type_1(StatesGroup):
    weather = State()


@dp.message_handler(commands="start")
async def start(message: types.Message):
    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    buttons = ["❄ Погода", "? Курс", "? Акций "]
    markup.add(*buttons)
    # немного поменял вывод сообщения
    time_dict = {
        "Доброе утро": range(6, 12),
        "Добрый день": range(12, 18),
        "Добрый вечер": range(18, 24),
        "Доброй ночи": range(0, 6),
    }
    for text, time in time_dict.items():
        if now.hour in time:
            await message.answer(f"{text}", reply_markup=markup)


@dp.message_handler(text="❄ Погода")
async def weather(message: types.Message):
    await message.reply('Введите название города, для получения сводки по погоде')
    # когда мы готовы ловить сообщения ставим стейт
    await FSM_type_1.weather.set()

# вот такой фильтр нужен, чтобы ловить при определенном стейте
@dp.message_handler(state=FSM_type_1.weather)
async def get_weather(message: types.Message, state: FSMContext):
    try:
        weather = message.text
        r = requests.get(
            f"http://api.openweathermap.org/data/2.5/weather?q={weather}&appid={WEATHER_SERVICE_API_KEY}&units=metric&lang=ru")
        data = r.json()
        city = data["name"]
        cur_weather = data['main']['temp']
        humidity = data['main']['humidity']
        pressure = data['main']['pressure']
        wind = data['wind']['speed']
        await message.answer(f"<u>{datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}</u>\n"
                             f'<b>Погода в городе:</b>  {city}\n<b>Температура:</b>  {cur_weather}Celsius\n'
                             f'<b>Влажность:</b>  {humidity}%\n<b>Давление:</b>  {pressure} мм.рт.ст \n<b>Ветер:</b>  {wind} м/с\n'
                             f'***Хорошего Дня)***', parse_mode='html')
        # завершаем стейт
        await state.finish()
    except Exception as e:
        await message.answer("Город не найден.\nВведите корректное название города\n"
                             f"Если хотите завершить ввод погоды напишите 'Отмена'")

# ну и добавил прекращение ввода
# работает как завершалка стейтов, будьте осторожны, если не хотите чтобы
# не сбросить нужный стейт этой командой
@dp.message_handler(commands=["отмена"], state="*")
@dp.message_handler(Text(equals="отмена", ignore_case=True), state="*")
async def cancel_fsm(message: types.Message, state: FSMContext):
    await state.finish()


if __name__ == '__main__':
    executor.start_polling(dp, skip_updates=True)

→ Ссылка