扩展socket.socket类以添加新属性

11

我希望扩展Python的socket.socket类,增加一个新属性(特别是一个queue.Queue,但也可以是其他任何东西)。我正在考虑是使用继承还是组合,但两种方法都存在一个问题,我不确定该如何解决:

A)如果我使用继承,像这样:

class MySocket(socket.socket):
    def __init__(self, *args, **kwargs):
        socket.socket.__init__(self, *args, **kwargs)
        self.queue = queue.Queue()

当我使用类似于操作时,就会遇到问题

connection, client_address = s.accept()

由于套接字的accept方法返回的对象类型为socket.socket而不是MySocket类型, 我不确定如何将一个转换为另一个。如果有一种简单的面向对象的方式来解决这个问题,我不知道它。

B)如果我使用组合代替继承,则之前的问题可以轻松解决:

class MySocket(socket.socket):
    def __init__(self, true_socket):
        self.true_socket = true_socket
        self.queue = queue.Queue()

我会简单地实现类似的东西

def accept(self, *args, **kwargs):
    con, cli = socket.socket.accept(self, *args, **kwargs)
    return self.__class__(con), cli

但是我还有另一个问题。当我需要做的时候

readable, writable, exceptional = select.select(inputs, outputs, inputs)

select 对于 socket.socket 可以正常工作。对于 A 版本,我希望这样可以直接运行,但是使用组合方式后,由于 MySockets 不是 socket.socket 的实例,所以 select 无法正常工作。

那么,有没有更好的 Pythonic 方法呢?

编辑:我忘记说我尝试过直接在 `socket.socket` 实例上添加属性:

s = socket.socket(...)
s.queue = queue.Queue()

但我收到一个异常,说对于 'socket.socket',该属性未知。


关于您的编辑:socket.socket使用__slots__,这就是为什么您无法添加属性的原因。这也防止您执行类似s.__class__ = MySocket的操作...太糟糕了。 - augurar
谢谢@augurar,那是我的猜测。他们肯定有很好的理由来这样实现它。 - zeycus
zeycus,这个对你有用吗?我需要做和你一样的事情。 - JDOaktown
3个回答

8

您可以使用以下内容,基于Lib/socket.py中的socket.socket.dup()

    import _socket

    class MySocket(socket.socket):
        def __init__(self, *args, **kwargs):
            super(MySocket, self).__init__(*args, **kwargs)
            self.queue = queue.Queue

        @classmethod
        def copy(cls, sock):
            fd = _socket.dup(sock.fileno())
            copy = cls(sock.family, sock.type, sock.proto, fileno=fd)
            copy.settimeout(sock.gettimeout())
            return copy

现在您可以使用构造函数创建新的MySocket,或使用MySocket.copy()从现有的socket.socket创建一个MySocket。请注意,在大多数情况下,您应该在创建副本后关闭原始套接字。

不错,我不知道 socket.socket.dup(),我会尝试你的建议! - zeycus

0
文件 select.select() 的文档明确允许您使用自己的类,只要您实现了 fileno() 方法:

您也可以自定义一个包装类,只要它具有适当的 fileno() 方法(真正返回一个文件描述符,而不仅仅是一个随机整数)。

因此,像这样的代码应该可以工作:

class MySocket(socket.socket):
    ...
    def fileno(self):
        return self.true_socket.fileno()

或者,如果您使用的是Python 3.4+,您可以使用selectors模块,该模块允许您将data(例如队列本身)与每个注册的套接字关联起来,并且您可以使用BaseSelector.get_key()检索它,例如sel.get_key(true_socket).data(它也由BaseSelector.select()返回)。如果您使用selectors.DefaultSelector(),在可用时还将获得kqueue/epoll/等的好处,而无需编写自己的后备select

这些选项中似乎没有特别“Pythonic”的选项,但socket.socket似乎不是设计为可被子类化的,因此组合更有可能保持向前兼容。


-2

也许你应该尝试使用super进行继承

class NewSocket(socket.socket):
    def __init__(self):
        super(NewSocket, self).__init__()

2
感谢@MishaVacic,我明白这可能更符合Python的风格,但它并不能解决我的问题,对吧? - zeycus
@zeycus,你能在某个地方发布一下代码吗?我想试一下。 - MishaVacic
我看不出这会怎么起作用(在找到这个页面之前,这是我的第一反应)-像accept()这样的东西返回一个socket.socket,而不是NewSocket,所以你没戏了。没有(明智的)方法将socket.socket“升级”为NewSocket - Ralph Bolton

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