-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Restrict dynamic attribute creation with slots #7388
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
Hi @xmunoz! Thanks so much for looking at this. I think there's probably a way to avoid manually managing the slots, instance variables, and parameters to |
Great, thanks! Yeah, I think |
After some more discussion on #7313 about |
Once the existential question about this PR is resolved, I have two follow up questions for the reviewers:
|
Hello! I am an automated bot and I have noticed that this pull request is not currently able to be merged. If you are able to either merge the |
Nope. :) @xmunoz Would it be possible for you to update this PR? |
We're green! |
I opened this pull request months ago, honestly am not too sure why I did this the way that I did. That said, I rolled back the Base class stuff locally and ran tests locally, which failed. I won't have too much time to dedicate to this in the near term, but given the concern I'll go ahead and push my local changes and leave the failures for someone else to debug. |
Scrap my last comment, looks like everything is green :) |
attr_getters = [operator.attrgetter(attr) | ||
for attr in self.__slots__] | ||
return all(getter(self) == getter(other) | ||
for getter in attr_getters) |
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.
Equality on list could have ordering issues; we probably need to cast the result; something like
self_attrs = {k: getattr(self, k) for k in self.__slots__}
other_attrs = {k: getattr(other, k) for k in other.__slots__}
return self_attrs == other_attrs
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.
We're protected by a self.__slots__ == other.__slots__
, which is reasonable with self.__class__
IMO.
Your suggestion is more robust tho.
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.
We're protected by a
self.__slots__ == other.__slots__
, which is reasonable withself.__class__
IMO.
Subclassing may result in self.__class__ != other.__class__
(therefore not guarenteeing the __slots__
declarations match), which is the main issue I have with the current implementation.
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.
Looks great to me! I'm fairly sure we can iterate on this in a follow up, and I'm biased toward merge everything-we-agree-on-and-iterate-on-the-rest. :)
Co-authored-by: Pradyun Gedam <[email protected]>
return False | ||
|
||
if self.__slots__ != other.__slots__: | ||
return False |
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.
Again, this is suspect to false positives when __slots__
is redefined.
Example:
class Foo:
__slots__ = ("a", "b")
def __init__(self, a, b):
self.a = a
self.b = b
def __eq__(self, other):
if self.__slots__ != other.__slots__:
return False
return all(
getattr(self, k) == getattr(other, k)
for k in self.__slots__
)
class Bar(Foo):
__slots__ = ("b", "a")
# False!
print(Foo(1, 2) == Bar(1, 2))
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 we want to be a little more explicit rather than pulling out a bunch of hacks here 😄 I mean there are only 2 attributes, which can be compared manually. Subclasses extending this may define their own comparison methods.
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'm inclined to leave this as-is for now.
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.
So if I understand correctly, the __eq__
should be updated to
def __eq__(self, other):
self_attrs = {k: getattr(self, k) for k in self.__slots__}
other_attrs = {k: getattr(other, k) for k in other.__slots__}
return self_attrs == other_attrs
as per this comment and #7388 (comment)
@uranusjr are you OK to iterate on the equality function in a follow up PR? I'd like to get this PR merged since it's good enough IMO. |
I am fine with merging this to master as long as the tests are able to pass. |
Fixes #7313.