bpo-44752: Make rlcompleter not call @property methods (GH-27401)

* 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.
This commit is contained in:
Jack DeVries 2021-07-29 07:40:29 -04:00 committed by GitHub
parent e5c8ddb171
commit 50de8f74f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 41 additions and 5 deletions

View File

@ -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:

View File

@ -81,18 +81,42 @@ 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
@property
def bar(self):
self.calls += 1
return None
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.property_called = True
return self._bar
f = Foo()
completer = rlcompleter.Completer(dict(f=f))
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
self.assertFalse(f.property_called)
def test_uncreated_attr(self):
# Attributes like properties and slots should be completed even when
# they haven't been created on an instance

View File

@ -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.