-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
gh-87634: deprecate cached_property locking, add lock kwarg #98123
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
carljm
commented
Oct 9, 2022
•
edited by bedevere-bot
Loading
edited by bedevere-bot
- Issue: functools.cached_property incorrectly locks the entire descriptor on class instead of per-instance locking #87634
This causes at least one deprecation warning to be emitted in the stdlib: C:\Users\alexw\coding\cpython>python -m idlelib
Running Debug|x64 interpreter...
C:\Users\alexw\coding\cpython\Lib\platform.py:850: PendingDeprecationWarning: Locking cached_property is deprecated; use @cached_property(lock=False)
@functools.cached_property
C:\Users\alexw\coding\cpython\Lib\platform.py:850: PendingDeprecationWarning: Locking cached_property is deprecated; use @cached_property(lock=False)
@functools.cached_property (Edit: looks like that's already been caught by one of the failing tests in CI :) |
Lib/functools.py
Outdated
warn("Locking cached_property is deprecated; use @cached_property(lock=False)", | ||
PendingDeprecationWarning, 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.
Maybe use warnings._deprecated
?
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 don't think warnings._deprecated
works here, because it assumes the thing that is deprecated is a single method/function/class with a name. What would you pass in for the name argument of warnings._deprecated
in this case?
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.
You could do this:
diff --git a/Lib/functools.py b/Lib/functools.py
index 8ad150d853..5eb772e6b4 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -969,9 +969,12 @@ def __init__(self, func=None, *, lock=True):
if func is not None:
self.__doc__ = func.__doc__
if lock:
- from warnings import warn
- warn("Locking cached_property is deprecated; use @cached_property(lock=False)",
- PendingDeprecationWarning, 2)
+ import warnings
+ warnings._deprecated(
+ "lock=True default behaviour of cached_property",
+ "Locking cached_property is deprecated; use @cached_property(lock=False)",
+ remove=(3, 14)
+ )
self.lock = RLock() if lock else None
And then you'd get this behaviour:
C:\Users\alexw\coding\cpython>python
Running Debug|x64 interpreter...
Python 3.12.0a0 (main, Oct 9 2022, 08:02:35) [MSC v.1932 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> # Behaviour on 3.12:
>>>
>>> import functools
>>> x = functools.cached_property(lambda self: 42)
<stdin>:1: DeprecationWarning: Locking cached_property is deprecated; use @cached_property(lock=False)
>>>
>>> # Behaviour when we get to 3.14 beta if the default hasn't been changed yet:
>>>
>>> import sys
>>> sys.version_info = 3, 14, 0, "beta"
>>> del sys.modules["warnings"]; del sys.modules["functools"]
>>> import functools
>>> x = functools.cached_property(lambda self: 42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Users\alexw\coding\cpython\Lib\functools.py", line 973, in __init__
warnings._deprecated(
File "C:\Users\alexw\coding\cpython\Lib\warnings.py", line 511, in _deprecated
raise RuntimeError(msg)
RuntimeError: 'lock=True default behaviour of cached_property' was slated for removal after Python 3.14 alpha
I agree that this probably isn't the use case warnings._deprecated
was designed for... but it seems to work pretty well? ;)
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.
Also, any reason you're using PendingDeprecationWarning
instead of DeprecationWarning
, btw?
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.
You could do this
Ok, agreed, using a full noun phrase as the name does seem to work fine. I can make this change.
any reason you're using PendingDeprecationWarning instead of DeprecationWarning
I thought the general preference was to provide one release of PendingDeprecationWarning
before moving to DeprecationWarning
in the next release, and then removing in the release after that?
Ripping out the locking logic seems reasonable — as you say, it was a mistake to include it in the first place. That said, this PR's approach to deprecation is going to be rough on users and will leave a permanent mark on the API. ISTM the most graceful thing we could do is to just fix the locking logic. I put some effort into this but it required more deep thought than I had time for. It might be a good sprint topic. |
I agree that this would be the best thing to do, if we can figure out how to do it. The fallout from this PR just on the stdlib and the CPython test suite is pretty concerning. And if users want to write Python code that's compatible on both Python 3.11 and Python 3.12, while avoiding import sys
if sys.version_info >= (3, 12):
import functools
cached_property = functools.partial(functools.cached_property, lock=False)
else:
from functools import cached_property (If we do decide to go with the approach proposed in this PR, it might be worth adding the above snippet to the "What's new in 3.12" entry.) |
Also, if locking is going to be "left to the users", there should be an example of how to make sure a call doesn't get made more than once. The current way can be catastrophically inefficient but at least it isn't wrong. |
I agree this deprecation is unpleasant, but I still think it might be the best option available. The transition pain is short term; the more important question is which long-term state we prefer to reach. I think the need for locking around a Do we know of any real-world use of "Fixing the locking" is not without tradeoffs, even if we reach a solution that we believe is correct. For one thing, So I believe that the lock-free version of Even if the deprecation of locking
I don't think the mark on the API has to be permanent, though cleaning it up would require another deprecation of the |
* main: (37 commits) pythongh-98251: Allow venv to pass along PYTHON* variables to pip and ensurepip when they do not impact path resolution (pythonGH-98259) Bpo-41246: IOCP Proactor avoid callback code duplication (python#21399) bpo-46364: Use sockets for stdin of asyncio only on AIX (python#30596) pythongh-98178: syslog() is not thread-safe on macOS (python#98213) Mark all targets in `Doc/Makefile` as `PHONY` (pythonGH-98189) pythongh-97982: Factorize PyUnicode_Count() and unicode_count() code (python#98025) pythongh-96265: Formatting changes for faq/general (python#98129) tutorial: remove "with single quotes" (python#98204) pythongh-97669: Remove Tools/scripts/startuptime.py (python#98214) signalmodule.c uses _PyErr_WriteUnraisableMsg() (python#98217) pythongh-97669: Fix test_tools reference leak (python#98216) pythongh-97669: Create Tools/patchcheck/ directory (python#98186) pythongh-65046: Link to logging cookbook from asyncio docs (python#98207) Formatting fixes in contextlib docs (python#98111) pythongh-95276: Add callable entry to the glossary (python#95738) pythongh-96130: Rephrase use of "typecheck" verb for clarity (python#98144) Fix some incorrect indentation around the main switch (python#98177) pythongh-98172: Fix formatting in `except*` docs (python#98173) pythongh-97982: Remove asciilib_count() (python#98164) pythongh-95756: Free and NULL-out code caches when needed (pythonGH-98181) ...
From a user point of view, it would be better to rip out the functionality all at once than to force EVERY user of Also, it is still possible to just fix the code. It will just take someone with the skill, time, and interest to do so. From a user point of view, this is the best possible option. (Perhaps have our developer-in-residence could work on it or the SC can hire someone). Also, the fix could be backported to give relief to current users who aren't aware of the downsides. I'm marking this PR as do-not-merge because it is too catastrophic to go forward without broader discussion and collective buy in. This PR inflicts pain on 100% of the users of |
@rhettinger Yeah, makes sense. I would love to just rip out the locking functionality without deprecation (since my guess is that few if any users of In the meantime, closing this PR. Will try to find time to take a look at fixing the locking. |
@carljm This PR was closed because the deprecation would impact too many users. What about the following variation? Add a kwarg with values
The default value is In this way most of the users (who do not use threading, so no use of the locking) will have the old behaviour for a couple of releases, and then switch to the desired implementation. Only users that make use of the locking will get a deprecation warning, but one that is easy to fix (e.g. update to |
@eendebakpt Interesting idea! I think the best place to keep an unfragmented discussion of options is over on the issue #87634 rather than on this closed PR, so I'll reply there. |