mirror of
https://github.com/python/cpython.git
synced 2024-11-24 18:34:43 +08:00
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:
parent
e5c8ddb171
commit
50de8f74f8
@ -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…
Reference in New Issue
Block a user