summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2021-07-29 12:07:00 (GMT)
committerGitHub <noreply@github.com>2021-07-29 12:07:00 (GMT)
commitd20f1095a6a51ee8f41ef445a009d26256e1fa61 (patch)
tree68686cce3fd886bd818501a8b8f305c5d06d2d49
parent761c641f19838517bcf8df5b91d2eb46880efe68 (diff)
downloadcpython-d20f1095a6a51ee8f41ef445a009d26256e1fa61.zip
cpython-d20f1095a6a51ee8f41ef445a009d26256e1fa61.tar.gz
cpython-d20f1095a6a51ee8f41ef445a009d26256e1fa61.tar.bz2
bpo-44752: Make rlcompleter not call `@property` methods (GH-27401) (GH-27444)
* rlcompleter was calling these methods to identify whether to add parenthesis to the completion, based on if the attribute is callable. * for property objects, completion with parenthesis are never desirable. * property methods with print statements behaved very strangely, which was especially unfriendly to language newcomers. <tab> could suddenly produce output unexpectedly. (cherry picked from commit 50de8f74f8e92b20e76438c22b6a8f91afd6df75) Co-authored-by: Jack DeVries <58614260+jdevries3133@users.noreply.github.com>
-rw-r--r--Lib/rlcompleter.py10
-rw-r--r--Lib/test/test_rlcompleter.py32
-rw-r--r--Misc/NEWS.d/next/Library/2021-07-27-22-11-29.bpo-44752._bvbrZ.rst2
3 files changed, 40 insertions, 4 deletions
diff --git a/Lib/rlcompleter.py b/Lib/rlcompleter.py
index c06388e..34b2599 100644
--- a/Lib/rlcompleter.py
+++ b/Lib/rlcompleter.py
@@ -176,6 +176,16 @@ class Completer:
if (word[:n] == attr and
not (noprefix and word[:n+1] == noprefix)):
match = "%s.%s" % (expr, word)
+ if isinstance(getattr(type(thisobject), word, None),
+ property):
+ # bpo-44752: thisobject.word is a method decorated by
+ # `@property`. What follows applies a postfix if
+ # thisobject.word is callable, but know we know that
+ # this is not callable (because it is a property).
+ # Also, getattr(thisobject, word) will evaluate the
+ # property method, which is not desirable.
+ matches.append(match)
+ continue
try:
val = getattr(thisobject, word)
except Exception:
diff --git a/Lib/test/test_rlcompleter.py b/Lib/test/test_rlcompleter.py
index ee3019d..1f7a6ed 100644
--- a/Lib/test/test_rlcompleter.py
+++ b/Lib/test/test_rlcompleter.py
@@ -81,17 +81,41 @@ class TestRlcompleter(unittest.TestCase):
if x.startswith('s')])
def test_excessive_getattr(self):
- # Ensure getattr() is invoked no more than once per attribute
+ """Ensure getattr() is invoked no more than once per attribute"""
+
+ # note the special case for @property methods below; that is why
+ # we use __dir__ and __getattr__ in class Foo to create a "magic"
+ # class attribute 'bar'. This forces `getattr` to call __getattr__
+ # (which is doesn't necessarily do).
class Foo:
calls = 0
+ bar = ''
+ def __getattribute__(self, name):
+ if name == 'bar':
+ self.calls += 1
+ return None
+ return super().__getattribute__(name)
+
+ f = Foo()
+ completer = rlcompleter.Completer(dict(f=f))
+ self.assertEqual(completer.complete('f.b', 0), 'f.bar')
+ self.assertEqual(f.calls, 1)
+
+ def test_property_method_not_called(self):
+ class Foo:
+ _bar = 0
+ property_called = False
+
@property
def bar(self):
- self.calls += 1
- return None
+ self.property_called = True
+ return self._bar
+
f = Foo()
completer = rlcompleter.Completer(dict(f=f))
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
- self.assertEqual(f.calls, 1)
+ self.assertFalse(f.property_called)
+
def test_uncreated_attr(self):
# Attributes like properties and slots should be completed even when
diff --git a/Misc/NEWS.d/next/Library/2021-07-27-22-11-29.bpo-44752._bvbrZ.rst b/Misc/NEWS.d/next/Library/2021-07-27-22-11-29.bpo-44752._bvbrZ.rst
new file mode 100644
index 0000000..0d8a2cd
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-07-27-22-11-29.bpo-44752._bvbrZ.rst
@@ -0,0 +1,2 @@
+:mod:`rcompleter` does not call :func:`getattr` on :class:`property` objects
+to avoid the side-effect of evaluating the corresponding method.