| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
Maybe this will cut down on hash collisions
There was a problem hiding this comment.
This looks reasonable, but let's make sure @ilevkivskyi agrees.
Can you add a test (preferably one that shows what's not working without the fix)?
It's fairly unorthodox to have a PR without a bug in the tracker -- can you create a bug describing the problem and link this to it? Also please add a news item.
Sorry, something went wrong.
|
TBH I don't understand what problem is being solved. This definitely requires tests. |
Sorry, something went wrong.
|
The problem seems clear enough to me: The __hash__() and __eq__() operators compute a value that depends on __forward_value__, but it's possible to use a ForwardRef as a dict key before the latter field is assigned (it is initialized to None). If then (through an alias) its _evaluate() is called, which assigns a non-None value to __forward_value__, the hash and equality change, which violates the rules for hashable objects and confuses the dict lookup operation so the lookup will fail. I'm not clear however on why someone would use a ForwardRef as a dict key. Maybe @plokmijnuhby can explain their use case? |
Sorry, something went wrong.
|
OK, I see how the current status is wrong. On the other hand, just removing the value for evaluated forward references will make two references referring to different classes with the same name equal. I think that the first one is more important, so I am fine with just removing the __forward_value__ from __hash__() and __eq__(). |
Sorry, something went wrong.
There was a problem hiding this comment.
Mostly looks good, just one question.
Please also add a NEWS item.
Sorry, something went wrong.
|
I try a test in this PR: from typing import ForwardRef, get_type_hints
def namespace1():
a = ForwardRef('A')
class A: pass
def fun(x: a): pass
get_type_hints(fun, globals(), locals())
return hash(a)
def namespace2():
a = ForwardRef('A')
class A: pass
def fun(x: a): pass
get_type_hints(fun, globals(), locals())
return hash(a)
print(namespace1() == namespace2())
Output True in this PR and output False in master branch. I'm afraid of removing the __forward_value__ can break a legitimate code. |
Sorry, something went wrong.
|
[edited] @hongweipeng, I see what you mean now. This is definitely tricky. I think combining the __eq__ from #15628 with the __hash__ from this PR makes the most sense. |
Sorry, something went wrong.
|
@brandtbucher Yeah, I want to use __eq__ to remove duplicate item instead of using __hash__. Now I think that __eq__ is only used for ForwardRef, while others keep the original __hash__ way. |
Sorry, something went wrong.
TBH I don't really like making __hash__() and __eq__() inconsistent. There are however precedents, for example hash('abc') == hash(b'abc')is True while 'abc' == b'abc' is False. So a possible thing I could imagine is keeping only __forward_arg__, but also take into account __forward_value__ in __eq__() if it is not None for self and other. IOW I am fine with
|
Sorry, something went wrong.
|
Removing __forward_value__ can solve the recursion problem incidentally issue37806. |
Sorry, something went wrong.
Do you mean removing it from __hash__ or from both places? Generally, I would propose to keep only one PR open, because currently the discussion is scattered between two PRs. @plokmijnuhby Are you interested in continuing to work on this PR? |
Sorry, something went wrong.
|
@ilevkivskyi Yes, we can remove __forward_value__ in __hash__ and keep it in __eq__. In this case, an actually recursive object will also throw an RecursionError normally. |
Sorry, something went wrong.
|
@ilevkivskyi I'm happy to keep working on it, but I'm not quite sure how where we want to take this. It seems like there are still a few ideas bouncing around here. I mean, in theory we could remove the caching thing entirely, and just perform a lookup of the class name in a function's __globals__ whenever we need to evaluate a forward reference on it. |
Sorry, something went wrong.
|
I'm not entirely certain if this is something we'd consider to be a behavioral changing enhancement or if the current behavior is an actual bug. For now, I'll categorize it as enhancement but feel free to change it. |
Sorry, something went wrong.
Alright, I don't really agree with this but I seem to be in the minority, so I'll change it. Co-Authored-By: Brandt Bucher <brandtbucher@gmail.com>
|
Okay, here's my thoughts on forward references. @hongweipeng's code example doesn't make much sense to me because users are unlikely to evaluate references right after creating them, i.e. in the same calling context, but much more likely to have someone else's code evaluate them a little later, in a completely different calling context. The trouble is that we have no good way of determining the correct context after we've left it. There are several possible solutions to this:
|
Sorry, something went wrong.
|
So we now have two competing almost identical PRs: this one and #15650. This one was first, but the other has more tests. I think the best way is to polish this one, so I am going to close the alternative one. |
Sorry, something went wrong.
| @@ -0,0 +1,3 @@ | |||
| Forward references in the typing module now have different hash and eq methods. References will now compare equal properly if only one of them has been evaluated and the other hasn't. Hash values are now consistent across the lifetime of a forward reference. | |||
There was a problem hiding this comment.
Use `__hash__` and `__eq__` instead of just hash and eq. Also this news item is too long, couple short sentences (or even one) should be enough.
Sorry, something went wrong.
There was a problem hiding this comment.
I changed it, is that better?
Sorry, something went wrong.
Add extra tests from pull request #15650.
Co-Authored-By: Kyle Stanley <aeros167@gmail.com>
|
Also, thanks for the PR and welcome @plokmijnuhby. (: |
Sorry, something went wrong.
There was a problem hiding this comment.
Thanks for updates! LGTM now.
Sorry, something went wrong.
|
Thanks everyone for help! @plokmijnuhby To make things consistent, you might also want to add this fix to the backport at https://github.com/python/typing. |
Sorry, something went wrong.
|
Thanks @plokmijnuhby for the PR, and @ilevkivskyi for merging it 🌮🎉.. I'm working now to backport this PR to: 3.8. |
Sorry, something went wrong.
|
GH-16128 is a backport of this pull request to the 3.8 branch. |
Sorry, something went wrong.
Ideally if we stick a ForwardRef in a dictionary we would like to reliably be able to get it out again. https://bugs.python.org/issue37953 (cherry picked from commit e082e7c) Co-authored-by: plokmijnuhby <39633434+plokmijnuhby@users.noreply.github.com>
Ideally if we stick a ForwardRef in a dictionary we would like to reliably be able to get it out again. https://bugs.python.org/issue37953 (cherry picked from commit e082e7c) Co-authored-by: plokmijnuhby <39633434+plokmijnuhby@users.noreply.github.com>
| Back | FazBrowse Home | New Git URL |
Ideally if we stick a ForwardRef in a dictionary we would like to reliably be able to get it out again.
https://bugs.python.org/issue37953