typing icon indicating copy to clipboard operation
typing copied to clipboard

Wrapper/Proxy Generic Type

Open hatal175 opened this issue 4 years ago • 13 comments

I've been putting some work into typeshed pull requests and I've noticed some places where I could really have used a Proxy Generic class - meaning I can inherit from Proxy[T] and that means that whatever attributes T has, Proxy[T] also has. We don't want to directly mark this as inheritance since then the distinction of a proxy is lost.

The examples I ran into are ProxyType and CallableProxyType in weakref or SynchronizedBase in multiprocessing,csharedtypes. A big one would be unittest.Mock (See https://github.com/python/mypy/issues/1188). I'm sure other cases exist,

Advantages:

  • Representing a proxy type easily
  • Allowing type checkers to enforce typing

Disadvantages:

  • Functions that get T won't accept Proxy[T] as an argument. Although the current state is probably something similar.

I'd really like to hear some opinions on the idea.

hatal175 avatar Apr 23 '21 09:04 hatal175

Yeah, this looks like it would be a useful feature to have. But you should probably propose it on typing-sig, which it where most discussion about typing features happens nowadays. And I don't see how this could be implemented without somehow special-casing it in the type checkers (like Union), which means it requires serious use cases, serious discussion, serious commitment to implementation by multiple static type checker teams, and a PEP. (OTOH if it sails smoothly through typing-sig it might be added to the typing_extensions package so it wouldn't have to wait for Python 3.11 to be usable.)

gvanrossum avatar Apr 24 '21 04:04 gvanrossum

Hello @gvanrossum,

What is the typing-sig thing you're talking about? (a repo? can't find it on GH) I'm coming from python/mypy#1188 and had the similar idea of a Proxy generic type (or intersection?) to solve the problem and have Mock[T] be typed like a Mock + Proxy[T] or similar.

Has there been other discussions after this issue? Could you link to them from here?

Thanks a lot in advance.

bew avatar Nov 09 '21 10:11 bew

@bew right now it was decided to move from typing-sig (it is a mailing list: https://mail.python.org/mailman3/lists/typing-sig.python.org/) to here 🙂

sobolevn avatar Nov 09 '21 10:11 sobolevn

This feature would be extremely based.

JS has a proxy type that is fully supported in TS.

class Target {
  message1 = "hello"
  message2 = "everyone"
};

class Handler<T> {
  get = (target: T, prop: keyof T, receiver: any) => target[prop] + " world"
};

const proxy = new Proxy(new Target, new Handler);
console.log(proxy.message1) // "hello world"

KotlinIsland avatar Nov 13 '21 03:11 KotlinIsland

from typing import TypeVar, Generic

T = TypeVar("T")


class _Proxy(Generic[T]):
    def __new__(cls, target: T) -> T:
        return super().__new__(cls)

    def __init__(self, target: T) -> None:
        self.target: T = target

    def __getattribute__(self, name: str) -> object:
        print(name)
        return getattr(object.__getattribute__(self, "target"), name)


def Proxy(target: T) -> T:
    """We use a factory function because you can't lie about the return type in `__new__`"""
    return _Proxy(target)  # type: ignore


class A:
    i = 1


a = Proxy(A())

print(a.i)  # 1
print(a)  # <__main__._Proxy object at 0x00000209256BA260>
print(isinstance(a, A))  # true
print(isinstance(a, _Proxy))  # True
print(type(a))  # _Proxy

You can get this functionality be hacking around making a factory function to produce the proxy and lie and ignore it's type.

Would be a little nicer if we could lie about the return type in __new__.

This is obviously not ideal as there are many complications around things like static methods and stuff.

KotlinIsland avatar Nov 13 '21 06:11 KotlinIsland

An even more based idea would be being able to have a generic base type,

Pseudo code alert!

class GenericSub(GenericBase[T]):
    def foo() -> None: ....

class A:
    def bar() -> None: ...

a = GenericSub[A]()

a.foo()
a.bar()
reveal_type(a)  # GenericSub[A]
isinstance(a, A)  # True
isinstance(a, GenericSub)  # True

KotlinIsland avatar Nov 13 '21 06:11 KotlinIsland

I think the first thing we want is a way to extract a structural type from a nominal type: it would have all of the same attributes, but not be a subclass.

Then we might want a way to transform types of things, but that gets complicated: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html

SamB avatar Oct 04 '22 17:10 SamB

+1 for the feature. My use case is I'm writing my own mock class with the following API:

patcher = Patcher(SomeClass)
patcher.some_value = 13

It would be great if mypy would check in this case that SomeClass class has some_value attribute of the type int.

Other use-cases include the built-in setattr and delattr that could check if the passed second argument, if static, is present in instance attributes.

orsinium avatar Mar 18 '23 07:03 orsinium

I think the API for the proxy type could look similar to ParamSpec:

A = AttrSpec('A')

class Proxy(Generic[A]):
    def __setattr__(self, name: A.name, value: A.value) -> None:
        ...

    def __delattr__(self, name: A.name) -> None:
        ...

    def __getattr__(self, name: A.name) -> A.value:
        ...

orsinium avatar Mar 18 '23 07:03 orsinium