-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Adds support for __slots__
assignment, refs #10801
#10864
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Improtant special case: |
I am not sure why this fails: https://app.travis-ci.com/github/python/mypy/jobs/526676991
It does not look related. |
__slots__
assignment, refs #10801__slots__
assignment, refs #10801
@hauntsaninja @JelleZijlstra friendly ping. Any chance to get this PR reviewed? 🙂 |
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.
Just a while ago I came across this behaviour of ignoring __slots__
and it didn't seem very intuitive, thanks for doing this!
@ilevkivskyi @JukkaL Hi! Any chance to push this forward? 🙂 |
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 think this looks pretty reasonable overall. Just a couple of questions/comments
mypy/checker.py
Outdated
if isinstance(lvalue, MemberExpr) and lvalue.node: | ||
name = lvalue.node.name | ||
inst = get_proper_type(self.expr_checker.accept(lvalue.expr)) | ||
if (isinstance(inst, Instance) and |
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.
If a class has a mix of slots and non-slots members, wouldn't this report an error incorrectly?
E.g.
class A:
__slots__ = ('a',)
b = 4
Edit: ah based on the tests it looks like this does not report an error incorrectly
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 will add a test case for this, thank you. It actually was not consistent with Python's runtime behavior:
class A:
__slots__ = ('a',)
b = 4
def __init__(self) -> None:
self.a = 1
self.b = 2 # error here in runtime
A()
# AttributeError: 'A' object attribute 'b' is read-only
I will change the code to make this an error with mypy
as well.
def __init__(self) -> None: | ||
self.a = 1 | ||
self.b = 2 | ||
self.missing = 3 |
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.
Isn't this an error if B
didn't inherit from another class? It seems weird to me this would be accepted since it isn't in the __slots__
for B nor in the mro.
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 how Python works in runtime. If any base class has __dict__
that the child class itself would have __dict__
. So, when using B(A)
you will implicitly have __dict__
in place. Try this:
class A:
pass # no slots
class B(A):
__slots__ = ("a", "b")
def __init__(self) -> None:
self.a = 1
self.b = 2
self.missing = 3
b = B()
b.a = 1
b.b = 2
b.missing = 3
b.extra = 4 # E: "B" has no attribute "extra"
But, without A
super class it would be different:
class B:
__slots__ = ("a", "b")
def __init__(self) -> None:
self.a = 1
self.b = 2
self.missing = 3 # AttributeError: 'B' object has no attribute 'missing'
b = B()
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 think, that I need to add this to the docs of mypy
. Do you agree?
What would be the best place for it? Common issues?
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.
Aha, I see, that makes more sense. I would probably add it to class basics: https://mypy.readthedocs.io/en/stable/class_basics.html#class-basics
Based on the mypy_primer output, it seems there are also a couple of cases that should be handled:
It also seems to report a few true errors :) |
Like just
Will do! 👍 But, tests will be added with no errors reported. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Ok, this reports too many false positives. I will fix that + add more tests for |
One more important case I've missed: This code works in runtime: class F:
__slots__ = ('a',)
def __init__(self) -> None:
self.a = {}
def __setattr__(self, k, v) -> None:
if k != 'a':
self.a[k] = v
else:
F.__dict__[k].__set__(self, v)
f = F()
f.extra = 2
print(f.a) https://stackoverflow.com/questions/19566419/can-setattr-can-be-defined-in-a-class-with-slots |
This comment has been minimized.
This comment has been minimized.
Now, let's look on cases highlighted by
|
@sobolevn wow that's great progress in the mypy_primer output, thank you! |
This comment has been minimized.
This comment has been minimized.
Looks like that |
Diff from mypy_primer, showing the effect of this PR on open source code: parso (https://github.com/davidhalter/parso.git)
+ parso/tree.py:385: error: Trying to assign name "parent" that is not in "__slots__" of type "parso.tree.NodeOrLeaf"
|
@ethanhs any other corner-cases you can think of? 🤔 |
Not really, this seems to cover things pretty well! Do you want to add the docs change in a separate PR? |
Anyways, I am here to help / fix any problems.
@ethanhs yes, I need to think about good places to fit it 👍 |
I don't think it should be. It seems to be a useful check and with the most recent changes I don't think it should raise too many false positives, and we have so many options already... |
@ethanhs agreed. Anything else I need to do to get this merged? 🙂 |
Thanks! |
Thanks, @ethanhs! I will submit a PR with the docs today 🙂 |
Description
We can now detect assignment that are not matching defined
__slots__
.Example:
In runtime it produces:
Closes #10801
Test Plan
__slots__
works__slots__
in super classes works__slots__
definitionI need to test that assignment of wrong__slots__
raise an errorI need to test that__slots__
with wrong elements raise an error__slots__
with dynamic elements are ignored__slots__ = 1
outside of class definition context should be fine__dict__
corner caseFuture steps
attrs
plugin to includeslots
if@attr.s(slots=True)
is passed