Skip to content

Commit d20f109

Browse files
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 50de8f7) Co-authored-by: Jack DeVries <[email protected]>
1 parent 761c641 commit d20f109

File tree

3 files changed

+40
-4
lines changed

3 files changed

+40
-4
lines changed

Lib/rlcompleter.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,16 @@ def attr_matches(self, text):
176176
if (word[:n] == attr and
177177
not (noprefix and word[:n+1] == noprefix)):
178178
match = "%s.%s" % (expr, word)
179+
if isinstance(getattr(type(thisobject), word, None),
180+
property):
181+
# bpo-44752: thisobject.word is a method decorated by
182+
# `@property`. What follows applies a postfix if
183+
# thisobject.word is callable, but know we know that
184+
# this is not callable (because it is a property).
185+
# Also, getattr(thisobject, word) will evaluate the
186+
# property method, which is not desirable.
187+
matches.append(match)
188+
continue
179189
try:
180190
val = getattr(thisobject, word)
181191
except Exception:

Lib/test/test_rlcompleter.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,41 @@ def test_attr_matches(self):
8181
if x.startswith('s')])
8282

8383
def test_excessive_getattr(self):
84-
# Ensure getattr() is invoked no more than once per attribute
84+
"""Ensure getattr() is invoked no more than once per attribute"""
85+
86+
# note the special case for @property methods below; that is why
87+
# we use __dir__ and __getattr__ in class Foo to create a "magic"
88+
# class attribute 'bar'. This forces `getattr` to call __getattr__
89+
# (which is doesn't necessarily do).
8590
class Foo:
8691
calls = 0
92+
bar = ''
93+
def __getattribute__(self, name):
94+
if name == 'bar':
95+
self.calls += 1
96+
return None
97+
return super().__getattribute__(name)
98+
99+
f = Foo()
100+
completer = rlcompleter.Completer(dict(f=f))
101+
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
102+
self.assertEqual(f.calls, 1)
103+
104+
def test_property_method_not_called(self):
105+
class Foo:
106+
_bar = 0
107+
property_called = False
108+
87109
@property
88110
def bar(self):
89-
self.calls += 1
90-
return None
111+
self.property_called = True
112+
return self._bar
113+
91114
f = Foo()
92115
completer = rlcompleter.Completer(dict(f=f))
93116
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
94-
self.assertEqual(f.calls, 1)
117+
self.assertFalse(f.property_called)
118+
95119

96120
def test_uncreated_attr(self):
97121
# Attributes like properties and slots should be completed even when
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`rcompleter` does not call :func:`getattr` on :class:`property` objects
2+
to avoid the side-effect of evaluating the corresponding method.

0 commit comments

Comments
 (0)