Skip to content

Commit 0cd5a67

Browse files
author
Erlend E. Aasland
committed
Merge branch 'main' into sqlite-rollback
Sync with main bco. pythonGH-27273
2 parents f5d5e68 + 6b61d74 commit 0cd5a67

File tree

14 files changed

+227
-15
lines changed

14 files changed

+227
-15
lines changed

Doc/library/ast.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1621,7 +1621,7 @@ Function and class definitions
16211621
A function definition.
16221622

16231623
* ``name`` is a raw string of the function name.
1624-
* ``args`` is a :class:`arguments` node.
1624+
* ``args`` is an :class:`arguments` node.
16251625
* ``body`` is the list of nodes inside the function.
16261626
* ``decorator_list`` is the list of decorators to be applied, stored outermost
16271627
first (i.e. the first in the list will be applied last).

Doc/library/http.server.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ provides three different variants:
197197
request header it responds back with a ``100 Continue`` followed by ``200
198198
OK`` headers.
199199
This method can be overridden to raise an error if the server does not
200-
want the client to continue. For e.g. server can chose to send ``417
200+
want the client to continue. For e.g. server can choose to send ``417
201201
Expectation Failed`` as a response header and ``return False``.
202202

203203
.. versionadded:: 3.2

Include/internal/pycore_code.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ typedef struct _stats {
321321
extern SpecializationStats _specialization_stats[256];
322322
#define STAT_INC(opname, name) _specialization_stats[opname].name++
323323
void _Py_PrintSpecializationStats(void);
324+
325+
PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
326+
324327
#else
325328
#define STAT_INC(opname, name) ((void)0)
326329
#endif

Lib/opcode.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,13 @@ def jabs_op(name, op):
234234
"LOAD_GLOBAL_MODULE",
235235
"LOAD_GLOBAL_BUILTIN",
236236
]
237+
238+
_specialization_stats = [
239+
"specialization_success",
240+
"specialization_failure",
241+
"hit",
242+
"deferred",
243+
"miss",
244+
"deopt",
245+
"unquickened",
246+
]

Lib/rlcompleter.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,20 @@ 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-
try:
180-
val = getattr(thisobject, word)
181-
except Exception:
182-
pass # Include even if attribute not set
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
189+
if (value := getattr(thisobject, word, None)) is not None:
190+
matches.append(self._callable_postfix(value, match))
183191
else:
184-
match = self._callable_postfix(val, match)
185-
matches.append(match)
192+
matches.append(match)
186193
if matches or not noprefix:
187194
break
188195
if noprefix == '_':

Lib/test/test__opcode.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import dis
22
from test.support.import_helper import import_module
33
import unittest
4+
import opcode
45

56
_opcode = import_module("_opcode")
67
from _opcode import stack_effect
@@ -64,5 +65,31 @@ def test_stack_effect_jump(self):
6465
self.assertEqual(nojump, common)
6566

6667

68+
class SpecializationStatsTests(unittest.TestCase):
69+
def test_specialization_stats(self):
70+
stat_names = opcode._specialization_stats
71+
72+
specialized_opcodes = [
73+
op[:-len("_ADAPTIVE")].lower() for
74+
op in opcode._specialized_instructions
75+
if op.endswith("_ADAPTIVE")]
76+
self.assertIn('load_attr', specialized_opcodes)
77+
self.assertIn('binary_subscr', specialized_opcodes)
78+
79+
stats = _opcode.get_specialization_stats()
80+
if stats is not None:
81+
self.assertIsInstance(stats, dict)
82+
self.assertCountEqual(stats.keys(), specialized_opcodes)
83+
self.assertCountEqual(
84+
stats['load_attr'].keys(),
85+
stat_names + ['fails'])
86+
for sn in stat_names:
87+
self.assertIsInstance(stats['load_attr'][sn], int)
88+
self.assertIsInstance(stats['load_attr']['fails'], dict)
89+
for k,v in stats['load_attr']['fails'].items():
90+
self.assertIsInstance(k, tuple)
91+
self.assertIsInstance(v, int)
92+
93+
6794
if __name__ == "__main__":
6895
unittest.main()

Lib/test/test__xxsubinterpreters.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,20 @@ def _run_output(interp, request, shared=None):
3939
return rpipe.read()
4040

4141

42+
def _wait_for_interp_to_run(interp, timeout=None):
43+
# bpo-37224: Running this test file in multiprocesses will fail randomly.
44+
# The failure reason is that the thread can't acquire the cpu to
45+
# run subinterpreter eariler than the main thread in multiprocess.
46+
if timeout is None:
47+
timeout = support.SHORT_TIMEOUT
48+
start_time = time.monotonic()
49+
deadline = start_time + timeout
50+
while not interpreters.is_running(interp):
51+
if time.monotonic() > deadline:
52+
raise RuntimeError('interp is not running')
53+
time.sleep(0.010)
54+
55+
4256
@contextlib.contextmanager
4357
def _running(interp):
4458
r, w = os.pipe()
@@ -51,6 +65,7 @@ def run():
5165

5266
t = threading.Thread(target=run)
5367
t.start()
68+
_wait_for_interp_to_run(interp)
5469

5570
yield
5671

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Expose specialization stats in python via :func:`_opcode.get_specialization_stats`.
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)