mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Update functools from 3.13.9 (#6205)
* Update `functools.py` from 3.13.9 * mark/unmark tests
This commit is contained in:
91
Lib/functools.py
vendored
91
Lib/functools.py
vendored
@@ -19,8 +19,9 @@ from collections import namedtuple
|
||||
# import types, weakref # Deferred to single_dispatch()
|
||||
from reprlib import recursive_repr
|
||||
from _thread import RLock
|
||||
from types import GenericAlias
|
||||
|
||||
# Avoid importing types, so we can speedup import time
|
||||
GenericAlias = type(list[int])
|
||||
|
||||
################################################################################
|
||||
### update_wrapper() and wraps() decorator
|
||||
@@ -236,14 +237,16 @@ _initial_missing = object()
|
||||
|
||||
def reduce(function, sequence, initial=_initial_missing):
|
||||
"""
|
||||
reduce(function, iterable[, initial]) -> value
|
||||
reduce(function, iterable[, initial], /) -> value
|
||||
|
||||
Apply a function of two arguments cumulatively to the items of a sequence
|
||||
or iterable, from left to right, so as to reduce the iterable to a single
|
||||
value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
|
||||
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
|
||||
of the iterable in the calculation, and serves as a default when the
|
||||
iterable is empty.
|
||||
Apply a function of two arguments cumulatively to the items of an iterable, from left to right.
|
||||
|
||||
This effectively reduces the iterable to a single value. If initial is present,
|
||||
it is placed before the items of the iterable in the calculation, and serves as
|
||||
a default when the iterable is empty.
|
||||
|
||||
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
|
||||
calculates ((((1 + 2) + 3) + 4) + 5).
|
||||
"""
|
||||
|
||||
it = iter(sequence)
|
||||
@@ -284,7 +287,7 @@ class partial:
|
||||
if not callable(func):
|
||||
raise TypeError("the first argument must be callable")
|
||||
|
||||
if hasattr(func, "func"):
|
||||
if isinstance(func, partial):
|
||||
args = func.args + args
|
||||
keywords = {**func.keywords, **keywords}
|
||||
func = func.func
|
||||
@@ -302,13 +305,23 @@ class partial:
|
||||
|
||||
@recursive_repr()
|
||||
def __repr__(self):
|
||||
qualname = type(self).__qualname__
|
||||
cls = type(self)
|
||||
qualname = cls.__qualname__
|
||||
module = cls.__module__
|
||||
args = [repr(self.func)]
|
||||
args.extend(repr(x) for x in self.args)
|
||||
args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
|
||||
if type(self).__module__ == "functools":
|
||||
return f"functools.{qualname}({', '.join(args)})"
|
||||
return f"{qualname}({', '.join(args)})"
|
||||
return f"{module}.{qualname}({', '.join(args)})"
|
||||
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None:
|
||||
return self
|
||||
import warnings
|
||||
warnings.warn('functools.partial will be a method descriptor in '
|
||||
'future Python versions; wrap it in staticmethod() '
|
||||
'if you want to preserve the old behavior',
|
||||
FutureWarning, 2)
|
||||
return self
|
||||
|
||||
def __reduce__(self):
|
||||
return type(self), (self.func,), (self.func, self.args,
|
||||
@@ -338,6 +351,9 @@ class partial:
|
||||
self.args = args
|
||||
self.keywords = kwds
|
||||
|
||||
__class_getitem__ = classmethod(GenericAlias)
|
||||
|
||||
|
||||
try:
|
||||
from _functools import partial
|
||||
except ImportError:
|
||||
@@ -372,28 +388,26 @@ class partialmethod(object):
|
||||
self.keywords = keywords
|
||||
|
||||
def __repr__(self):
|
||||
args = ", ".join(map(repr, self.args))
|
||||
keywords = ", ".join("{}={!r}".format(k, v)
|
||||
for k, v in self.keywords.items())
|
||||
format_string = "{module}.{cls}({func}, {args}, {keywords})"
|
||||
return format_string.format(module=self.__class__.__module__,
|
||||
cls=self.__class__.__qualname__,
|
||||
func=self.func,
|
||||
args=args,
|
||||
keywords=keywords)
|
||||
cls = type(self)
|
||||
module = cls.__module__
|
||||
qualname = cls.__qualname__
|
||||
args = [repr(self.func)]
|
||||
args.extend(map(repr, self.args))
|
||||
args.extend(f"{k}={v!r}" for k, v in self.keywords.items())
|
||||
return f"{module}.{qualname}({', '.join(args)})"
|
||||
|
||||
def _make_unbound_method(self):
|
||||
def _method(cls_or_self, /, *args, **keywords):
|
||||
keywords = {**self.keywords, **keywords}
|
||||
return self.func(cls_or_self, *self.args, *args, **keywords)
|
||||
_method.__isabstractmethod__ = self.__isabstractmethod__
|
||||
_method._partialmethod = self
|
||||
_method.__partialmethod__ = self
|
||||
return _method
|
||||
|
||||
def __get__(self, obj, cls=None):
|
||||
get = getattr(self.func, "__get__", None)
|
||||
result = None
|
||||
if get is not None:
|
||||
if get is not None and not isinstance(self.func, partial):
|
||||
new_func = get(obj, cls)
|
||||
if new_func is not self.func:
|
||||
# Assume __get__ returning something new indicates the
|
||||
@@ -423,6 +437,17 @@ def _unwrap_partial(func):
|
||||
func = func.func
|
||||
return func
|
||||
|
||||
def _unwrap_partialmethod(func):
|
||||
prev = None
|
||||
while func is not prev:
|
||||
prev = func
|
||||
while isinstance(getattr(func, "__partialmethod__", None), partialmethod):
|
||||
func = func.__partialmethod__
|
||||
while isinstance(func, partialmethod):
|
||||
func = getattr(func, 'func')
|
||||
func = _unwrap_partial(func)
|
||||
return func
|
||||
|
||||
################################################################################
|
||||
### LRU Cache function decorator
|
||||
################################################################################
|
||||
@@ -483,8 +508,9 @@ def lru_cache(maxsize=128, typed=False):
|
||||
can grow without bound.
|
||||
|
||||
If *typed* is True, arguments of different types will be cached separately.
|
||||
For example, f(3.0) and f(3) will be treated as distinct calls with
|
||||
distinct results.
|
||||
For example, f(decimal.Decimal("3.0")) and f(3.0) will be treated as
|
||||
distinct calls with distinct results. Some types such as str and int may
|
||||
be cached separately even when typed is false.
|
||||
|
||||
Arguments to the cached function must be hashable.
|
||||
|
||||
@@ -660,7 +686,7 @@ def cache(user_function, /):
|
||||
def _c3_merge(sequences):
|
||||
"""Merges MROs in *sequences* to a single MRO using the C3 algorithm.
|
||||
|
||||
Adapted from https://www.python.org/download/releases/2.3/mro/.
|
||||
Adapted from https://docs.python.org/3/howto/mro.html.
|
||||
|
||||
"""
|
||||
result = []
|
||||
@@ -905,7 +931,6 @@ def singledispatch(func):
|
||||
if not args:
|
||||
raise TypeError(f'{funcname} requires at least '
|
||||
'1 positional argument')
|
||||
|
||||
return dispatch(args[0].__class__)(*args, **kw)
|
||||
|
||||
funcname = getattr(func, '__name__', 'singledispatch function')
|
||||
@@ -941,13 +966,18 @@ class singledispatchmethod:
|
||||
return self.dispatcher.register(cls, func=method)
|
||||
|
||||
def __get__(self, obj, cls=None):
|
||||
dispatch = self.dispatcher.dispatch
|
||||
funcname = getattr(self.func, '__name__', 'singledispatchmethod method')
|
||||
def _method(*args, **kwargs):
|
||||
method = self.dispatcher.dispatch(args[0].__class__)
|
||||
return method.__get__(obj, cls)(*args, **kwargs)
|
||||
if not args:
|
||||
raise TypeError(f'{funcname} requires at least '
|
||||
'1 positional argument')
|
||||
return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs)
|
||||
|
||||
_method.__isabstractmethod__ = self.__isabstractmethod__
|
||||
_method.register = self.register
|
||||
update_wrapper(_method, self.func)
|
||||
|
||||
return _method
|
||||
|
||||
@property
|
||||
@@ -966,6 +996,7 @@ class cached_property:
|
||||
self.func = func
|
||||
self.attrname = None
|
||||
self.__doc__ = func.__doc__
|
||||
self.__module__ = func.__module__
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
if self.attrname is None:
|
||||
|
||||
435
Lib/test/test_functools.py
vendored
435
Lib/test/test_functools.py
vendored
@@ -31,10 +31,6 @@ c_functools = import_helper.import_fresh_module('functools',
|
||||
|
||||
decimal = import_helper.import_fresh_module('decimal', fresh=['_decimal'])
|
||||
|
||||
_partial_types = [py_functools.partial]
|
||||
if c_functools:
|
||||
_partial_types.append(c_functools.partial)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def replaced_module(name, replacement):
|
||||
@@ -189,6 +185,19 @@ class TestPartial:
|
||||
flat = partial(signature, 'asdf', bar=True)
|
||||
self.assertEqual(signature(nested), signature(flat))
|
||||
|
||||
def test_nested_optimization_bug(self):
|
||||
partial = self.partial
|
||||
class Builder:
|
||||
def __call__(self, tag, *children, **attrib):
|
||||
return (tag, children, attrib)
|
||||
|
||||
def __getattr__(self, tag):
|
||||
return partial(self, tag)
|
||||
|
||||
B = Builder()
|
||||
m = B.m
|
||||
assert m(1, 2, a=2) == ('m', (1, 2), dict(a=2))
|
||||
|
||||
def test_nested_partial_with_attribute(self):
|
||||
# see issue 25137
|
||||
partial = self.partial
|
||||
@@ -207,10 +216,7 @@ class TestPartial:
|
||||
kwargs = {'a': object(), 'b': object()}
|
||||
kwargs_reprs = ['a={a!r}, b={b!r}'.format_map(kwargs),
|
||||
'b={b!r}, a={a!r}'.format_map(kwargs)]
|
||||
if self.partial in _partial_types:
|
||||
name = 'functools.partial'
|
||||
else:
|
||||
name = self.partial.__name__
|
||||
name = f"{self.partial.__module__}.{self.partial.__qualname__}"
|
||||
|
||||
f = self.partial(capture)
|
||||
self.assertEqual(f'{name}({capture!r})', repr(f))
|
||||
@@ -229,10 +235,7 @@ class TestPartial:
|
||||
for kwargs_repr in kwargs_reprs])
|
||||
|
||||
def test_recursive_repr(self):
|
||||
if self.partial in _partial_types:
|
||||
name = 'functools.partial'
|
||||
else:
|
||||
name = self.partial.__name__
|
||||
name = f"{self.partial.__module__}.{self.partial.__qualname__}"
|
||||
|
||||
f = self.partial(capture)
|
||||
f.__setstate__((f, (), {}, {}))
|
||||
@@ -344,8 +347,10 @@ class TestPartial:
|
||||
f.__setstate__((f, (), {}, {}))
|
||||
try:
|
||||
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||
with self.assertRaises(RecursionError):
|
||||
pickle.dumps(f, proto)
|
||||
# gh-117008: Small limit since pickle uses C stack memory
|
||||
with support.infinite_recursion(100):
|
||||
with self.assertRaises(RecursionError):
|
||||
pickle.dumps(f, proto)
|
||||
finally:
|
||||
f.__setstate__((capture, (), {}, {}))
|
||||
|
||||
@@ -390,6 +395,30 @@ class TestPartial:
|
||||
f = self.partial(object)
|
||||
self.assertRaises(TypeError, f.__setstate__, BadSequence())
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_partial_as_method(self):
|
||||
class A:
|
||||
meth = self.partial(capture, 1, a=2)
|
||||
cmeth = classmethod(self.partial(capture, 1, a=2))
|
||||
smeth = staticmethod(self.partial(capture, 1, a=2))
|
||||
|
||||
a = A()
|
||||
self.assertEqual(A.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
|
||||
self.assertEqual(A.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4}))
|
||||
self.assertEqual(A.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
|
||||
with self.assertWarns(FutureWarning) as w:
|
||||
self.assertEqual(a.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
|
||||
self.assertEqual(w.filename, __file__)
|
||||
self.assertEqual(a.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4}))
|
||||
self.assertEqual(a.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4}))
|
||||
|
||||
def test_partial_genericalias(self):
|
||||
alias = self.partial[int]
|
||||
self.assertIs(alias.__origin__, self.partial)
|
||||
self.assertEqual(alias.__args__, (int,))
|
||||
self.assertEqual(alias.__parameters__, ())
|
||||
|
||||
|
||||
@unittest.skipUnless(c_functools, 'requires the C _functools module')
|
||||
class TestPartialC(TestPartial, unittest.TestCase):
|
||||
if c_functools:
|
||||
@@ -436,6 +465,14 @@ class TestPartialC(TestPartial, unittest.TestCase):
|
||||
self.assertIn('astr', r)
|
||||
self.assertIn("['sth']", r)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_repr(self):
|
||||
return super().test_repr()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_recursive_repr(self):
|
||||
return super().test_recursive_repr()
|
||||
|
||||
|
||||
class TestPartialPy(TestPartial, unittest.TestCase):
|
||||
module = py_functools
|
||||
@@ -454,14 +491,6 @@ class TestPartialCSubclass(TestPartialC):
|
||||
if c_functools:
|
||||
partial = CPartialSubclass
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
def test_pickle(self):
|
||||
TestPartial.test_pickle(self)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
def test_recursive_pickle(self):
|
||||
TestPartial.test_recursive_pickle(self)
|
||||
|
||||
# partial subclasses are not optimized for nested calls
|
||||
test_nested_optimization = None
|
||||
|
||||
@@ -572,6 +601,14 @@ class TestPartialMethod(unittest.TestCase):
|
||||
method = functools.partialmethod(func=capture, a=1)
|
||||
|
||||
def test_repr(self):
|
||||
self.assertEqual(repr(vars(self.A)['nothing']),
|
||||
'functools.partialmethod({})'.format(capture))
|
||||
self.assertEqual(repr(vars(self.A)['positional']),
|
||||
'functools.partialmethod({}, 1)'.format(capture))
|
||||
self.assertEqual(repr(vars(self.A)['keywords']),
|
||||
'functools.partialmethod({}, a=2)'.format(capture))
|
||||
self.assertEqual(repr(vars(self.A)['spec_keywords']),
|
||||
'functools.partialmethod({}, self=1, func=2)'.format(capture))
|
||||
self.assertEqual(repr(vars(self.A)['both']),
|
||||
'functools.partialmethod({}, 3, b=4)'.format(capture))
|
||||
|
||||
@@ -620,9 +657,7 @@ class TestUpdateWrapper(unittest.TestCase):
|
||||
|
||||
|
||||
def _default_update(self):
|
||||
# XXX: RUSTPYTHON; f[T] is not supported yet
|
||||
# def f[T](a:'This is a new annotation'):
|
||||
def f(a:'This is a new annotation'):
|
||||
def f[T](a:'This is a new annotation'):
|
||||
"""This is a test"""
|
||||
pass
|
||||
f.attr = 'This is also a test'
|
||||
@@ -632,8 +667,6 @@ class TestUpdateWrapper(unittest.TestCase):
|
||||
functools.update_wrapper(wrapper, f)
|
||||
return wrapper, f
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_default_update(self):
|
||||
wrapper, f = self._default_update()
|
||||
self.check_wrapper(wrapper, f)
|
||||
@@ -717,6 +750,14 @@ class TestUpdateWrapper(unittest.TestCase):
|
||||
self.assertTrue(wrapper.__doc__.startswith('max('))
|
||||
self.assertEqual(wrapper.__annotations__, {})
|
||||
|
||||
def test_update_type_wrapper(self):
|
||||
def wrapper(*args): pass
|
||||
|
||||
functools.update_wrapper(wrapper, type)
|
||||
self.assertEqual(wrapper.__name__, 'type')
|
||||
self.assertEqual(wrapper.__annotations__, {})
|
||||
self.assertEqual(wrapper.__type_params__, ())
|
||||
|
||||
|
||||
class TestWraps(TestUpdateWrapper):
|
||||
|
||||
@@ -951,9 +992,16 @@ class TestCmpToKey:
|
||||
self.assertRaises(TypeError, hash, k)
|
||||
self.assertNotIsInstance(k, collections.abc.Hashable)
|
||||
|
||||
@unittest.skipIf(support.MISSING_C_DOCSTRINGS,
|
||||
"Signature information for builtins requires docstrings")
|
||||
def test_cmp_to_signature(self):
|
||||
self.assertEqual(str(Signature.from_callable(self.cmp_to_key)),
|
||||
'(mycmp)')
|
||||
sig = Signature.from_callable(self.cmp_to_key)
|
||||
self.assertEqual(str(sig), '(mycmp)')
|
||||
def mycmp(x, y):
|
||||
return y - x
|
||||
sig = Signature.from_callable(self.cmp_to_key(mycmp))
|
||||
self.assertEqual(str(sig), '(obj)')
|
||||
|
||||
|
||||
|
||||
@unittest.skipUnless(c_functools, 'requires the C _functools module')
|
||||
@@ -961,46 +1009,6 @@ class TestCmpToKeyC(TestCmpToKey, unittest.TestCase):
|
||||
if c_functools:
|
||||
cmp_to_key = c_functools.cmp_to_key
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_bad_cmp(self):
|
||||
super().test_bad_cmp()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_cmp_to_key(self):
|
||||
super().test_cmp_to_key()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_cmp_to_key_arguments(self):
|
||||
super().test_cmp_to_key_arguments()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_hash(self):
|
||||
super().test_hash()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_obj_field(self):
|
||||
super().test_obj_field()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_sort_int(self):
|
||||
super().test_sort_int()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_sort_int_str(self):
|
||||
super().test_sort_int_str()
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_cmp_to_signature(self):
|
||||
super().test_cmp_to_signature()
|
||||
|
||||
@support.cpython_only
|
||||
def test_disallow_instantiation(self):
|
||||
# Ensure that the type disallows instantiation (bpo-43916)
|
||||
@@ -1008,6 +1016,38 @@ class TestCmpToKeyC(TestCmpToKey, unittest.TestCase):
|
||||
self, type(c_functools.cmp_to_key(None))
|
||||
)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_bad_cmp(self):
|
||||
return super().test_bad_cmp()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_cmp_to_key(self):
|
||||
return super().test_cmp_to_key()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_cmp_to_key_arguments(self):
|
||||
return super().test_cmp_to_key_arguments()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_cmp_to_signature(self):
|
||||
return super().test_cmp_to_signature()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_hash(self):
|
||||
return super().test_hash()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_obj_field(self):
|
||||
return super().test_obj_field()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_sort_int(self):
|
||||
return super().test_sort_int()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_sort_int_str(self):
|
||||
return super().test_sort_int_str()
|
||||
|
||||
|
||||
class TestCmpToKeyPy(TestCmpToKey, unittest.TestCase):
|
||||
cmp_to_key = staticmethod(py_functools.cmp_to_key)
|
||||
@@ -1340,6 +1380,16 @@ class TestCache:
|
||||
self.module._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))
|
||||
|
||||
|
||||
class TestCachePy(TestCache, unittest.TestCase):
|
||||
module = py_functools
|
||||
|
||||
|
||||
@unittest.skipUnless(c_functools, 'requires the C _functools module')
|
||||
class TestCacheC(TestCache, unittest.TestCase):
|
||||
if c_functools:
|
||||
module = c_functools
|
||||
|
||||
|
||||
class TestLRU:
|
||||
|
||||
def test_lru(self):
|
||||
@@ -1773,8 +1823,7 @@ class TestLRU:
|
||||
time.sleep(.01)
|
||||
return 3 * x
|
||||
def test(i, x):
|
||||
with self.subTest(thread=i):
|
||||
self.assertEqual(f(x), 3 * x, i)
|
||||
self.assertEqual(f(x), 3 * x, i)
|
||||
threads = [threading.Thread(target=test, args=(i, v))
|
||||
for i, v in enumerate([1, 2, 2, 3, 2])]
|
||||
with threading_helper.start_threads(threads):
|
||||
@@ -1878,6 +1927,7 @@ class TestLRU:
|
||||
return 1
|
||||
self.assertEqual(f.cache_parameters(), {'maxsize': 1000, "typed": True})
|
||||
|
||||
@support.suppress_immortalization()
|
||||
def test_lru_cache_weakrefable(self):
|
||||
@self.module.lru_cache
|
||||
def test_function(x):
|
||||
@@ -1908,12 +1958,33 @@ class TestLRU:
|
||||
self.assertIsNone(ref())
|
||||
|
||||
def test_common_signatures(self):
|
||||
def orig(): ...
|
||||
def orig(a, /, b, c=True): ...
|
||||
lru = self.module.lru_cache(1)(orig)
|
||||
|
||||
self.assertEqual(str(Signature.from_callable(lru)), '(a, /, b, c=True)')
|
||||
self.assertEqual(str(Signature.from_callable(lru.cache_info)), '()')
|
||||
self.assertEqual(str(Signature.from_callable(lru.cache_clear)), '()')
|
||||
|
||||
@support.skip_on_s390x
|
||||
@unittest.skipIf(support.is_wasi, "WASI has limited C stack")
|
||||
def test_lru_recursion(self):
|
||||
|
||||
@self.module.lru_cache
|
||||
def fib(n):
|
||||
if n <= 1:
|
||||
return n
|
||||
return fib(n-1) + fib(n-2)
|
||||
|
||||
if not support.Py_DEBUG:
|
||||
depth = support.get_c_recursion_limit()*2//7
|
||||
with support.infinite_recursion():
|
||||
fib(depth)
|
||||
if self.module == c_functools:
|
||||
fib.cache_clear()
|
||||
with support.infinite_recursion():
|
||||
with self.assertRaises(RecursionError):
|
||||
fib(10000)
|
||||
|
||||
|
||||
@py_functools.lru_cache()
|
||||
def py_cached_func(x, y):
|
||||
@@ -2524,6 +2595,74 @@ class TestSingleDispatch(unittest.TestCase):
|
||||
self.assertTrue(A.t(''))
|
||||
self.assertEqual(A.t(0.0), 0.0)
|
||||
|
||||
def test_slotted_class(self):
|
||||
class Slot:
|
||||
__slots__ = ('a', 'b')
|
||||
@functools.singledispatchmethod
|
||||
def go(self, item, arg):
|
||||
pass
|
||||
|
||||
@go.register
|
||||
def _(self, item: int, arg):
|
||||
return item + arg
|
||||
|
||||
s = Slot()
|
||||
self.assertEqual(s.go(1, 1), 2)
|
||||
|
||||
def test_classmethod_slotted_class(self):
|
||||
class Slot:
|
||||
__slots__ = ('a', 'b')
|
||||
@functools.singledispatchmethod
|
||||
@classmethod
|
||||
def go(cls, item, arg):
|
||||
pass
|
||||
|
||||
@go.register
|
||||
@classmethod
|
||||
def _(cls, item: int, arg):
|
||||
return item + arg
|
||||
|
||||
s = Slot()
|
||||
self.assertEqual(s.go(1, 1), 2)
|
||||
self.assertEqual(Slot.go(1, 1), 2)
|
||||
|
||||
def test_staticmethod_slotted_class(self):
|
||||
class A:
|
||||
__slots__ = ['a']
|
||||
@functools.singledispatchmethod
|
||||
@staticmethod
|
||||
def t(arg):
|
||||
return arg
|
||||
@t.register(int)
|
||||
@staticmethod
|
||||
def _(arg):
|
||||
return isinstance(arg, int)
|
||||
@t.register(str)
|
||||
@staticmethod
|
||||
def _(arg):
|
||||
return isinstance(arg, str)
|
||||
a = A()
|
||||
|
||||
self.assertTrue(A.t(0))
|
||||
self.assertTrue(A.t(''))
|
||||
self.assertEqual(A.t(0.0), 0.0)
|
||||
self.assertTrue(a.t(0))
|
||||
self.assertTrue(a.t(''))
|
||||
self.assertEqual(a.t(0.0), 0.0)
|
||||
|
||||
def test_assignment_behavior(self):
|
||||
# see gh-106448
|
||||
class A:
|
||||
@functools.singledispatchmethod
|
||||
def t(arg):
|
||||
return arg
|
||||
|
||||
a = A()
|
||||
a.t.foo = 'bar'
|
||||
a2 = A()
|
||||
with self.assertRaises(AttributeError):
|
||||
a2.t.foo
|
||||
|
||||
def test_classmethod_register(self):
|
||||
class A:
|
||||
def __init__(self, arg):
|
||||
@@ -2659,6 +2798,7 @@ class TestSingleDispatch(unittest.TestCase):
|
||||
"""My function docstring"""
|
||||
return str(arg)
|
||||
|
||||
prefix = A.__qualname__ + '.'
|
||||
for meth in (
|
||||
A.func,
|
||||
A().func,
|
||||
@@ -2668,7 +2808,11 @@ class TestSingleDispatch(unittest.TestCase):
|
||||
A().static_func
|
||||
):
|
||||
with self.subTest(meth=meth):
|
||||
self.assertEqual(meth.__doc__, 'My function docstring')
|
||||
self.assertEqual(meth.__qualname__, prefix + meth.__name__)
|
||||
self.assertEqual(meth.__doc__,
|
||||
('My function docstring'
|
||||
if support.HAVE_PY_DOCSTRINGS
|
||||
else None))
|
||||
self.assertEqual(meth.__annotations__['arg'], int)
|
||||
|
||||
self.assertEqual(A.func.__name__, 'func')
|
||||
@@ -2757,7 +2901,10 @@ class TestSingleDispatch(unittest.TestCase):
|
||||
WithSingleDispatch().decorated_classmethod
|
||||
):
|
||||
with self.subTest(meth=meth):
|
||||
self.assertEqual(meth.__doc__, 'My function docstring')
|
||||
self.assertEqual(meth.__doc__,
|
||||
('My function docstring'
|
||||
if support.HAVE_PY_DOCSTRINGS
|
||||
else None))
|
||||
self.assertEqual(meth.__annotations__['arg'], int)
|
||||
|
||||
self.assertEqual(
|
||||
@@ -2829,11 +2976,26 @@ class TestSingleDispatch(unittest.TestCase):
|
||||
|
||||
def test_invalid_positional_argument(self):
|
||||
@functools.singledispatch
|
||||
def f(*args):
|
||||
def f(*args, **kwargs):
|
||||
pass
|
||||
msg = 'f requires at least 1 positional argument'
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
f()
|
||||
msg = 'f requires at least 1 positional argument'
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
f(a=1)
|
||||
|
||||
def test_invalid_positional_argument_singledispatchmethod(self):
|
||||
class A:
|
||||
@functools.singledispatchmethod
|
||||
def t(self, *args, **kwargs):
|
||||
pass
|
||||
msg = 't requires at least 1 positional argument'
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
A().t()
|
||||
msg = 't requires at least 1 positional argument'
|
||||
with self.assertRaisesRegex(TypeError, msg):
|
||||
A().t(a=1)
|
||||
|
||||
def test_union(self):
|
||||
@functools.singledispatch
|
||||
@@ -2957,6 +3119,115 @@ class TestSingleDispatch(unittest.TestCase):
|
||||
self.assertEqual(f(""), "default")
|
||||
self.assertEqual(f(b""), "default")
|
||||
|
||||
def test_method_equal_instances(self):
|
||||
# gh-127750: Reference to self was cached
|
||||
class A:
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
def __hash__(self):
|
||||
return 1
|
||||
@functools.singledispatchmethod
|
||||
def t(self, arg):
|
||||
return self
|
||||
|
||||
a = A()
|
||||
b = A()
|
||||
self.assertIs(a.t(1), a)
|
||||
self.assertIs(b.t(2), b)
|
||||
|
||||
def test_method_bad_hash(self):
|
||||
class A:
|
||||
def __eq__(self, other):
|
||||
raise AssertionError
|
||||
def __hash__(self):
|
||||
raise AssertionError
|
||||
@functools.singledispatchmethod
|
||||
def t(self, arg):
|
||||
pass
|
||||
|
||||
# Should not raise
|
||||
A().t(1)
|
||||
hash(A().t)
|
||||
A().t == A().t
|
||||
|
||||
def test_method_no_reference_loops(self):
|
||||
# gh-127750: Created a strong reference to self
|
||||
class A:
|
||||
@functools.singledispatchmethod
|
||||
def t(self, arg):
|
||||
return weakref.ref(self)
|
||||
|
||||
a = A()
|
||||
r = a.t(1)
|
||||
self.assertIsNotNone(r())
|
||||
del a # delete a after a.t
|
||||
if not support.check_impl_detail(cpython=True):
|
||||
support.gc_collect()
|
||||
self.assertIsNone(r())
|
||||
|
||||
a = A()
|
||||
t = a.t
|
||||
del a # delete a before a.t
|
||||
support.gc_collect()
|
||||
r = t(1)
|
||||
self.assertIsNotNone(r())
|
||||
del t
|
||||
if not support.check_impl_detail(cpython=True):
|
||||
support.gc_collect()
|
||||
self.assertIsNone(r())
|
||||
|
||||
def test_signatures(self):
|
||||
@functools.singledispatch
|
||||
def func(item, arg: int) -> str:
|
||||
return str(item)
|
||||
@func.register
|
||||
def _(item: int, arg: bytes) -> str:
|
||||
return str(item)
|
||||
|
||||
self.assertEqual(str(Signature.from_callable(func)),
|
||||
'(item, arg: int) -> str')
|
||||
|
||||
def test_method_signatures(self):
|
||||
class A:
|
||||
def m(self, item, arg: int) -> str:
|
||||
return str(item)
|
||||
@classmethod
|
||||
def cm(cls, item, arg: int) -> str:
|
||||
return str(item)
|
||||
@functools.singledispatchmethod
|
||||
def func(self, item, arg: int) -> str:
|
||||
return str(item)
|
||||
@func.register
|
||||
def _(self, item, arg: bytes) -> str:
|
||||
return str(item)
|
||||
|
||||
@functools.singledispatchmethod
|
||||
@classmethod
|
||||
def cls_func(cls, item, arg: int) -> str:
|
||||
return str(arg)
|
||||
@func.register
|
||||
@classmethod
|
||||
def _(cls, item, arg: bytes) -> str:
|
||||
return str(item)
|
||||
|
||||
@functools.singledispatchmethod
|
||||
@staticmethod
|
||||
def static_func(item, arg: int) -> str:
|
||||
return str(arg)
|
||||
@func.register
|
||||
@staticmethod
|
||||
def _(item, arg: bytes) -> str:
|
||||
return str(item)
|
||||
|
||||
self.assertEqual(str(Signature.from_callable(A.func)),
|
||||
'(self, item, arg: int) -> str')
|
||||
self.assertEqual(str(Signature.from_callable(A().func)),
|
||||
'(self, item, arg: int) -> str')
|
||||
self.assertEqual(str(Signature.from_callable(A.cls_func)),
|
||||
'(cls, item, arg: int) -> str')
|
||||
self.assertEqual(str(Signature.from_callable(A.static_func)),
|
||||
'(item, arg: int) -> str')
|
||||
|
||||
|
||||
class CachedCostItem:
|
||||
_cost = 1
|
||||
@@ -3007,8 +3278,7 @@ class TestCachedProperty(unittest.TestCase):
|
||||
self.assertEqual(item.get_cost(), 4)
|
||||
self.assertEqual(item.cached_cost, 3)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_object_with_slots(self):
|
||||
item = CachedCostItemWithSlots()
|
||||
with self.assertRaisesRegex(
|
||||
@@ -3032,8 +3302,7 @@ class TestCachedProperty(unittest.TestCase):
|
||||
):
|
||||
MyClass.prop
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_reuse_different_names(self):
|
||||
"""Disallow this case because decorated function a would not be cached."""
|
||||
with self.assertRaises(TypeError) as ctx:
|
||||
@@ -3089,7 +3358,13 @@ class TestCachedProperty(unittest.TestCase):
|
||||
self.assertIsInstance(CachedCostItem.cost, py_functools.cached_property)
|
||||
|
||||
def test_doc(self):
|
||||
self.assertEqual(CachedCostItem.cost.__doc__, "The cost of the item.")
|
||||
self.assertEqual(CachedCostItem.cost.__doc__,
|
||||
("The cost of the item."
|
||||
if support.HAVE_PY_DOCSTRINGS
|
||||
else None))
|
||||
|
||||
def test_module(self):
|
||||
self.assertEqual(CachedCostItem.cost.__module__, CachedCostItem.__module__)
|
||||
|
||||
def test_subclass_with___set__(self):
|
||||
"""Caching still works for a subclass defining __set__."""
|
||||
|
||||
Reference in New Issue
Block a user