Creating a proxy class in Python in another class

2.3k Views Asked by At

I'm trying to get a proxy class working, but it does not really want to work. The class that I want to proxy is the following:

import zmq


class ZmqPublish():
    def __init__(self):
        self.context = None
        self.pub_socket = None

    def setup(self, addr):
        self.context = zmq.Context()
        if isinstance(addr, str):
            addr = addr.split(':')
        host, port = addr if len(addr) == 2 else (addr[0], None)
        self.pub_socket = self.context.socket(zmq.PUB)
        self.pub_socket.bind('tcp://%s:%s' % (host, port))

    def send(self, msg_type, msg):
        self.pub_socket.send_multipart([msg_type, msg])

This simply starts a publisher and has the ability to send messages. Then I have another class that takes care of storing data and sending messages as well as a kind of reply:

from multiprocessing import Manager
from multiprocessing.managers import BaseManager

import zmq_publish


class publishManager(BaseManager):
    pass


publishManager.register('zmq_publish.ZmqPublish', zmq_publish.ZmqPublish)


class Storage:
    def __init__(self):
        self.manager = Manager()
        self.dict = self.manager.dict()
        # Additional variables
        self.host = '127.0.0.1'
        self.port = 5555
        self.bind_addr = (self.host, self.port)
        self.publishManager = Storage.publishManager()
        self.publishManager.start()
        self.publishManager.setup(self.bind_addr)

    def setup(self):
        # Set up zmq subscriber with callbacks

    def add(self, msg):
        self.dict[msg] = 0
        self.publishManager.send('type', msg)

For example the add function is given to a zmq process as a callback and it adds some information. The idea is that it can also send a message back as a response. The problem is, that the message is never send and I believe it is due to the fact that the callback process is another one than the one who created the publisher instance. Thus I am trying to proxy this class and make it available through a manager, but so far it does not work. Can somebody help me or give me some hints?

Kind regards Patrick

1

There are 1 best solutions below

2
GIZ On

I haven't understood your question totally. I'll provide you with a hint that hopefully will assist in achieving the task. You can proxy objects by overloading __getattr__ for undefined attributes accesses and/or __getattribute__ for all attribute accesses.

class Proxy:
    def __init__(self, Klass): 
        self.Klass = Klass                    # Save/embed original class/object
    def __getattr__(self, name):              # On undefined attr fetches only
        ...
        return getattr(self.Klass, name)      # Delegate to Klass/ could be an obj too
    ....

Now any attribute that's not defined at Proxy will be intercepted by __getattr__ and delegated to Klass. Klass here is a class object so we proxy the class itself, Klass can also be an actual instance of a class. Basically, Proxy delegates only undefined attribute fetches to klass. Hence, when you access an attribute that's not available for Proxy, doesn't exist in Proxy or its instances, __getattr__ gets triggered automatically and will delegate your request to the wrapped/embedded object klass. For intercepting all attribute fetches you would use __getattribute__ instead of __getattr__ and in addition to that you might need to use object.__getattribute__ to avoid recursion inside __getattribute__, because any self.attr inside your proxy methods will trigger your class's __getattribute__.

Edit: How to use the Proxy class:

If you would like to to proxy an object of another class, one way to achieve this:

class Proxy:
    # Proxy objects of other classes 
    def __init__(self, obj): 
        self.obj = obj

    def __getattr__(self, attr): 
        return getattr(self.obj, attr)  # Run only on undefined attribute accesses 

class Test:
    data = 'some data'
    def __init__(self, x, y):
        self.x = x 
        self.y = y 
    def add(self):
        return self.x + self.y 
    def mul(self): 
        return self.x * self.y 

test_obj = Test(3, 4)        
proxied_obj = Proxy(test_obj)    # Delegate some operations to test_obj

proxied_obj.add()                # Calls Proxy.__getattr__
print(proxied_obj.mul())         # Calls Proxy.__getattr__

As you can see, add and mul method doesn't actually exist in Proxy class which proxies other classes' instances. proxied_obj is an instance of Proxy class with a wrapped Test obj. *Notice, __getattr__ is like a fallback option, only when attributes are not found, __getattr__ gets called. That is, if any attribute was looked up in the Proxy instance itself or its class or superclasses was not found, then __getattr__ gets triggered.

You could add other methods in Proxy to augment the the interface of the proxied object. In 3.X and new-style classes, if your proxied object may require operator overloading methods: __add__ for + or __getitem__ for indexing, you need to redefine all the overloading methods inside Proxy or in a superclass again so builtin operations will succeed:

class Proxy:
    def __init__(self, obj): 
        self.obj = obj

    def __getattr__(self, attr): 
        return getattr(self.obj, attr)

    def   __add__  (...): ... 
    def __getitem__(...): ...

If you're still confused: OOP and Delegation: “Wrapper” Proxy Objects

I really encourage you to open Python interpreter and try to experiment with code, just small examples. One line of code might be better than 100 spoken words and vice versa.