Search code examples
pythonsocketsnamespacesglobal-variablesmonkeypatching

Python socket implementation


I am fairly new to python, I come from a strong C background however, and I was trying to figure out how this works: How can I use a SOCKS 4/5 proxy with urllib2?

The part I am having trouble understanding is that they overwrite socket.socket yet socket.socket is a 'type' as told by running type(socket.socket). So I am confused about how you are capable of overwriting a type like that, or is socket.socket in this case actually a function they are redefining hence actually returning a different type? Normally if I run type on a function it returns that its a function or a method or whatever.

I guess what I am really trying to understand is what is happening under the covers?


Solution

  • I guess what I am really trying to understand is what is happening under the covers?

    This is a great question.

    Storing a variable in another namespace like this is called "monkey patching".

    The code example isn't actually overwriting the type. Instead, it updates the socket variable in the socket module namespace to point at a socks 4/5 class. Then, when urllib2 looks-up the socket.socket variable, it now uses the SocksiPy module instead of native sockets.

    The import thing to know is that socket.socket is a variable that is initially set to point at the built-in socket type. That variable can be updated to point at a new 4/5 socket type. When urllib2 looks up the variable, it uses the substitute instead of the original.

    Conceptually, what is going on is roughly akin to this:

    >>> socket = 'old_native_socket'
    >>> def urllib2(url):
             return 'Looking up', url, 'using', socket
    
    >>> socket = 'new_4_5_socket'
    >>> urllib2('http://www.python.org')
    Looking up http://www.python.org using new_4_5_socket
    

    And here is a simple monkey patch example for the math module:

    >>> import math
    >>> def area(radius):
            return math.pi * radius ** 2.0
    
    >>> math.pi = 3.1          # monkey patch
    >>> area(10)
    310.0