Search code examples
pythonweak-references

Did weakref.proxy became hashable in Python 3.9?


The documentation of weakref.proxy (Python version 3.9.5) states the following:

Return a proxy to object which uses a weak reference. This supports use of the proxy in most contexts instead of requiring the explicit dereferencing used with weak reference objects. The returned object will have a type of either ProxyType or CallableProxyType, depending on whether object is callable. Proxy objects are not hashable regardless of the referent; this avoids a number of problems related to their fundamentally mutable nature, and prevent their use as dictionary keys. callback is the same as the parameter of the same name to the ref() function.

If I understand this correctly, the following code should raise an error:

import weakref


class Model:
    pass


m = Model()
proxy = weakref.proxy(m)
d = {proxy: 1}
assert d[proxy] == 1

And it does so with Python 3.8:

  File "...", line 11, in <module>
    d = {proxy: 1}
TypeError: unhashable type: 'weakproxy'

However, the code runs just fine with Python 3.9.5!

Python 3.9.5 (default, May 18 2021, 14:42:02) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import weakref
>>>
>>>
>>> class Model:
...     pass
...
>>>
>>> m = Model()
>>> proxy = weakref.proxy(m)
>>> print(type(proxy))
<class 'weakproxy'>
>>> d = {proxy: 1}
>>> assert d[proxy] == 1
>>> d
{<weakproxy at 0x000001E3DCF665E0 to Model at 0x000001E3DC9DA100>: 1}

The code could be shorter if print(hash(proxy)) is used which--again--raises TypeError for Python 3.8 but not for Python 3.9:

import weakref

class Model:
    pass

m = Model()
proxy = weakref.proxy(m)
ref = weakref.ref(m)
print(hash(m))     # >>> 159803004579 (Python 3.8 + 3.9)
print(hash(ref))   # >>> 159803004579 (Python 3.8 + 3.9)
print(hash(proxy)) # >>> TypeError (Python 3.8) 159803004579 (Python 3.9)

Does this mean weakref.proxy is now hashable even though the documentation states otherwise or did I miss something fundamental somwehere?


Solution

  • Short answer: Yes there was a hash pass-through in weakref.proxy but the next releases (3.9.6 and 3.10.0) will have this removed and weakref.proxy will raise errors again when used for instance as a key in a dictionary.

    Longer answer: As Carcigenicate mentioned in his answer, a hash pass-through was committed in May 2020. I filed an issue since the documentation seemed outdated. However, after some discussion it was decided to revert to the old behavior and remove the pass-through for weakref.proxy from Python 3.9, 3.10 and 3.11. This means using weakref.proxy as a dictionary key will raise an error in future releases (again). The developers argued that this omission was most likely intentional and that keeping it that way will save some headaches for some users (ref).