Skip to content

Commit 8a2a39f

Browse files
committed
pythongh-83076: ~4x speed improvement in (Async)Mock instantiation
1 parent 9663853 commit 8a2a39f

File tree

1 file changed

+15
-14
lines changed

1 file changed

+15
-14
lines changed

Lib/unittest/mock.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -411,15 +411,18 @@ class NonCallableMock(Base):
411411
# necessary.
412412
_lock = RLock()
413413

414-
def __new__(cls, /, *args, **kw):
414+
def __new__(
415+
cls, spec=None, wraps=None, name=None, spec_set=None,
416+
parent=None, _spec_state=None, _new_name='', _new_parent=None,
417+
_spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs
418+
):
415419
# every instance has its own class
416420
# so we can create magic methods on the
417421
# class without stomping on other mocks
418422
bases = (cls,)
419423
if not issubclass(cls, AsyncMockMixin):
420424
# Check if spec is an async object or function
421-
bound_args = _MOCK_SIG.bind_partial(cls, *args, **kw).arguments
422-
spec_arg = bound_args.get('spec_set', bound_args.get('spec'))
425+
spec_arg = spec_set or spec
423426
if spec_arg is not None and _is_async_obj(spec_arg):
424427
bases = (AsyncMockMixin, cls)
425428
new = type(cls.__name__, bases, {'__doc__': cls.__doc__})
@@ -503,11 +506,6 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
503506

504507
_spec_class = None
505508
_spec_signature = None
506-
_spec_asyncs = []
507-
508-
for attr in dir(spec):
509-
if iscoroutinefunction(getattr(spec, attr, None)):
510-
_spec_asyncs.append(attr)
511509

512510
if spec is not None and not _is_list(spec):
513511
if isinstance(spec, type):
@@ -525,7 +523,6 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
525523
__dict__['_spec_set'] = spec_set
526524
__dict__['_spec_signature'] = _spec_signature
527525
__dict__['_mock_methods'] = spec
528-
__dict__['_spec_asyncs'] = _spec_asyncs
529526

530527
def __get_return_value(self):
531528
ret = self._mock_return_value
@@ -1015,7 +1012,8 @@ def _get_child_mock(self, /, **kw):
10151012
For non-callable mocks the callable variant will be used (rather than
10161013
any custom subclass)."""
10171014
_new_name = kw.get("_new_name")
1018-
if _new_name in self.__dict__['_spec_asyncs']:
1015+
_spec_val = getattr(self.__dict__["_spec_class"], _new_name, None)
1016+
if _spec_val is not None and asyncio.iscoroutinefunction(_spec_val):
10191017
return AsyncMock(**kw)
10201018

10211019
if self._mock_sealed:
@@ -1057,9 +1055,6 @@ def _calls_repr(self, prefix="Calls"):
10571055
return f"\n{prefix}: {safe_repr(self.mock_calls)}."
10581056

10591057

1060-
_MOCK_SIG = inspect.signature(NonCallableMock.__init__)
1061-
1062-
10631058
class _AnyComparer(list):
10641059
"""A list which checks if it contains a call which may have an
10651060
argument of ANY, flipping the components of item and self from
@@ -2183,6 +2178,10 @@ def __get__(self, obj, _type=None):
21832178
return self.create_mock()
21842179

21852180

2181+
_CODE_ATTRS = dir(CodeType)
2182+
_CODE_SIG = inspect.signature(partial(CodeType.__init__, None))
2183+
2184+
21862185
class AsyncMockMixin(Base):
21872186
await_count = _delegating_property('await_count')
21882187
await_args = _delegating_property('await_args')
@@ -2200,7 +2199,9 @@ def __init__(self, /, *args, **kwargs):
22002199
self.__dict__['_mock_await_count'] = 0
22012200
self.__dict__['_mock_await_args'] = None
22022201
self.__dict__['_mock_await_args_list'] = _CallList()
2203-
code_mock = NonCallableMock(spec_set=CodeType)
2202+
code_mock = NonCallableMock(spec_set=_CODE_ATTRS)
2203+
code_mock.__dict__["_spec_class"] = CodeType
2204+
code_mock.__dict__["_spec_signature"] = _CODE_SIG
22042205
code_mock.co_flags = inspect.CO_COROUTINE
22052206
self.__dict__['__code__'] = code_mock
22062207
self.__dict__['__name__'] = 'AsyncMock'

0 commit comments

Comments
 (0)