You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
functools.cached_property doc warnings about key-sharing are obsolete #101815
The documentation for functools.cached_property currently says "this decorator interferes with the operation of PEP 412 key-sharing dictionaries. This means that instance dictionaries can take more space than usual." This warning is no longer accurate as of Python 3.12; dict key-sharing is now resilient to instance attributes being created in different orders across different instances of the same type, as happens with cached properties; this no longer causes keys to become unshared for the type. The different keys are just appended to the shared keys for the type and some instances may store a NULL for some keys.
It remains true that cached_property can cause instances to use slightly more memory, but the reason for this has changed (and the extra memory used will be significantly less.) The new reason is that any access of __dict__ on an instance will force creation of a real dictionary object for that instance, rather than just a PyDictValues. The keys remain shared in the created dict, though. So the added memory use is no longer "all the keys" but rather just "a PyDictObject" (which consists of only three fields.)
I'll file a PR to update the wording in the docs so that it is accurate to the new behavior.
(Accessing __dict__ in cached_property is probably not strictly required, but that's a topic for a separate issue/PR.)
There's a subtlety here I hadn't yet understood when I initially wrote this issue. In order to reduce wasted space in instances' value arrays, each new instance created shrinks the dk_usable counter in the type's shared keys by 1 (if there is unused space remaining in the shared keys) before creating the instance's values array (with a size matching the current total size of the shared keys). (See init_inline_values in dictobject.c.) This means that eventually, as more instances are created, the size of the shared keys will shrink to match its used entries: the available room for growth in the shared keys will shrink to zero.
So if the cached property is accessed on one of the early instances created, there will be no problem with key sharing; an entry will be made for the cached property in the shared keys while it still has extra room. But if many instances are created before the cached property is ever accessed on any of them, the "extra room" in the shared keys will shrink to zero, and then accessing the cached property on any instance will remove that instance from key sharing. So whether cached_property use interferes with key sharing or not is now heavily dependent on the specific usage pattern of the application.
These details of how instance attributes are managed are not documented or stable; it has changed significantly in recent versions and may continue to change over time. In general, CPython performance optimizations should adapt to how Python code authors write their code, more than the reverse. So I'm still inclined to think that the cached_property docs shouldn't venture into discussion of key-sharing.
Activity
carljm commentedon Feb 10, 2023
cc @markshannon to confirm the accuracy of what I've said above re key-sharing, dict-creation, etc
pythongh-101815: fix functools.cached_property key sharing doc warnings
rhettinger commentedon Feb 11, 2023
This is a nice improvement. It was irritating to lose key-sharing.
carljm commentedon May 3, 2023
There's a subtlety here I hadn't yet understood when I initially wrote this issue. In order to reduce wasted space in instances' value arrays, each new instance created shrinks the
dk_usablecounter in the type's shared keys by 1 (if there is unused space remaining in the shared keys) before creating the instance's values array (with a size matching the current total size of the shared keys). (Seeinit_inline_valuesindictobject.c.) This means that eventually, as more instances are created, the size of the shared keys will shrink to match its used entries: the available room for growth in the shared keys will shrink to zero.So if the cached property is accessed on one of the early instances created, there will be no problem with key sharing; an entry will be made for the cached property in the shared keys while it still has extra room. But if many instances are created before the cached property is ever accessed on any of them, the "extra room" in the shared keys will shrink to zero, and then accessing the cached property on any instance will remove that instance from key sharing. So whether
cached_propertyuse interferes with key sharing or not is now heavily dependent on the specific usage pattern of the application.These details of how instance attributes are managed are not documented or stable; it has changed significantly in recent versions and may continue to change over time. In general, CPython performance optimizations should adapt to how Python code authors write their code, more than the reverse. So I'm still inclined to think that the
cached_propertydocs shouldn't venture into discussion of key-sharing.