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'.

zeycus
  • 860
  • 1
  • 8
  • 20
  • 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. – augurar Jul 20 '17 at 08:55
  • Thanks @augurar, that was my guess. Surely they have a good reason to implement it like that. – zeycus Jul 20 '17 at 09:02
  • zeycus, did this work for you? I need to do exactly what you're doing. – JDOaktown Jun 03 '19 at 20:15

3 Answers3

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.

augurar
  • 12,081
  • 6
  • 50
  • 65
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.

tc.
  • 33,468
  • 5
  • 78
  • 96
-2

Maybe you should try inheritance with super

class NewSocket(socket.socket):
    def __init__(self):
        super(NewSocket, self).__init__()
t.m.adam
  • 15,106
  • 3
  • 32
  • 52
MishaVacic
  • 1,812
  • 8
  • 25
  • 29
  • 2
    Thanks @MishaVacic, I understand this is a bit more Pythonistic, but would solve none if my problems, would it? – zeycus Jul 20 '17 at 07:15
  • @zeycus Could you post somewhere the code,I will try it. – MishaVacic Jul 20 '17 at 07:17
  • 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`. – Ralph Bolton Jan 09 '20 at 14:09