-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
SelfType or another way to spell "type of self" (or, How to define a copy() function) #1212
Comments
Hmm if this works it's not by design. I actually get errors when type checking the example:
To make this work properly we could provide a new kind of type: a "self" type, which is basically the concrete type of We could have something like this:
|
Oh, I could have sworn that it worked but it was late. :-( So yes, I guess this is two feature requests: |
(It seems to work when the class is generic, but who knows what other effects that has.) |
This was closed accidentally? |
Could we make this work without new syntax? The following might have potential: T = TypeVar('T', bound='Copyable') # 'Copyable' is a forward ref
class Copyable:
def copy(self: T) -> T: ... Mypy would have to special-case the snot out of this, and I don't know if bound='ForwardRef' currently works, but it would avoid a new magic variable. We could use something similar based on # Same as above, then
@classmethod
def factory(cls: Type[T]) -> T: ... |
Eeeewww... But, more seriously, why exactly does this need to be special-cased? Other than the forward reference, this seems to generally be what I would think would be normal. Is it because the type passed to |
Mostly because it's self. And because AFAIK it doesn't work now. :-)
|
Ah, ok. So the "special-casing" is allowing |
The bound may not even be necessary. The key behavior we'd want that's not implemented right now is that if you have a subclass: class C(Copyable):
pass
x = C().copy()
y = C.factory() we want the type of x and y to be C. |
This seems to me like it could work. This would save us from having to define a special "self type", and we wouldn't need to update the implementation of A remaining open issue is enforcing a compatible Here is a more detailed example of how this could work internally:
We'd probably have to special case at least these things:
In phase 2, figure out |
As discussed in python/typing#107, with
If |
Yes, but it would need a type variable. |
Oops, yes! Corrected. |
I recently ran into the factory use-case @gvanrossum suggested in a real application, trying to write something like this:
As expected, this doesn't work. Possible workarounds are to move |
@elazarg Do you want to give this a try? We'll also need some new words for PEP 484 on the issue (but no new syntax, technically). The idea is as follows:
There are probably some additional implied constraints on the type of |
Oh, forgot that the PEP text is ready to go: python/peps#89 Having a working implementation in mypy would help here. |
I will try. |
Thank you! It shouldn't be too hard, mostly you should probably just
disable setting the type of 'cls' or 'self' to something derived from the
current class when it's specified explicitly; the existing machinery should
take over from there.
|
It also requires instantiating the typevars in the signature at member access, and checking for strictly-covariant overriding. We'll see what's more. |
It will be nice to have this declaration in the typing module: Self = TypeVar('Self', covariant=True) Where the bound is documented to be that of the current class (lexically). I have a feeling that the suggested syntax, although nice and seemingly intuitive, is somewhat misleading. It looks like it is standard type variable, but it must be exactly the type of self, so it must be strictly covariant and cannot be used (bare) as the type of other parameters. This leaves place for mistakes that can be avoided or have better diagnostics by avoiding the declaration of the type of self. It can also be parametrized: from typing import Self, TypeVar
T = TypeVar('T')
class MyList(Generic(T)):
@classmethod
def new(cls) -> Self: <make one>
def copy(self) -> Self: <make a copy>
def twice(self) -> Tuple[Self, Self]: <make two copies>
def copy_as_mapped(self, mapper: Callable[[T], Bla]) -> Self[Bla]: <make a mapped copy>
def copy2(self, another: Self) -> Self: ... # Error: using bare Self in parameters is unsafe It is also somewhat more greppable, although |
(It may also ease the implementation, but that's not really important) |
The problem with adding a new typevar (or anything) to typing.py is that
we'd have to wait for the next typing.py release, and it only makes sense
to release that ~concurrently with Python 3.5.x releases.
|
We have this now. PEP change: python/peps@ada7d35 mypy changes: ba85545 and several followups (e.g. 8a5f463, a90841f) |
For anyone else that stumbles on this and is looking for the SelfType application, this helped me: |
…4298) The return type of the BaseException.with_traceback() method [1] is not specific enough. The return type is guaranteed to be of the same type as ‘self’, which is usually a subclass of BaseException. In fact, .with_traceback() returns ‘self’: try: raise ValueError except Exception as exc: assert exc.with_traceback(None) is exc Fix the annotation to reflect this using the self-type annotation technique described in PEP484 [2], which is supported by (at least) mypy [3]. [1] https://docs.python.org/3/library/exceptions.html#BaseException.with_traceback [2] https://www.python.org/dev/peps/pep-0484/#annotating-instance-and-class-methods [3] python/mypy#1212
* Allow covariants of __enter__, __aenter__, and @classmethod The problem we currently have is the return type of classes such as Client does not allow covariants when subclassing or context manager. In other words: ```python class Base: def __enter__(self) -> Base: # XXX return self class Derived(Base): ... with Derived() as derived: # The type of derived is Base but not Derived. It is WRONG ... ``` There are three approaches to improve type annotations. 1. Just do not type-annotate and let the type checker infer `return self`. 2. Use a generic type with a covariant bound `_AsyncClient = TypeVar('_AsyncClient', bound=AsyncClient)` 3. Use a generic type `T = TypeVar('T')` or `Self = TypeVar('Self')` They have pros and cons. 1. It just works and is not friendly to developers as there is no type annotation at the first sight. A developer has to reveal its type via a type checker. Aslo, documentation tools that rely on type annotations lack the type. I haven't found any python docuementation tools that rely on type inference to infer `return self`. There are some tools simply check annotations. 2. This approach is correct and has a nice covariant bound that adds type safety. It is also nice to documentation tools and _somewhat_ friendly to developers. Type checkers, pyright that I use, always shows the the bounded type '_AsyncClient' rather than the subtype. Aslo, it requires more key strokes. Not good, not good. It is used by `BaseException.with_traceback` See https://github.com/python/typeshed/pull/4298/files 3. This approach always type checks, and I believe it _will_ be the official solution in the future. Fun fact, Rust has a Self type keyword. It is slightly unfriendly to documentation, but is simple to implement and easy to understand for developers. Most importantly, type checkers love it. See python/mypy#1212 But, we can have 2 and 3 combined: ```python _Base = typing.TypeVar('_Base', bound=Base) class Base: def __enter__(self: _Base) -> _Base: return self class Derive(Base): ... with Derived() as derived: ... # type of derived is Derived and it's a subtype of Base ``` * revert back type of of SteamContextManager to Response * Remove unused type definitions * Add comment and link to PEP484 for clarification * Switch to `T = TypeVar("T", covariant=True)` * fixup! Switch to `T = TypeVar("T", covariant=True)` * Add back bound=xxx in TypeVar Co-authored-by: Florimond Manca <florimond.manca@gmail.com>
* Allow covariants of __enter__, __aenter__, and @classmethod The problem we currently have is the return type of classes such as Client does not allow covariants when subclassing or context manager. In other words: ```python class Base: def __enter__(self) -> Base: # XXX return self class Derived(Base): ... with Derived() as derived: # The type of derived is Base but not Derived. It is WRONG ... ``` There are three approaches to improve type annotations. 1. Just do not type-annotate and let the type checker infer `return self`. 2. Use a generic type with a covariant bound `_AsyncClient = TypeVar('_AsyncClient', bound=AsyncClient)` 3. Use a generic type `T = TypeVar('T')` or `Self = TypeVar('Self')` They have pros and cons. 1. It just works and is not friendly to developers as there is no type annotation at the first sight. A developer has to reveal its type via a type checker. Aslo, documentation tools that rely on type annotations lack the type. I haven't found any python docuementation tools that rely on type inference to infer `return self`. There are some tools simply check annotations. 2. This approach is correct and has a nice covariant bound that adds type safety. It is also nice to documentation tools and _somewhat_ friendly to developers. Type checkers, pyright that I use, always shows the the bounded type '_AsyncClient' rather than the subtype. Aslo, it requires more key strokes. Not good, not good. It is used by `BaseException.with_traceback` See https://github.com/python/typeshed/pull/4298/files 3. This approach always type checks, and I believe it _will_ be the official solution in the future. Fun fact, Rust has a Self type keyword. It is slightly unfriendly to documentation, but is simple to implement and easy to understand for developers. Most importantly, type checkers love it. See python/mypy#1212 But, we can have 2 and 3 combined: ```python _Base = typing.TypeVar('_Base', bound=Base) class Base: def __enter__(self: _Base) -> _Base: return self class Derive(Base): ... with Derived() as derived: ... # type of derived is Derived and it's a subtype of Base ``` * revert back type of of SteamContextManager to Response * Remove unused type definitions * Add comment and link to PEP484 for clarification * Switch to `T = TypeVar("T", covariant=True)` * fixup! Switch to `T = TypeVar("T", covariant=True)` * Add back bound=xxx in TypeVar Co-authored-by: Florimond Manca <florimond.manca@gmail.com>
A colleague and I were wondering how to define a copy() method in a base class so that when called on an instance of a subclass it is known that it returns an instance of that subclass. We found the following solution:
Note that the
# type: ignore
is required for two reasons: you can't callself.__class__()
, and it doesn't like the return type. Try e.g.return self
and you getIt works in
--py2
mode too:UPDATE: This doesn't actually work. :-( See next comment.
The text was updated successfully, but these errors were encountered: