Python - 神秘的双对象实例化

Python - Mysterious Double Object Instantiation

我 运行 遇到了一个我无法弄清楚的看似奇怪的问题 - 我正在尝试创建一个对象,但由于某种原因,总是创建两个对象。

这两个文件控制着我正在建造的机器人。

App.py Hypervisor.py

我正在尝试从 Flask 服务器 app.py 中创建 Hypervisor class 的单个实例,但是始终会创建两个实例。

下面是我的代码和控制台输出,显示了双重对象的创建。为什么会这样!!?!?!?

App.py

#!/usr/bin/env python
from flask import Flask,  render_template,  session,  request,  send_from_directory,  send_file
from flask_socketio import SocketIO,  emit,  join_room,  leave_room,  close_room,  rooms,  disconnect
import time
import json
import datetime
import logging
import platform
from bColors import bcolors
from RobotSystem.Hypervisor import Hypervisor
from RobotSystem.Services.Utilities.RobotUtils import RobotUtils

async_mode = None
app = Flask(__name__,  static_url_path='/static')
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app,  async_mode=async_mode)
log = logging.getLogger("werkzeug")
log.setLevel(logging.ERROR)

thread = None
connections = 0

@app.route('/',  methods=['GET',  'POST'])
def index():
    return render_template('index.html',  async_mode=socketio.async_mode)

def background_thread():
    while True:
        socketio.sleep(1)

@socketio.on('valueUpdate')
def valueUpdateHandler(message):
    RobotUtils.ColorPrinter("app.py",'Value update fired ', 'OKBLUE')
    quadbot.inputData(message)
    data = {}
    data['Recieved'] = True
    return json.dumps(data)

@socketio.on('connect')
def test_connect():
    global connections
    connections+=1
    print_str = "Client connected. "+ str(connections)+  " current connections"
    RobotUtils.ColorPrinter("app.py",print_str, 'OKBLUE')

    global thread, quadbotThread
    if thread is None:
        print "init"
        thread = socketio.start_background_task(target=background_thread)

@socketio.on('disconnect')
def test_disconnect():
    global connections
    connections -= 1
    RobotUtils.ColorPrinter("app.py",str( 'Client disconnected. ' +str(connections)+ " current connections" ), 'OKBLUE')

if __name__ == '__main__':
    global quadbot
    quadbot = Hypervisor()
    socketio.run(app,  debug=True)

Hypervisor.py

#!/usr/bin/python
from Services import *
import time
import math
import json
import sys
import threading
import os
from Queue import Queue,Empty

class Hypervisor():

    def __init__(self):

        if RobotUtils.LIVE_TESTING:
            self.pwm = PWM()
            self.pwm.setPWMFreq(RobotUtils.FREQUENCY)
        else:
            self.pwm = None

        self.inputQueue = Queue()
        self.agendaThread = threading.Thread(group=None,target=self.updateAgendaLoop,name="agendaThread")
        self.agendaThread.start()

        self.data_file_name = RobotUtils.DATA_FILE

        self.front_left = None
        self.front_right = None
        self.back_left = None
        self.back_right = None

        self.TURN_LEFT = RobotUtils.TURN_LEFT
        self.TURN_RIGHT = RobotUtils.TURN_RIGHT
        self.FORWARD = RobotUtils.FORWARD
        self.BACKWARD = RobotUtils.BACKWARD
        self.STOP = RobotUtils.STOP
        self.AUTONOMOUS = RobotUtils.AUTONOMOUS
        self.INVALID_DATA_ERROR = RobotUtils.INVALID_DATA_ERROR

        self.horizVidMotor = Motor(50, RobotUtils.HORIZONTAL_VID_PIN, RobotUtils.HORIZONTAL_VID_MIN_VAL, RobotUtils.HORIZONTAL_VID_MAX_VAL, 0, "horizontal video motor", self.pwm)
        self.vertVidMotor = Motor( 50, RobotUtils.VERTICAL_VID_PIN, RobotUtils.VERTICAL_VID_MIN_VAL, RobotUtils.VERTICAL_VID_MAX_VAL, 0, "vertical video motor", self.pwm)

        self.setup()

        self.motors = [self.front_left, self.front_right,self.back_left,self.back_right, self.horizVidMotor, self.vertVidMotor ]

        self.MotionController = MotionController(self.TURN_LEFT,  self.TURN_RIGHT, self.FORWARD, self.BACKWARD, self.STOP,self.AUTONOMOUS,self.INVALID_DATA_ERROR,
        self.motors, RobotUtils
                                                )
        self.stand()
        RobotUtils.ColorPrinter(self.__class__.__name__, '__init__() finished. Robot Created with id ' +str(id(self)), 'OKBLUE')

    # loads json data and creates Leg objects with add_leg()
    def setup(self):

        with open(self.data_file_name) as data_file:
            data = json.load(data_file)
            constants = data["constants"]
            for i in range(len(data["legs"])):
                self.add_leg(data["legs"][i],constants)

    # reads dictuanary values from input, creates a Leg object, and adds it to leg variables
    def add_leg(self,legData,constants):

        leg_name = legData["name"]

        body_pin                = legData["motors"]["body"]["pinValue"]
        body_offset             = legData["motors"]["body"]["offset"]
        body_center             = constants["bodyCenterValue"] + body_offset
        body_min                = constants["bodyRange"]["min"]
        body_max                = constants["bodyRange"]["max"]

        mid_horiz_value         = legData["motors"]["middle"]["horizValue"]
        middle_pin              = legData["motors"]["middle"]["pinValue"]
        middle_min              = constants["middleRange"]["min"]
        middle_max              = constants["middleRange"]["max"]
        middle_offset_to_center = constants["midOffsetFromHoriz"]

        leg_horiz_value         = legData["motors"]["leg"]["horizValue"]
        leg_pin                 = legData["motors"]["leg"]["pinValue"]
        leg_min                 = constants["legRange"]["min"]
        leg_max                 = constants["legRange"]["max"]
        leg_offset_to_center    = constants["legOffsetFromHoriz"]

        leg = Leg( self.pwm, leg_name, body_pin,    body_min,   body_max,   body_center, mid_horiz_value,   middle_pin, middle_min, middle_max, middle_offset_to_center, leg_horiz_value, leg_pin, leg_min, leg_max, leg_offset_to_center)

        if leg_name == "FR":
            self.front_right = leg

        elif leg_name == "FL":
            self.front_left = leg

        elif leg_name == "BL":
            self.back_left = leg

        elif leg_name == "BR":
            self.back_right = leg

        else:
            print "ERROR: LEG CANNOT BE IDENTIFIED"

    # Called by server when a change in user data is detected
    def inputData(self,data):
        self.inputQueue.put(data)

    def updateAgendaLoop(self):
        while True:
            try:
                data = self.inputQueue.get_nowait()
                self.updateAgenda(data)
            except Empty:
                pass

            time.sleep(RobotUtils.AGENDA_UPDATE_SPEED)
        print '3[94m' + "Robot: QUEUE READING FINISHED" + '3[0m'
        sys.exit()

    # acts as central coordinator for the robot - raeads incoming data + state of the bot and calls methods accordingly
    def updateAgenda(self,data):
        self.MotionController.updateCameras(data)
        nextMove = self.MotionController.NextMove(data)
        if nextMove == self.INVALID_DATA_ERROR:
            print "Fix this"
        else:
            self.MotionController.MakeMove(nextMove)

控制台输出

首先是一些肥皂剧:

如果您提供 SSCCE(简短、自包含、正确(可编译)、示例),那么您更有可能获得回复。此外,通过将示例修剪到重现所需的最低限度,您可以很好地自己确定问题的根源。例如,以下将是您问题的 SSCCE:

必需的依赖项:
pip install flask
pip install flask-socketio
代码:
import logging
from flask import Flask
from flask_socketio import SocketIO

logging.basicConfig(level=logging.ERROR)
app = Flask(__name__)
socketio = SocketIO(app)

class Hypervisor():
    def __init__(self):
        print('Hypervisor initialized')

if __name__ == '__main__':
    quadbot = Hypervisor()
    socketio.run(app,  debug=True)
输出:
Hypervisor initialized
Hypervisor initialized

说明

如果您使用调试器,答案很容易出现。您可以使用 IDE 中包含的调试器工具,也可以始终使用 python 标准库 (a.k.a.pdb) 中的 Python 调试器。

虽然提供 pdb 的完整教程超出了本答案的范围,但我们将使用的调试方法是通过导入 pdb 并在您需要的位置插入以下 pdb.set_trace() 来进入调试器想开始调试。

由于问题与 Hypervisor 创建有关,插入调试器的逻辑点就在 Hypervisor 初始化之前,如下所示:

if __name__ == '__main__':
    import pdb
    global quadbot
    pdb.set_trace() # debugging begins here
    quadbot = Hypervisor()
    socketio.run(app, debug=True)

从这一点开始,当您 运行 您的应用程序时,它将在 Hypervisor 初始化之前立即放入 pdb。不过,在执行此操作之前,您需要了解使用 pdb 的两个命令。

第一个是 n for next,它将继续执行直到当前函数中的下一行代码。

第二个是 s 步,它将进入当前代码,一有机会就停止。

或者如文档所解释的那样:

The difference between next and step is that step stops inside a called function, while next executes called functions at (nearly) full speed, only stopping at the next line in the current function.

有了这些知识 运行 你的应用程序,你将看到 pdb 提示符,它看起来应该是这样的:

-> quadbot = Hypervisor()
(Pdb)

->表示当前行代码即将执行。 (Pdb) 是等待输入的提示符。此时让我们通过键入 n 并回车进入 下一个 代码行来初始化 Hypervisor。此时您应该看到已经创建了一个 Hypervisor。

[4/3/2017 20:02:46 ] Hypervisor:  __init__() finished. Robot Created with id 4218654299

并返回到下一行的 pdb 提示符:

-> socketio.run(app,  debug=True)
(Pdb)

因此,由于 运行 只剩下一行代码,问题出在 socketio.run 中的某处。所以这次我们将 step 通过键入 s 进入当前代码行,然后输入将带您到:

-> def run(self, app, host=None, port=None, **kwargs):
(Pdb) 

从这一点开始,继续前进到 下一个 代码行,直到看到第二个 Hypervisor 初始化。查看跟踪,您应该会看到如下内容:

-> app.run(host=host, port=port, threaded=True,
(Pdb) n

-> use_reloader=use_reloader, **kwargs)
(Pdb) n

-> quadbot = Hypervisor()
(Pdb) n
[4/3/2017 20:03:52 ] Hypervisor:  __init__() finished. Robot Created with id 4367452293

这表明 app.run 执行后(由于该命令跨越两行代码,需要执行两次 n),它 returns 到 quadbot = Hypervisor() 我们开始的代码行。因此,仔细查看 app.run 会发现一个名为 use_reloader 的参数。如果您还没有猜到,查看 Flask-SocketIO docs 会告诉我们:

use_reloader - True to enable the Flask reloader, False to disable it.

通过多一点挖掘,我们可以在 Flask docs 中找到以下两个智慧:

debug
The debug flag. Set this to True to enable debugging of the application. In debug mode the debugger will kick in when an unhandled exception occurs and the integrated server will automatically reload the application if changes in the code are detected.

run
...
Flask will suppress any server error with a generic error page unless it is in debug mode. As such to enable just the interactive debugger without the code reloading, you have to invoke run() with debug=True and use_reloader=False. Setting use_debugger to True without being in debug mode won’t catch any exceptions because there won’t be any to catch.

有了这些信息,您可以选择禁用调试,或者将选项 use_reloader=False 传递给 socketio.run

TL;DR

您很可能想通过将 use_reloader=False 传递给 socketio.run

来禁用 Flask 重新加载器
if __name__ == '__main__':
    global quadbot
    quadbot = Hypervisor()
    socketio.run(app, debug=True, use_reloader=False)