-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
For class variables, lookup type in base classes (#1338, #2022, #2211) #2380
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
Conversation
I am having a hard time figuring out how TypeVars could be resolved. Any pointers (given the rest of the patch is a good idea) would be very welcome. For now I simply disabled inheriting TypeVars from the base class, and possibly it is even something for another PR. |
This avoids looking into the base class of a base class while the type has been changed
I'm excited that you're working on this! I'm not sure I can answer your questions (maybe @JukkaL can help). Personally I'm not sure whether type variables declared in a class should ever be overridden. However I don't see any tests for generic classes -- surely there are all sorts of interesting cases when the base class is generic over T and the class variable's type involves T, and then the subclass either substitutes a specific type for T, or a different type variable, etc... regarding |
Found a way to lookup TypeVars. Added some test cases for it, let me know if you can think of more juicy ones.
|
python/mypy#2380 showed a discrepancy between object and FunctionType in stdlib2. The first defined __doc__ to be str, the second Optional[str]. As FunctionType depends on object, this is no longer valid. As suggested by @gvanrossum in python/mypy#2380, all __doc__ should be considered Optional. (Final verdict was just to remove most __doc__ attributes since it's inherited from object.)
You can probably use Example where this would be useful:
If that doesn't work for some reason, it's also okay to reject assignments to class variables with type variables in them and file an issue about supporting them. |
I'm not sure it's a good idea to make those errors. I expect those to be
common, as stand-in for instance variables. (Also encouraged by PEP 526.)
…--Guido (mobile)
|
I indeed found out that This should implement all cases I could find (including TypeVars). Let me know if you can think up any more test cases to include! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pretty neat small change! I have a few questions.
class A: | ||
a = 1 # type: int | ||
class B(A): | ||
a = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, this would become invalid with strict none checking. Maybe just use another int value?
a = "a" | ||
[out] | ||
main: note: In class "B": | ||
main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a large class hierarchy this error could be pretty mysterious. Can you figure out a way to at least link to the superclass where the variable is declared first? (Even better the exact line, but that may be tricky.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is an issue for only class variables; in general it would be nice if we can tell where the original declaration came from. As the same issue is already true for when using "self.a = 1" for example. I will fiddle with this a bit, see what I can come up with.
class A: | ||
a = None # type: int | ||
class B(A): | ||
a = None # type: str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, are you sure that this (without caring about C even) should be allowed? I think it's just as much an error as when no type is given. (However, the declared type here should be allowed to be a subclass of the type declared earlier.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Class variables are invariant, so the type should be equivalent (is_equivalent
) to the base class type. Example:
class A:
a = None # type: object
def f(self) -> None:
self.a = 'x'
class B(A):
a = None # type: int
def g(self) -> None:
print(self.a + 1) # ouch, could be str
Another idea for more tests: the superclass could have a method; a subclass should not be able to override this with a variable. And vice versa. |
…pe of a base class
I always love how adding a test case can show you other issues. Fixed all issues I am aware off. I also added that if a superclass defines a |
Splitted off the "find where a type was first declared" into a bug ticket and a PR in my fork. Hope you don't mind, but I would like to resolve it a bit more generic :) |
Found one additional issue. Line 35 defines I have no idea how to resolve this cleanly, as I cannot figure out what 'args' refers to. Mainly because the Python source does not reference it. Any suggestions would be appreciated. |
Ah, I see what this is. I don't know how this ended up in the stubs but the solution is to just remove these from the ConfigParser stub. (For bonus points, change it to |
Um, you synced typeshed one commit before the fix you applied. You should probably do something like this:
|
Oops, indeed, I forgot to rebase my master branch against upstream. Silly mistake :D Tnx! |
@TrueBrain This looks good to me. If you fix the merge conflict (should be easy) this can be merged. Optionally, implement/test redefining an attribute with an
|
Added #2500 as a potential follow-up task based on above discussion about showing the class that originally defined the attribute. |
python/mypy#2380 showed a discrepancy between object and FunctionType in stdlib2. The first defined __doc__ to be str, the second Optional[str]. As FunctionType depends on object, this is no longer valid. As suggested by @gvanrossum in python/mypy#2380, all __doc__ should be considered Optional. (Final verdict was just to remove most __doc__ attributes since it's inherited from object.)
With this patch, class variables are validated for their type if only the base class defines the type.
Typeshed shows 1 issue in the pyi files. doc is set by MyPy to be 'str'. In stdlib/2/types.pyi, in FunctionType, it is set to func_doc, which is 'Optional[str]'. Because with this patch assignments are checked against their definition, assigning 'Optional[str]' to 'str' is incompatible.
The solution appears simple, by doing types.pyi similar as stdlib/3: not using a level of indirection when assigning these types.
So instead of:
Doing:
If you like, I can make a PR for that change too; but I was first more curious if this PR is correct.