@Tysharela

Python. Создание голосового ассистента. Не могли бы помочь с оптимизацией кода?

Так как впервые работаю с большинством библиотек, хотелось бы услышать мнение более-менее опытного пользователя по оптимизации. В идеале помочь с заменой структуры if
Код можно посмотреть на GitHub'e: https://github.com/Tysharela/MyAsisstant
  • Вопрос задан
  • 89 просмотров
Пригласить эксперта
Ответы на вопрос 1
Vindicar
@Vindicar
RTFM!
Ну во-первых, твоя задача меняется с написания голосового помощника под фиксированный набор команд на написание фреймворка для создания голосовых помощников. Это надо иметь ввиду, и проектировать помощника так, чтобы программисту (тебе) было удобнее его дорабатывать.
Например, возьми за образец Flask или многие либы для создания ботов, которые используют декораторы для регистрации команд. Тогда скрипт превращается в набор функций, реализующих саму логику команд, а фреймворк/библиотека занимаются нижележащими базовыми вещами типа общения с сервером.
В твоём случае, этими базовыми вещами будут:
1. Рабочий цикл бота
2. Обнаружение и распознавание речи пользователя
3. Определение, обращается ли пользователь к боту.
4. Опционально, синтез ответа пользователю.
Таким образом, можно спрятать тонкости распознавания речи от отдельных команд - пусть работают с текстом!
Вывод: в итоговом боте, помимо базовых вещей, будут отдельные функции-команды. Каждая команда будет помечена декоратором, и с его помощью будут задаваться текстовые строки, на которые она должна реагировать.
Примерно так:
@command('привет')  # на какую команду реагировать?
def hello(message: str) -> typing.Optional[str]:
  # параметр - полная текстовая строка, которую мы приняли
  return "и тебе привет" # возвращаемое значение - None, или строка, которую нужно произнести

Это позволит легко дополнять функциональность бота, и не строить длинные цепочки if-elif-else. Теперь надо подумать, как это лучше реализовать.
Так как текстовые строки могут иметь вариации, или переменные части (например, погоду на какой день надо узнать?), то стоит использовать не поиск подстроки, а регулярные выражения. Тогда функция бота будет выглядеть как-то так.
@command(r'погода\s+город\s+(.+)')  # строка должна начинаться со слов "погода город", а всё что дальше - мы запоминаем.
def hello(match: re.Match) -> typing.Optional[str]:
  # параметр - результат сопоставления принятой строки с регуляркой
  city = match.group(1)
  weather = get_weather_for(city) # узнаём (как-то) погоду, город выбираем по запомненной строке
  # возвращаемое значение - None, или строка, которую нужно произнести
  return f"Погода в городе {city}: {weather}"

Это всё хорошо, но как это реализовать? Почитай про декораторы. Если коротко, это просто функции, которые принимают другие функции на вход.
Пример (упрощённый) реализации декоратора command:
import re
# список зарегистрированных через декоратор команд
registered_commands = []

def command(regexp: str):
  def decorator(command_func):
    global registered_commands
    reg = re.compile(regexp, re.I) # компилируем регулярку, чтобы потом она быстрее работала
    item = reg, command_func
    registered_commands.append(item) # добавляем регулярку и команду в список команд
    return command_func # декоратор должен вернуть функцию, которую обработал
  return decorator # возвращаем декоратор для использования

По сути, следующие два кода тогда будут эквивалентны:
@command('команда')
def some_func(match):
  pass
# это то же самое что и ниже
decorator = command('команда') # получили функцию-декоратор
# объявили декорируемую функцию
def some_func(match):
  pass
# применили декоратор к функции
some_func = decorator(some_func)

Таким образом, все функции, которые мы отдекорируем через @command, будут автоматически собраны в списке registered_commads. Тогда при обработке очередной команды мы сможем этот список перебрать, и найти нужную. А дальше просто. Делаешь рабочий цикл бота (код абстрактный, но идею должен передать).
while True:
  voice = record_user_voice() # детектируем и записываем слова пользователя
  text = speech_to_text(voice) # превращаем голос в текст
  if text.startswith('имя бота'): # пользователь должен сказать имя бота, чтобы мы напрасно не реагировали
    text = text[len('имя бота'):].strip() # убираем имя бота из текста
    for regexp, command in registered_commands:
      match = regexp.match(text) # пытаемся сопоставить текст с регуляркой
      if match is not None: # успешно?
        # да
        try:
          response = command(match) # пытаемся выполнить команду
        except Exception as err: # неудачно - сообщаем об ошибке
          print(err)
          response = "Возникла ошибка"
        if response: # если есть что сказать
          say(response) # проговариваем ответ
        break # так или иначе, мы нашли совпадение с командой. Дальше не ищем.
    else: # этот else относится к for ... in registered_commands и сработает если не было break
      say("Я не понял команду") # мы не нашли команду

Как-то так.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы