I would like to draft a PEP for a typing.Self class which would perform similarly to rust's Self keyword in function annotations. Examples ```python from typing import TypeVar F = TypeVar("F", bound="Foo") class Foo: def parse(self: F, data: bytes) -> F: ... return self ``` vs ```python from typing import Self class Foo: def parse(self, data: bytes) -> Self: ... return self ``` Another example of where this could be useful is classmethods: ```python class Foo: @classmethod def bar(cls: type[F], *args: Any, **kwargs: Any) -> F: return cls() ``` vs ```python class Foo: @classmethod def bar(cls, *args: Any, **kwargs: Any) -> Self: return cls() ``` Implementation wise it would be a copy of NoReturn, i.e. not subscriptable, a _SpecialForm and should only be valid as a return type. I haven't seen any discussion of the idea on the mailing list, so I wanted to run it by everyone. Thanks, James.
Isn't this use case addressed with string annotations? class Foo: def parse(self: "Foo", data: bytes) -> "Foo": ... return self PEP 563 should also allow for: class Foo: def parse(self: Foo, data: bytes) -> Foo: ... return self Paul On Wed, 2021-06-02 at 11:57 +0000, gobot1234yt@gmail.com wrote:
I would like to draft a PEP for a typing.Self class which would perform similarly to rust's Self keyword in function annotations.
Examples ```python from typing import TypeVar
F = TypeVar("F", bound="Foo")
class Foo: def parse(self: F, data: bytes) -> F: ... return self ``` vs ```python from typing import Self
class Foo: def parse(self, data: bytes) -> Self: ... return self ``` Another example of where this could be useful is classmethods: ```python class Foo: @classmethod def bar(cls: type[F], *args: Any, **kwargs: Any) -> F: return cls() ``` vs ```python class Foo: @classmethod def bar(cls, *args: Any, **kwargs: Any) -> Self: return cls() ```
Implementation wise it would be a copy of NoReturn, i.e. not subscriptable, a _SpecialForm and should only be valid as a return type.
I haven't seen any discussion of the idea on the mailing list, so I wanted to run it by everyone.
Thanks, James. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: pbryan@anode.ca
Why not? On Wed, 2021-06-02 at 12:24 +0000, gobot1234yt@gmail.com wrote:
That doesn't work for subclasses that wish to use the parse method _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: pbryan@anode.ca
Say parse is a method that adds attributes from the class's annotations. ```python class Foo: bar: int def parse(self, data: bytes) -> Foo: for name, type in self.__annotations__.items(): value = read_chunk(data) assert isinstance(value, type) setattr(self, name, value) return self class SubFoo: baz: str ``` If I then call reveal_type(SubFoo().parse(...)) would say that this is of type Foo, but if I use TypeVars on the self parameter it would be correct and say that the type is of SubFoo.
OK, I think I've got it. It's not clear to me though how static type checkers can infer the SubFoo type from a TypeVar that binds to Foo. Without using TypeVars, I suppose I would naively have to write the following: class Foo: def method(self) -> Foo: ... return self class Bar(Foo): def method(self) -> Bar: return super().method() With your proposal, I could eliminate such wrapping and the static type checker would infer the return value of method is Bar for the subclass: class Foo: def method(self) -> Self: ... return self class Bar(Foo): ... On Wed, 2021-06-02 at 13:15 +0000, gobot1234yt@gmail.com wrote:
Say parse is a method that adds attributes from the class's annotations. ```python class Foo: bar: int def parse(self, data: bytes) -> Foo: for name, type in self.__annotations__.items(): value = read_chunk(data) assert isinstance(value, type) setattr(self, name, value) return self
class SubFoo: baz: str ``` If I then call reveal_type(SubFoo().parse(...)) would say that this is of type Foo, but if I use TypeVars on the self parameter it would be correct and say that the type is of SubFoo. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: pbryan@anode.ca
Am 02.06.21 um 13:57 schrieb gobot1234yt@gmail.com:
I would like to draft a PEP for a typing.Self class which would perform similarly to rust's Self keyword in function annotations.
Examples ```python from typing import TypeVar
F = TypeVar("F", bound="Foo")
Nit: The bound is not necessary here, since it's implicit when using for self annotations. That said, this is a small, but nice ergonomy improvement. - Sebastian
I find that these annotations are typically unnecessary, at least when using pyright or mypy. I'm not sure about other Python type checkers. When pyright sees an unannotated `self` or `cls` parameter, it infers its type to be an internally-synthesized TypeVar that is bound to the parent class. If that method then returns `self` or `cls` or `cls()`, it infers the proper return type. It appears that mypy does something similar. Because of this behavior, I rarely need to annotate `self` or `cls` explicitly in my code. It's still required in overloads or in stub files because the return type cannot be inferred without the function's implementation. -Eric -- Eric Traut Contributor to Pyright & Pylance Microsoft Corp
Does this apply to return values as well? I suspect this is where the pain point is. On Wed, 2021-06-02 at 15:05 +0000, Eric Traut wrote:
I find that these annotations are typically unnecessary, at least when using pyright or mypy. I'm not sure about other Python type checkers.
When pyright sees an unannotated `self` or `cls` parameter, it infers its type to be an internally-synthesized TypeVar that is bound to the parent class. If that method then returns `self` or `cls` or `cls()`, it infers the proper return type. It appears that mypy does something similar.
Because of this behavior, I rarely need to annotate `self` or `cls` explicitly in my code. It's still required in overloads or in stub files because the return type cannot be inferred without the function's implementation.
-Eric
--
Eric Traut Contributor to Pyright & Pylance Microsoft Corp _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: pbryan@anode.ca
If a return type annotation is omitted, static type checkers will typically infer the return type from the implementation, by evaluating the types of all returned expressions and unioning them together. I need to amend my previous statement though. It appears that mypy doesn't implement this capability currently. I would think that it would be straightforward to implement in mypy. ```python from typing import Any class Foo: def parse(self, data: bytes): return self @classmethod def bar(cls, *args: Any, **kwargs: Any): return cls() class Bar(Foo): pass x = Bar.bar() reveal_type(x) # Pyright says "Bar", mypy says "Any" y = Bar().parse(b"") reveal_type(y) # Pyright says "Bar", mypy says "Any" ``` -- Eric Traut Contributor to Pyright & Pylance Microsoft Corp
Wouldn't it be better to have this statically type checked however and not having to relying on type checker specific features/implementations?
On Wed, Jun 2, 2021 at 8:43 AM James H-B <gobot1234yt@gmail.com> wrote:
Wouldn't it be better to have this statically type checked however and not having to relying on type checker specific features/implementations?
I'm not sure what you're saying here. Could you elaborate with an example (or two)? -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
I just don't think leaving something untyped and then letting the type checker guess at what you meant is a good idea especially as there are differences in the ways mypy and pyright will handle the snippet Eric has shown.
I wouldn't say that the type checker is guessing your intent. It's still verifying full type consistency of the function and its callers. I presume that you rely on type inference in other places within your code. Or do you provide annotations for every variable? You're correct that any time you rely on type inference (and this is true for variables as well), you may find there are differences between type checkers. I didn't realize that mypy never inferred return types for functions. If that's the case, then it would be a bigger investment to add this capability to mypy. My team maintains a Python code base with about a quarter million lines of code, and most of it uses the strictest type checking modes in pyright, which means that full type consistency is validated. We tend to annotate function return types only in cases where type inference is insufficient, probably about a quarter of the functions or methods in the code base. -- Eric Traut Contributor to Pyright & Pylance Microsoft Corp
@Eric: Explicit would still be better than implicit. As you mentioned, inference would not help with stubs that return Self, since there won't be a method body to analyze. It would be good to standardize this across typecheckers. This pattern of typing `self` or `cls` makes up about a quarter of all TypeVar definitions (in typeshed and other projects). Most people aren't familiar with the `Type[T]` trick for doing this, which means they often leave such methods without any return type. It makes sense to have a simple way to express their intentions ("return Self"). @James: We'd discussed the Self idea during the Typing Summit [1]. Happy to collaborate on this if you'd like. Another use case is a parameter that needs to be of type Self: ``` class Foo: def difference(self, other: Self) -> Self: ... def apply(self, f: Callable[[Self], None]) -> None: ... ``` [1]: Slides: https://drive.google.com/file/d/1x-qoDVY_OvLpIV1EwT7m3vm4HrgubHPG/view?usp=s... On Wed, Jun 2, 2021 at 9:30 AM Eric Traut <eric@traut.com> wrote:
I wouldn't say that the type checker is guessing your intent. It's still verifying full type consistency of the function and its callers. I presume that you rely on type inference in other places within your code. Or do you provide annotations for every variable?
You're correct that any time you rely on type inference (and this is true for variables as well), you may find there are differences between type checkers.
I didn't realize that mypy never inferred return types for functions. If that's the case, then it would be a bigger investment to add this capability to mypy.
My team maintains a Python code base with about a quarter million lines of code, and most of it uses the strictest type checking modes in pyright, which means that full type consistency is validated. We tend to annotate function return types only in cases where type inference is insufficient, probably about a quarter of the functions or methods in the code base.
--
Eric Traut Contributor to Pyright & Pylance Microsoft Corp _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: gohanpra@gmail.com
-- S Pradeep Kumar
Well, mypy never infers return types from the code. So we do recommend the trick with the typevar. It's not particularly often that you need this though, so I'm not keen on adding magic to the type system just to make that less verbose. On Wed, Jun 2, 2021 at 8:31 AM Eric Traut <eric@traut.com> wrote:
If a return type annotation is omitted, static type checkers will typically infer the return type from the implementation, by evaluating the types of all returned expressions and unioning them together.
I need to amend my previous statement though. It appears that mypy doesn't implement this capability currently. I would think that it would be straightforward to implement in mypy.
```python from typing import Any
class Foo: def parse(self, data: bytes): return self
@classmethod def bar(cls, *args: Any, **kwargs: Any): return cls()
class Bar(Foo): pass
x = Bar.bar() reveal_type(x) # Pyright says "Bar", mypy says "Any"
y = Bar().parse(b"") reveal_type(y) # Pyright says "Bar", mypy says "Any" ```
--
Eric Traut Contributor to Pyright & Pylance Microsoft Corp _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: guido@python.org
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
I just arrived on this thread after chasing down a particularly unintuitive set of type checking errors. I had just created a subclass of ZipFile in a repository that is 100% typed. This new class only added methods to the builtin ZipFile yet I was getting errors all over when I actually use this class to open a zip file in a context manager. After much looking around, and digging in the typeshed directly, I figured out that this is because ZipFile.__enter__ has a return annotation of ZipFile. So even though I did not want to alter any of the base class's ability to actually open a zip, didn't even want to have to think about how it did exactly that, I ended up having to reimplement __enter__ in my base class to keep type coverage at the level it was. I was not able to simply return super() as that gave the error `Incompatible return value type (got "ZipFile", expected "MyZipFile")`. For ZipFile this was not a big deal, as the entire body is just returning self, but for a more complicated __enter__ I could see this being a very annoying way to keep type information. Even with pyright, I get the same set of seemingly wrong error about using the variable bound by the with statement as a MyZipFile object and not just a ZipFile object, since in typeshed this function that is explicitly annotated to return ZipFile. This seems like a problem that will keep coming up as subclassing built in classes is very common and there are a handful of protocols that very often return self besides __enter__, __iter__, __getitem__, __add__, and others, all of which get explicit types in the standard lib. Are pythonistas expected to remember if the class they are inheriting from use any of these, and if they return self, and if they have an explicit type hint, and then to rebind them just to get correct types? For reference I made this minimum example when I was going to open an issue for the typeshed. I only stopped because I don't think there is any better annotation currently that can be put on ZipFile. ```python # myzip.py from typing import Sequence, TYPE_CHECKING, cast from zipfile import ZipFile class MyZipFile(ZipFile): zip_sep = "/" def get_zip_path_components(self, path: str) -> Sequence[str]: return path.split(self.zip_sep) with ZipFile("./example.zip") as zf: print(zf.namelist()) print() print(MyZipFile.zip_sep) my_zip = MyZipFile("./example.zip") print(my_zip.zip_sep) if TYPE_CHECKING: reveal_type(my_zip) print() with my_zip as zf: if TYPE_CHECKING: reveal_type(zf) cast(MyZipFile, zf) if TYPE_CHECKING: reveal_type(zf) print(zf.zip_sep) print(list(component for name in zf.namelist() for component in zf.get_zip_path_components(name))) ``` There are no errors in this script, it runs fine and produces the output you would expect. However this is what mypy says about it ```bash ❯ python3 -m mypy newzip.py newzip.py:22: note: Revealed type is 'newzip.MyZipFile' newzip.py:28: note: Revealed type is 'zipfile.ZipFile' newzip.py:31: note: Revealed type is 'zipfile.ZipFile' newzip.py:32: error: "ZipFile" has no attribute "zip_sep" newzip.py:33: error: "ZipFile" has no attribute "get_zip_path_components" Found 2 errors in 1 file (checked 1 source file) ``` pyright will generate the same notes and errors.
El lun, 21 jun 2021 a las 23:19, <ucodery@gmail.com> escribió:
I just arrived on this thread after chasing down a particularly unintuitive set of type checking errors. I had just created a subclass of ZipFile in a repository that is 100% typed. This new class only added methods to the builtin ZipFile yet I was getting errors all over when I actually use this class to open a zip file in a context manager. After much looking around, and digging in the typeshed directly, I figured out that this is because ZipFile.__enter__ has a return annotation of ZipFile. So even though I did not want to alter any of the base class's ability to actually open a zip, didn't even want to have to think about how it did exactly that, I ended up having to reimplement __enter__ in my base class to keep type coverage at the level it was.
I submitted https://github.com/python/typeshed/pull/5675 to fix this in typeshed. But I agree with your larger point that this would be easier if we had `Self`.
I was not able to simply return super() as that gave the error `Incompatible return value type (got "ZipFile", expected "MyZipFile")`. For ZipFile this was not a big deal, as the entire body is just returning self, but for a more complicated __enter__ I could see this being a very annoying way to keep type information. Even with pyright, I get the same set of seemingly wrong error about using the variable bound by the with statement as a MyZipFile object and not just a ZipFile object, since in typeshed this function that is explicitly annotated to return ZipFile. This seems like a problem that will keep coming up as subclassing built in classes is very common and there are a handful of protocols that very often return self besides __enter__, __iter__, __getitem__, __add__, and others, all of which get explicit types in the standard lib. Are pythonistas expected to remember if the class they are inheriting from use any of these, and if they return self, and if they have an explicit type hint, and then to rebind them just to get correct types?
For reference I made this minimum example when I was going to open an issue for the typeshed. I only stopped because I don't think there is any better annotation currently that can be put on ZipFile. ```python # myzip.py from typing import Sequence, TYPE_CHECKING, cast from zipfile import ZipFile
class MyZipFile(ZipFile): zip_sep = "/"
def get_zip_path_components(self, path: str) -> Sequence[str]: return path.split(self.zip_sep)
with ZipFile("./example.zip") as zf: print(zf.namelist()) print()
print(MyZipFile.zip_sep) my_zip = MyZipFile("./example.zip") print(my_zip.zip_sep) if TYPE_CHECKING: reveal_type(my_zip) print()
with my_zip as zf: if TYPE_CHECKING: reveal_type(zf) cast(MyZipFile, zf) if TYPE_CHECKING: reveal_type(zf) print(zf.zip_sep) print(list(component for name in zf.namelist() for component in zf.get_zip_path_components(name))) ``` There are no errors in this script, it runs fine and produces the output you would expect. However this is what mypy says about it ```bash ❯ python3 -m mypy newzip.py newzip.py:22: note: Revealed type is 'newzip.MyZipFile' newzip.py:28: note: Revealed type is 'zipfile.ZipFile' newzip.py:31: note: Revealed type is 'zipfile.ZipFile' newzip.py:32: error: "ZipFile" has no attribute "zip_sep" newzip.py:33: error: "ZipFile" has no attribute "get_zip_path_components" Found 2 errors in 1 file (checked 1 source file) ``` pyright will generate the same notes and errors. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: jelle.zijlstra@gmail.com
On Tue, Jun 22, 2021 at 7:47 AM Jelle Zijlstra <jelle.zijlstra@gmail.com> wrote:
El lun, 21 jun 2021 a las 23:19, <ucodery@gmail.com> escribió;
I submitted https://github.com/python/typeshed/pull/5675 to fix this in typeshed. But I agree with your larger point that this would be easier if we had `Self`.
Thanks for this. I really had no idea how to get this annotation to work. In hindsight it’s more obvious. I wonder how many other typeshed classes don’t do this for methods that return self. It was only after seeing the solution that I could find docs describing it on mypy.readthedocs. There is a note claiming that a generic self is still experimental but maybe a convention should be added to the typeshed contributing guide about using it, just like Sequence is recommended over list.
El mar, 22 jun 2021 a las 9:03, micro codery (<ucodery@gmail.com>) escribió:
On Tue, Jun 22, 2021 at 7:47 AM Jelle Zijlstra <jelle.zijlstra@gmail.com> wrote:
El lun, 21 jun 2021 a las 23:19, <ucodery@gmail.com> escribió;
I submitted https://github.com/python/typeshed/pull/5675 to fix this in typeshed. But I agree with your larger point that this would be easier if we had `Self`.
Thanks for this. I really had no idea how to get this annotation to work. In hindsight it’s more obvious. I wonder how many other typeshed classes don’t do this for methods that return self. It was only after seeing the solution that I could find docs describing it on mypy.readthedocs. There is a note claiming that a generic self is still experimental but maybe a convention should be added to the typeshed contributing guide about using it, just like Sequence is recommended over list.
I agree, filed https://github.com/python/typeshed/issues/5676 to track fixing this.
Pradeep and I have been talking over some of the knitty gritty of this idea and we are conflicted as to whether you should be able to annotate a class attribute as Self. I wanted to discuss this a bit further on typing-sig rather than waiting to add this as a feature further to the PEP down the line: Pros: - Allows type hinting recursive types e.g. a linked list or payloads in a succinct way. Currently this is possible using some property wizardry (https://mypy-play.net/?mypy=latest&python=3.8&gist=bdaf10859dad2a266730db2ea8088932), but this is hardly ideal. - Makes it have identical use cases to rust's Self (https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7d78e6bd86f1e37548f521904aadb1d4). Cons: - This requires us to add a whole new kind of construct to the Python type system (something that behaves like a TypeVar in functions but differently elsewhere). Although I think this isn't that big an issue as it could just be analogous to a lazily evaluated `__class__` - Adding it as a special case would increase the complexity burden of this PEP and might make other type checkers reject it for that reason (in mypy it doesn't seem too bad https://github.com/python/mypy/commit/a6c26dd60ee056b8c1b5d01930bd51062f06f2...). Poll here: https://www.strawpoll.me/45577679 Thanks, James.
`Self` is good. Rust has it so we know it's not at odds with a consistent type system. It would also allow annotating classmethods that return cls() in a more consistent and correct fashion -- because TypeVars can't be generic, there's no way to have a classmethod that returns a Self[T], even in trivial cases. On Sun, Aug 15, 2021, at 9:04 PM, James H-B wrote:
Pradeep and I have been talking over some of the knitty gritty of this idea and we are conflicted as to whether you should be able to annotate a class attribute as Self. I wanted to discuss this a bit further on typing-sig rather than waiting to add this as a feature further to the PEP down the line:
Pros: - Allows type hinting recursive types e.g. a linked list or payloads in a succinct way. Currently this is possible using some property wizardry (https://mypy-play.net/?mypy=latest&python=3.8&gist=bdaf10859dad2a266730db2ea8088932), but this is hardly ideal. - Makes it have identical use cases to rust's Self (https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7d78e6bd86f1e37548f521904aadb1d4).
Cons: - This requires us to add a whole new kind of construct to the Python type system (something that behaves like a TypeVar in functions but differently elsewhere). Although I think this isn't that big an issue as it could just be analogous to a lazily evaluated `__class__` - Adding it as a special case would increase the complexity burden of this PEP and might make other type checkers reject it for that reason (in mypy it doesn't seem too bad https://github.com/python/mypy/commit/a6c26dd60ee056b8c1b5d01930bd51062f06f2...).
Poll here: https://www.strawpoll.me/45577679
Thanks, James. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: rbt@sent.as
I have a potential usecase for Self but am not sure if it would be possible: class Base(Generic[_T]): def f(self) -> _T: … class SubSelf(Base[Self]): … class SubInt(Base[int]): … Here, SubSelf().f() would return an instance of SubSelf. AFAIK to implement this currently would require redefining f in SubSelf with a separate type var and annotating self with that. This has come up in django-stubs as Managers have many methods that return QuerySets and QuerySets have these same methods but return Self. Without Self, one must duplicate each method in each class where the only difference is the return type, which makes maintenance difficult.
On 16 Aug 2021, at 03.14, Rebecca Turner <rbt@sent.as> wrote:
`Self` is good. Rust has it so we know it's not at odds with a consistent type system. It would also allow annotating classmethods that return cls() in a more consistent and correct fashion -- because TypeVars can't be generic, there's no way to have a classmethod that returns a Self[T], even in trivial cases.
On Sun, Aug 15, 2021, at 9:04 PM, James H-B wrote: Pradeep and I have been talking over some of the knitty gritty of this idea and we are conflicted as to whether you should be able to annotate a class attribute as Self. I wanted to discuss this a bit further on typing-sig rather than waiting to add this as a feature further to the PEP down the line:
Pros: - Allows type hinting recursive types e.g. a linked list or payloads in a succinct way. Currently this is possible using some property wizardry (https://mypy-play.net/?mypy=latest&python=3.8&gist=bdaf10859dad2a266730db2ea8088932), but this is hardly ideal. - Makes it have identical use cases to rust's Self (https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7d78e6bd86f1e37548f521904aadb1d4).
Cons: - This requires us to add a whole new kind of construct to the Python type system (something that behaves like a TypeVar in functions but differently elsewhere). Although I think this isn't that big an issue as it could just be analogous to a lazily evaluated `__class__` - Adding it as a special case would increase the complexity burden of this PEP and might make other type checkers reject it for that reason (in mypy it doesn't seem too bad https://github.com/python/mypy/commit/a6c26dd60ee056b8c1b5d01930bd51062f06f2...).
Poll here: https://www.strawpoll.me/45577679
Thanks, James. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: rbt@sent.as
_______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: syastrov@gmail.com
Pyright already effectively implements the behavior you're describing, so it wouldn't be a complexity burden for us to expose this with a `typing.Self` type. We use this technique whenever a `self` or `cls` parameter is unannotated. Internally, we synthesize a TypeVar that is bound to the class (even if that class is generic), and this synthesized type is applied to the unannotated `self` or `cls` parameter. This allows us to correctly infer return types in cases like this: ```python class A(Generic[T]): @classmethod def allocate(cls): return cls() class B(A[T]): pass val1 = A[int].allocate() reveal_type(val1) # pyright reveals: A[int], mypy reveals: Any val2 = B[float].allocate() reveal_type(val2) # pyright reveals: B[float], mypy reveals: Any ``` -Eric -- Eric Traut Contributor to Pyright & Pylance Microsoft Corp.
Sorry, just to be a bit clearer, I am asking about being able to do: ```py class LinkedList(Generic[T]): value: T next: Self | None = None class OrdinalLinkedList(LinkedList[int]): @property def ordinal_value(self) -> int: ... reveal_type(OrdinalLinkedList(value=1, next=OrdinalLinkedList(value=2)).next) # revealed type is OrdinalLinkedList ```
I have one more thing to run by on the topic of metaclasses and the usage of Self in metaclasses: There have been concerns about allowing the use of `Self` in metaclasses as it may be confusing to which type `Self` is referring. I will present my point of view for allowing `Self` in metaclasses.
"Special cases aren't special enough to break the rules."
— Tim Peters in the Zen of Python.
In the rest of this PEP, we have `Self` stand for a type variable bound to the encapsulating class. But I ask why should a metaclass operate any differently to a normal class. Consider: ```py class MyMetaclass(type): def __new__(mcs, *args: Any) -> type[Self]: cls = super().__new__(mcs, *args) return cls def __mul__(cls, count: int) -> list[type[Self]]: return [cls] * count ``` This is different right? It's like a classmethod with its `cls` and `mcs` parameters. But if I rename the `cls`s to `self` and `mcs` to `cls`: ```py class MyNormalClass(type): def __new__(cls, *args: Any) -> Self: self = super().__new__(cls, *args) return self def __mul__(self, count: int) -> list[Self]: return [self] * count ``` this looks similar to the other examples we have in the PEP. Now I do agree there could initially be some confusion about what the type of `cls` is if it is called: ```py class MyMetaclass(type): def __new__(mcs, *args: Any) -> Self: cls = super().__new__(mcs, *args) return cls() def __mul__(cls, count: int) -> list[Self]: return [cls()] * count ``` (notice the call to `cls()`) It may not be immediately noticeable that the return type of `Self` here is incorrect. But if we rename some variables. ```py class MyNormalClass(type): def __new__(cls, *args: Any) -> Self: self = super().__new__(cls, *args) return self() def __mul__(self, count: int) -> list[Self]: return [self()] * count ``` It is much more apparent this is wrong. We are using `__call__` on our instance of `Self` in this case, [`type.__call__`](https://github.com/python/typeshed/blob/05cc30b8da576f882dff41ae31c281c859b6...) which returns `Any`, breaking the mental model we have and so should be flagged as invalid. Personally I think disallowing this would make typing more confusing for newbies and veterans alike as well as being inconsistent with everything else this PEP allows.
участники (12)
-
Eric Traut
-
gobot1234yt@gmail.com
-
Guido van Rossum
-
James H-B
-
Jelle Zijlstra
-
micro codery
-
Paul Bryan
-
Rebecca Turner
-
S Pradeep Kumar
-
Sebastian Rittau
-
syastrov@gmail.com
-
ucodery@gmail.com