'NoneType' 对象没有属性 'earlierDate_'

'NoneType' object has no attribute 'earlierDate_'

我刚刚开始学习 Python,为了做到这一点,我正在努力实现一个简单的聊天机器人。在我想实现一些文本到语音功能之前,它工作得很好,当它们出现在屏幕上时它会说出这些行。 为了实现这一目标,我不得不深入研究多线程,这就是我被困的地方:

import concurrent.futures
import pyttsx3
from time import sleep
import sys

# Settings
engine = pyttsx3.init()
voices = engine.getProperty('voices')
engine.setProperty('voice', voices[0].id)
typing_delay=0.035

def textToSpeech(text):
    engine.say(text)
    engine.runAndWait()  
 
def typing(sentence):
    for char in sentence:
        sleep(typing_delay)
        sys.stdout.write(char)
        sys.stdout.flush()

# def parallel(string):
#     tasks = [lambda: textToSpeech(string), lambda: typing("\n> "+string+"\n\n")]
#     with ThreadPoolExecutor(max_workers=2) as executor:
#         futures = [executor.submit(task) for task in tasks]      
#         for future in futures:
#             try:
#                 future.result()
#             except Exception as e:
#                 print(e)
        
def parallel(text):
    with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
        future_tasks = {executor.submit(textToSpeech, text), executor.submit(typing, "\n> "+text+"\n\n")}
        for future in concurrent.futures.as_completed(future_tasks):
            try:
                data = future.result()
            except Exception as e:
                print(e)

# Test Sentence
parallel("Greetings Professor Falken")

顶部的两个函数应该运行 并行。我已经为我的 parallel() 函数尝试了两种不同的实现(一个被注释掉了),但是这两种实现都产生了相同的结果。对于聊天机器人发出的第一行文本,我实际上同时收到了文本和语音,但随后出现错误: 'NoneType' object has no attribute 'earlierDate_' 之后,我只收到文字,不再有语音和错误:run loop already started

我假设 concurrent.futures 中的某处是属性 'earlierDate_' 并且我没有正确处理它,因此文本到语音线程永远不会停止。但我不知道如何解决它。

我希望这里有人有可能有所帮助的想法。我已将我的代码缩减到尽可能小但仍然可以 运行 和测试的东西。

附录:我在 Python 3.8 上导入 pyttsx3 时遇到问题,所以我降级到 Python 3.7,它似乎可以工作。


更新: 所以我想到,当我专注于多线程时,问题可能一直出在我的文本转语音实现上。

最明显的一点是我在全局初始化我的语音引擎。所以我将我的设置移到了 textToSpeech 函数中:

def textToSpeech(text):
    engine = pyttsx3.init()
    voices = engine.getProperty('voices')
    engine.setProperty('voice', voices[0].id)
    engine.say(text)
    engine.runAndWait()

run loop already started 错误现在不会立即出现,我在前几次聊天机器人交互中收到了文本和语音。 我仍然收到 'NoneType' object has no attribute 'earlierDate_' 错误,现在在每个聊天机器人输出之后,最终 run loop already started 错误再次出现,我失去了声音。不过,我想更近了一步。


更新 2:

经过又一天的挖掘,我想我又近了一步。 这似乎是与多线程相关的 Mac 特定问题。我在不同领域发现了多个问题,人们 运行 遇到了这个问题。

我在 PyObjCTools/AppHelper.py

中找到了问题

我们有以下功能:

def runConsoleEventLoop(
    argv=None, installInterrupt=False, mode=NSDefaultRunLoopMode, maxTimeout=3.0
):
    if argv is None:
        argv = sys.argv
    if installInterrupt:
        installMachInterrupt()
    runLoop = NSRunLoop.currentRunLoop()
    stopper = PyObjCAppHelperRunLoopStopper.alloc().init()
    PyObjCAppHelperRunLoopStopper.addRunLoopStopper_toRunLoop_(stopper, runLoop)
    try:

        while stopper.shouldRun():
            nextfire = runLoop.limitDateForMode_(mode)
            if not stopper.shouldRun():
                break

            soon = NSDate.dateWithTimeIntervalSinceNow_(maxTimeout)
            nextfire = nextfire.earlierDate_(soon)
            if not runLoop.runMode_beforeDate_(mode, nextfire):
                stopper.stop()

    finally:
        PyObjCAppHelperRunLoopStopper.removeRunLoopStopperFromRunLoop_(runLoop)

靠近按钮的这一行是罪魁祸首:nextfire = nextfire.earlierDate_(soon) 对象 nextfire 似乎是一个日期。在 Objective-C 中,NSDate 对象确实有一个 earlierDate() 方法,所以它应该可以工作。但是初始化有问题。当我 print(nextfire) 时,我得到 None。那么 NoneType 对象没有属性 'earlierDate_'.

也就不足为奇了

所以,我有点解决了我的两个问题,但是,我以一种不令人满意的方式解决了它们。这有点老套。但这是我能做的最好的,除了剖析 pyttsx3.

1) 首先,对于 run loop already started 错误的问题: 我已将我的引擎初始化移回 textToSpeech 函数之外的全局级别(就像在我的初始代码片段中一样)。 然后,每次调用 textToSpeech 函数之前,我都会输入以下代码:

try:
    engine.endLoop()
except Exception as e:
    pass

这样,当已经有一个循环时 运行,它会在新调用之前停止,从而防止错误发生。如果没有循环 运行,什么也不会发生。

2) 我的 'NoneType' object has no attribute 'earlierDate_' 错误的主要问题更加深入。我查看了各种来源,但我并不完全了解那里发生了什么。 正如我在第二次更新中所写,错误源自 PyObjCTools/AppHelper.pynextfire 使用 NSRunLoop 中的 limitDateForMode 方法初始化,根据 Apple 文档 returns nil 如果此模式没有输入源。

下一步是查看 pyttsx3/nsss.py 实例化 PyObjCTools/AppHelper.py 中的方法的位置。但是我并没有真正理解该实例化是如何工作的以及如何解决 NSRunLoop 显然在没有输入源的情况下被实例化的事实,或者是否应该解决这个问题。 所以我进行了一次肮脏的黑客攻击,并通过在 earlierDate_ 调用中切换 nextfiresoon 来更改 PyObjCTools/AppHelper.py

    try:

        while stopper.shouldRun():
            nextfire = runLoop.limitDateForMode_(mode)
            if not stopper.shouldRun():
                break

            soon = NSDate.dateWithTimeIntervalSinceNow_(maxTimeout)
            nextfire = soon.earlierDate_(nextfire)
            if not runLoop.runMode_beforeDate_(mode, nextfire):
                stopper.stop()

    finally:
        PyObjCAppHelperRunLoopStopper.removeRunLoopStopperFromRunLoop_(runLoop)

soon 变量总是正确地实例化为 NSDate 对象,因此方法 earlierDate 可用。显然它也适用于 nil 作为参数。

当然,如果所涉及的两个库之一中有适当的修复,那就更好了。我的猜测是 pyttsx3/nsss.py 需要一些工作,但我对 NSRunLoops 的了解不够,无法得出明智的意见。