mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
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 50de8f74f8
)
Co-authored-by: Jack DeVries <58614260+jdevries3133@users.noreply.github.com>
This commit is contained in:
parent
761c641f19
commit
d20f1095a6
3 changed files with 41 additions and 5 deletions
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
Loading…
Add table
Add a link
Reference in a new issue