Search code examples
pythonproxy-classes

Proxy class that isn't yet defined


Consider a registry with a dict-like interface. Each key is a string name and each value is a class. Using it in this order works:

registry['foo'] = FooClass
cls = registry['foo']
instance = cls

But in this order it wouldn't of course:

cls = registry['foo']
registry['foo'] = FooClass
instance = cls()

To support the second use-case, I implemented a class constructor wrapper in a function but it "denaturates" the class. I mean that this won't work:

cls = registry['foo']
registry['foo'] = FooClass
issubclass(cls, FooClass)

I'd like to support that third case, so I'm looking for a better way to proxy the class registry items.


Solution

  • Interesting problem, I would try something like this:

    from abc import ABCMeta
    
    
    class Registry(object):
    
        def __init__(self):
            self._proxies = {}
            self._classes = {}
    
        def resolve(self, name):
            try:
                return self._classes[name]
            except KeyError:
                raise KeyError('Cannot resolve "%s".'
                               ' Class not registered yet.' % name)
    
        def __getitem__(self, name):
            """Return a proxy class bound to `name`."""
            if name not in self._proxies:
                self._proxies[name] = make_proxy(lambda: self.resolve(name))
            return self._proxies[name]
    
        def __setitem__(self, name, val):
            """Store a class for `name`."""
            self._classes[name] = val
    
    
    def make_proxy(resolve):
        """
        Return a proxy class.
    
        :param resolve: a function that returns the actual class
    
        """
    
        class ProxyMeta(ABCMeta):
            """
            Custom meta class based on ABCMeta that forwards various checks
            to the resolved class.
    
            """
            def __eq__(self, y):
                return resolve() == y
    
            def __repr__(self):
                return repr(resolve())
    
            def __str__(self):
                return str(resolve())
    
        class Proxy(object):
            """
            The actual proxy class.
    
            """
            __metaclass__ = ProxyMeta
    
            def __new__(cls, *args, **kwargs):
                """Calling this class returns an instance of the resolved class."""
                return resolve()(*args, **kwargs)
    
            @classmethod
            def __subclasshook__(cls, subclass):
                """issubclass() overwrite."""
                return issubclass(resolve(), subclass)
    
        return Proxy
    
    >>> registry = Registry()
    >>> List = registry['list']
    >>> List
    KeyError: 'Cannot resolve "list". Class not registered yet.'
    >>> registry['list'] = list
    >>> List
    <type 'list'>
    >>> issubclass(List, List)
    True
    >>> issubclass(list, List)
    True
    >>> List == list
    True
    >>> List()
    []
    >>> registry['list'] = tuple
    >>> List()
    ()