在Python中将Websocket客户端作为一个类使用

20

我正在尝试使用Websockets访问一些数据,但我真的无法理解Websockets文档中提供的示例。

我有以下代码(https://pypi.org/project/websocket_client/),希望将其转换为一个类。

import websocket
import thread
import time

def on_message(ws, message):
    print message

def on_error(ws, error):
    print error

def on_close(ws):
    print "### closed ###"

def on_open(ws):
    def run(*args):
        for i in range(3):
            time.sleep(1)
            ws.send("Hello %d" % i)
        time.sleep(1)
        ws.close()
        print "thread terminating..."
    thread.start_new_thread(run, ())


if __name__ == "__main__":
    websocket.enableTrace(True)
    ws = websocket.WebSocketApp("ws://echo.websocket.org/",
                                on_message = on_message,
                                on_error = on_error,
                                on_close = on_close)
    ws.on_open = on_open

    ws.run_forever()

这个想法是将所有的WebSocket功能封装在一个类中,这样我只需要创建该类的对象即可。

我试图开始实现它,但是连这个都无法通过:

class MySocket(object):
    def __init__(self):
        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp("ws://echo.websocket.org:12300/foo",
                                    on_message = on_message,
                                    on_error = on_error,
                                    on_close = on_close)

    def on_message(ws, message):
        print message

    def on_error(ws, error):
        print error

    def on_close(ws):
        print "### closed ###"

    def on_open(ws):
    ws.send("Hello %d" % i)

错误从on_message开始,提示“未解决的引用”。

8个回答

31

将函数包装在匿名的lambda函数中,以实现正确的调用并使用正确的self

class Client:
    def __init__(self, db, symbols):
        self.ws = websocket.WebSocketApp("wss://the.server.com/api",
                    on_message = lambda ws,msg: self.on_message(ws, msg),
                    on_error   = lambda ws,msg: self.on_error(ws, msg),
                    on_close   = lambda ws:     self.on_close(ws),
                    on_open    = lambda ws:     self.on_open(ws))

    def on_message(self, ws, message):
            msg = json.loads(message)
            print(msg)
    ...

3
这似乎是解决问题的正确方法。 - KaZyKa
没有使用 lambda 表达式会更简单:on_message=self.on_message, on_error=self.on_error, ... - gil9red
没有lambda,你就失去了self。换句话说,在on_message内部,self将不会指向您对象的实例。 - Евген
1
非常感谢。这节省了我好几个小时的时间。 - chrisckwong821
我已经尝试过使用lambda和self.on_message,但仍然出现“on_open()缺少1个必需的位置参数:'ws'”的错误。 - Jeremiah Payne

8

WebSocketApp 需要可调用对象来处理回调函数(包括在构造函数中传递的 on_message 以及之后设置的 on_open)。

普通函数是可调用对象,因此您的非面向对象版本可以正常工作,因为您正在传递普通函数。

绑定方法同样也是可调用对象。但是,您的面向对象版本没有传递绑定方法。绑定方法是绑定到对象的方法。您可以使用 obj.method 表示法来实现这一点。在您的情况下,这是 self.on_message

self.ws = websocket.WebSocketApp("ws://echo.websocket.org/",
                                 on_message = self.on_message,
                                 on_error = self.on_error,
                                 on_close = self.on_close)
self.ws.on_open = self.on_open

然而,你还有另一个问题。虽然这样可以消除你的错误,但并不能使你的代码真正运行。普通方法必须将 self 作为其第一个参数:

def on_message(self, ws, message):
    print message

值得注意的是,你并没有真正地使用这个类。如果你从来没有访问过self的任何内容,那么这个类就只是像一个命名空间一样。不是说这总是一件坏事,但通常这表明你需要至少思考一下你的设计。真的有任何状态需要维护吗?如果没有,你为什么要首先创建一个类呢?
你可能需要重新阅读关于方法、self等的类教程部分。

嗯,我无法像那样让它工作。我尝试了:ws = MySocket(); ws.run_forever(); 然后我得到了错误:'MySocket'对象没有属性'run_forever'。但也许你是对的,我应该把这个放在一个类里面。 - jbssm
@jbssm:嗯,你定义了一个名为 MySocket 的类,但是它没有 run_forever() 方法。你创建了该类的一个实例并尝试在其上调用 run_forever() 方法。你期望会发生什么?如果你想让你的类自动将所有未知的方法委托给 self.ws,那么你可以这样做,但你需要编写相应的代码。 - abarnert
谢谢。我将停止使用类并尝试使用普通函数。 - jbssm

4
import websocket

try:
    import thread
except ImportError:
    import _thread as thread
import time


class OnyxGenericClient:
    """
    Onyx Client Interface

    """

    def __init__(self, ):
        websocket.enableTrace(True)
        ws = websocket.WebSocketApp("ws://localhost:3000/",
                                         on_message=self.on_message,
                                         on_error=self.on_error,
                                         on_close=self.on_close)
        self.ws = ws
        self.ws.on_open = self.on_open
        self.ws.run_forever()

    # def initiate(self):

    def on_message(self, message):
        print(message)
        return message

    def on_error(self, error):
        return error

    def on_close(self):
        print("### closed ###")

    def run(self, *args):
        global driver
        driver = True
        while driver:
            try:
                time.sleep(1)
                print("Say something nice")
                p = input()
                self.ws.send(p)
            except KeyboardInterrupt:
                driver = False
        time.sleep(1)
        self.ws.close()
        print("thread terminating...")

    def on_open(self):
        thread.start_new_thread(self.run, ())


if __name__ == "__main__":
    websocket.enableTrace(True)
    onyx_client = OnyxGenericClient()

我不明白为什么每个人还在添加 ws 参数。

请查看错误日志。

文件 "venv/lib/python3.7/site-packages/websocket/_app.py",第 343 行,_callback 函数中,调用了回调函数 callback(*args)

    def _callback(self, callback, *args):
    if callback:
        try:
            if inspect.ismethod(callback):
                callback(*args)
            else:
                callback(self, *args)

        except Exception as e:
            _logging.error("error from callback {}: {}".format(callback, e))
            if _logging.isEnabledForDebug():
                _, _, tb = sys.exc_info()
                traceback.print_tb(tb)

观察我们的回调函数,on_open(self, ws)

try代码块执行时,它会检查我们的回调函数是方法还是函数。如果它是一个方法,它将执行callback(*args),而我们的CustomClient自身已经作为参数传递给了(*args)。请注意,在def _callback(self, callback, *args)中,它已经有自己的self。因此,您的每个属于CustomClient实例的回调函数都不应该有ws参数。


谢谢你,@vc74。我甚至都记不得我回答这个问题的时间了。但是这确实很有道理。 - Durodola Opemipo

3

我只是更新了此页面上其他作者编写的代码,并且对我有效。问题在于,在事件回调函数定义中,例如on_message,我们不应使用ws作为参数。self会处理它,在这些事件处理程序函数的主体中,我们应该使用self.ws。

class MySocket(object):
    def __init__(self):
        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp("ws://echo.websocket.org:12300/foo",
                                on_message = self.on_message,
                                on_error = self.on_error,
                                on_close = self.on_close)

    
    def on_message(self, message):
        # if you want to send something use like this
        # self.ws.send()
        print message

    def on_error(self, error):
        print error

    def on_close(self):
        print "### closed ###"

    def on_open(self):
        self.ws.send("Hello Message")

2
您需要在类方法中添加“self”:
您需要在您的类方法中加入“self”。
class MySocket(object):
    def __init__(self):
        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp("ws://echo.websocket.org:12300/foo",
                                on_message = self.on_message,
                                on_error = self.on_error,
                                on_close = self.on_close)

    def on_message(self, ws, message):
        print message

    def on_error(self, ws, error):
        print error

    def on_close(self, ws):
        print "### closed ###"

    def on_open(self, ws):
        ws.send("Hello %d" % i)

我不是这样工作的。我尝试了:ws = MySocket(); ws.run_forever(); 然后我得到了错误提示:'MySocket' 对象没有 'run_forever' 属性。 - jbssm
ws不是一个WebSocketApp对象,而是一个MySocket对象。您需要在该对象中添加代码来启动它,或者简单地调用属性上的run_forever方法,例如: ws.ws.run_forever() - Jon
谢谢。我想我只是没有理解这背后的原则。我现在会停止使用类,而是使用普通函数。 - jbssm

1
自身将这些方法作为类方法,如果由自身调用,on_error/message/close方法的签名将得到满足,因为它们会指向类本身。
 class MySocket(object):
   def __init__(self,x):
     websocket.enableTrace(True)
     ## Only Keep the object Initialisation here  
     self.x=x
     self.ws=None

     # call This method from a Object and it will create and run the websocket 
    def ws_comm(self):
        self.ws = websocket.WebSocketApp(self.WS_URL,on_message = 
        self.on_message,on_error =self.on_error,on_close = self.on_close)
        self.ws.on_open = self.on_open
        self.ws.run_forever()

    def on_error(self,ws, error):
        print "onError", error

    def on_close(self,ws):
       print "onClosed"

    #Send some message on open 
    def on_open(self,ws):
       self.ws.send(json.dumps(register_msg))

    def on_message(self,ws, msg):
       self.ws.send(json.dumps(msg))


 user1=Userapp('x')
 user1.ws_comm()

0

我想尝试这种方式:

class FooClient(object):
    def __init__(self):
        def on_message(ws, message):
            print message
            # use 'self' variable to access other resource
            # handle message from websocket server like this
            self.handler.handle(message)

        def on_error(ws, error):
            print error

        def on_close(ws):
            print "### closed ###"

        def on_open(ws):
            ws.send("Hello %d" % i)

        # assign to 'self.handler'
        self.handler = FooHandler()
        # maybe there are another module should be initiated
        # ...

        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp("ws://echo.websocket.org:12300/foo",
                                         on_message = on_message,
                                         on_error = on_error,
                                         on_close = on_close)

    def run_forever(self):
        self.ws.run_forever()

    def close(self):
        """clean other resources"""
        pass

在方法__init__(self)中使用内部函数可以避免on_message(self, ws, message)方法的参数数量与WebSocketApp提供给其参数on_message的数量不匹配的问题(类方法有一个额外的参数self)。

我有一个以上的handler来处理消息,方法close(self)用于清理一些资源(如果有的话),run_forever(self)用于运行websocket。


1
请描述您提供的解决方案。 - Dinesh Ghule
@DineshGhule 我添加了一些描述,如果有任何错误,请评论。 - hhka

0

这个可以工作:

class MySocket(object):
    def __init__(self):
        websocket.enableTrace(True)
        self.ws = websocket.WebSocketApp("ws://echo.websocket.org:12300/foo",
                                on_message = self.on_message,
                                on_error = self.on_error,
                                on_close = self.on_close)

    @staticmethod
    def on_message(ws, message):
        print message

    @staticmethod
    def on_error(ws, error):
        print error

    @staticmethod
    def on_close(ws):
        print "### closed ###"

    @staticmethod
    def on_open(ws):
        ws.send("Hello %d" % i)

但是你没有访问self的权限


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接