Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrapper/Proxy Generic Type #802

Open
hatal175 opened this issue Apr 23, 2021 · 11 comments
Open

Wrapper/Proxy Generic Type #802

hatal175 opened this issue Apr 23, 2021 · 11 comments
Labels
topic: feature Discussions about new features for Python's type annotations

Comments

@hatal175
Copy link

hatal175 commented Apr 23, 2021

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 python/mypy#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.

@gvanrossum
Copy link
Member

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.)

@bew
Copy link

bew commented Nov 9, 2021

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.

@sobolevn
Copy link
Member

sobolevn commented Nov 9, 2021

@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 🙂

@srittau srittau added the topic: feature Discussions about new features for Python's type annotations label Nov 9, 2021
@srittau srittau reopened this Nov 9, 2021
@KotlinIsland
Copy link

KotlinIsland commented Nov 13, 2021

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
Copy link

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
Copy link

KotlinIsland commented Nov 13, 2021

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

@SamB
Copy link

SamB commented Oct 4, 2022

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

@orsinium
Copy link

orsinium commented Mar 18, 2023

+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
Copy link

orsinium commented Mar 18, 2023

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:
        ...

@JasonGrace2282
Copy link

JasonGrace2282 commented Jul 11, 2024

+1 for this feature.
The library Manim has an API that looks something like

self.play(Square().animate.shift(UP))

In this case, we would want methods after .animate (which is a Proxy) to autocomplete with all the fields of Square(), without having to do weird stuff like

# this should ideally just return _AnimationBuilder[Self] and autocomplete
# like a Square, instead of this
@property
def animate(self) -> _AnimationBuilder | Self:  # Self for autocomplete
    return _AnimationBuilder(self)

Which then causes type errors because self.play doesn't accept a raw Square, but only _AnimationBuilder.

@khoover
Copy link

khoover commented Sep 16, 2024

Another +1, the Mock types from unittest.mock would roughly fall under this when given a spec. I'm also working on a threadsafe boto wrapper, where what I'd like is actually a generic ThreadLocal wrapper like (modulo name collision prevention)

import threading
from typing import TypeVar, Generic, Proxy, Callable

T = TypeVar("T")

class ThreadLocal(Generic[T], Proxy[T]):
    def __init__(self, builder: Callable[[], T]) -> None:
        self._builder = builder
        self._holder = threading.local()

    def _get_inner(self) -> T:
        try:
            return self._holder.inner
        except AttributeError:
            self._holder.inner = self._builder()
            return self._holder.inner

    def __getattr__(self, name: str) -> Any:
        return getattr(self._get_inner(), name)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: feature Discussions about new features for Python's type annotations
Projects
None yet
Development

No branches or pull requests

10 participants