Search code examples
pythondesign-patternspython-typing

How to make a proxy object with typing as underlying object in python


I have a proxy class with an underlying object. I wish to pass the proxy object to a function that expects and underlying object type. How do I make the proxy typing match the underlying object?

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

    def __getattribute__(self, name):
        return getattr(self.obj, name)

    def __setattr__(self, name, value):
        setattr(self.obj, name, value)

def foo(bar: MyClass):
    ...

foo(Proxy(MyClass())) # Warning: expected 'MyClass', got 'Proxy' instead

Solution

  • Credit to @SUTerliakov for actually providing the essence of this answer in the comment on the question. The code should look something along the lines of the following:

    from typing import TYPE_CHECKING
    from my_module import MyClass
    
    if TYPE_CHECKING:
        base = MyClass
    else:
        base = object
    
    
    class Proxy(base):
        def __init__(self, obj):
            self.obj = obj
    
        def __getattribute__(self, name):
            return getattr(self.obj, name)
    
        def __setattr__(self, name, value):
            setattr(self.obj, name, value)
    
    
    def foo(bar: MyClass):
        ...
    
    foo(Proxy(MyClass())) # Works!
    

    Note that your class may not always be available - but that's no real problem. The trick works perfectly fine with TypeVars.

    # proxy.py
    from typing import TYPE_CHECKING, TypeVar
    
    
    Proxied = TypeVar('Proxied')
    if TYPE_CHECKING:
        base = Proxied
    else:
        base = object
    
    
    class Proxy(base):
        def __init__(self, obj: Proxied):
            self.obj = obj
    
        def __getattribute__(self, name):
            return getattr(self.obj, name)
    
        def __setattr__(self, name, value):
            setattr(self.obj, name, value)
    

    And in the file where you use it:

    from proxy import Proxy
    
    
    def foo(bar: MyClass):
        ...
    
    foo(Proxy(MyClass())) # Works!