Extending socket.socket with a new attribute
Asked Answered
H

3

11

I wish to extend Python socket.socket with a new attribute (in particular, a queue.Queue, but could be anything else). I am trying to decide whether I should use inheritance or composition, but at some point both pose a problem I am not sure how to solve:

A) If I use inheritance, with something like:

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

then I have problems when I use operations like

connection, client_address = s.accept()

because the accept method of sockets returns objects of type socket.socket, and not of type MySocket, and I am not sure how to convert one into the other. If there is is a trivial OOP way to do this, I do not know it.

B) The problem before is trivially solved if I used composition instead:

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

I would simply implement something like

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

But then I have another problem. When I need to do

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

select works for socket.socket. With the A) version I would expect this to work just as is, but with composition, now that MySockets are not instances of socket.socket, select is broken.

So, what is the best, pythonistic approach for this?

EDIT: I forgot to say that the first thing I tried was to add the attribute directly to instances of `socket.socket':

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

but I got an exception saying the attribute is unknown for 'socket.socket'.

Happen answered 20/7, 2017 at 6:58 Comment(3)
Regarding your edit: socket.socket uses __slots__, which is why you can't add attributes. This also prevents you from doing something like s.__class__ = MySocket... too bad.Bacteriology
Thanks @augurar, that was my guess. Surely they have a good reason to implement it like that.Happen
zeycus, did this work for you? I need to do exactly what you're doing.Baptist
B
8

You can use the following, based on socket.socket.dup() in Lib/socket.py:

    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

You can now use the constructor to create new MySockets, or use MySocket.copy() to create a MySocket from an existing socket.socket. Note that in most cases you should close the original socket after creating the copy.

Bacteriology answered 20/7, 2017 at 8:52 Comment(1)
Nice, I did not know about socket.socket.dup(), I'll try your suggestion!Happen
M
0

The documentation for select.select() explicitly allow you to use your own class as long as you implement fileno():

You may also define a wrapper class yourself, as long as it has an appropriate fileno() method (that really returns a file descriptor, not just a random integer).

So something like this should work:

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

Alternatively, if you are using Python 3.4+ you can use the selectors module which lets you associate data (such as the queue itself) with each registered socket, which you can retrieve it with BaseSelector.get_key(), e.g. sel.get_key(true_socket).data (it is also returned by BaseSelector.select()). If you use selectors.DefaultSelector() you'll also get the benefits of kqueue/epoll/etc when those available, without needing to write your own fallback to select.

None of the options seem particularly "Pythonic", but socket.socket does not appear to be designed to be subclassed, so composition is more likely to be forward-compatible.

Mongol answered 23/8, 2023 at 14:1 Comment(0)
L
-2

Maybe you should try inheritance with super

class NewSocket(socket.socket):
    def __init__(self):
        super(NewSocket, self).__init__()
Lama answered 20/7, 2017 at 7:10 Comment(3)
Thanks @MishaVacic, I understand this is a bit more Pythonistic, but would solve none if my problems, would it?Happen
@Happen Could you post somewhere the code,I will try it.Lama
I can't see how this will work (it was my first thought before I found this page) - things like accept() return a socket.socket, not a NewSocket, so you're out of luck. There's no (sensible) way to "upgrade" a socket.socket to a NewSocket.Horsehair

© 2022 - 2024 — McMap. All rights reserved.