mirror of
https://github.com/python/cpython
synced 2026-11-06 09:02:40 +03:00
Setting __name__, __qualname__, __doc__ or __module__ on a function
stopped the world in the free-threaded build since gh-154826 and
gh-154851, making functools.wraps() orders of magnitude slower with
multiple threads.
Use the per-object critical section and _PyObject_XSetRefDelayed()
instead, as gen_set_name() already does. Use acquire loads in the
readers and hold strong references across error reporting, tracing, and
eval-frame recording that may execute Python or detach the thread. The
__code__, __defaults__, __kwdefaults__ and __closure__ setters still
stop the world to clear func_version.
Document synchronization requirements for direct field access and the
lifetime of borrowed function attributes.
(cherry picked from commit 010bebbf9f)
346 lines
12 KiB
Python
346 lines
12 KiB
Python
import unittest
|
|
from test.support import import_helper
|
|
|
|
|
|
_testcapi = import_helper.import_module('_testcapi')
|
|
|
|
|
|
class FunctionTest(unittest.TestCase):
|
|
def test_function_get_code(self):
|
|
# Test PyFunction_GetCode()
|
|
import types
|
|
|
|
def some():
|
|
pass
|
|
|
|
code = _testcapi.function_get_code(some)
|
|
self.assertIsInstance(code, types.CodeType)
|
|
self.assertEqual(code, some.__code__)
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_get_code(None) # not a function
|
|
|
|
def test_function_get_globals(self):
|
|
# Test PyFunction_GetGlobals()
|
|
def some():
|
|
pass
|
|
|
|
globals_ = _testcapi.function_get_globals(some)
|
|
self.assertIsInstance(globals_, dict)
|
|
self.assertEqual(globals_, some.__globals__)
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_get_globals(None) # not a function
|
|
|
|
def test_function_get_module(self):
|
|
# Test PyFunction_GetModule()
|
|
def some():
|
|
pass
|
|
|
|
module = _testcapi.function_get_module(some)
|
|
self.assertIsInstance(module, str)
|
|
self.assertEqual(module, some.__module__)
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_get_module(None) # not a function
|
|
|
|
self.assertEqual(_testcapi.function_get_module_macro(some), module)
|
|
value = object()
|
|
some.__module__ = value
|
|
self.assertIs(_testcapi.function_get_module(some), value)
|
|
self.assertIs(_testcapi.function_get_module_macro(some), value)
|
|
|
|
def test_function_get_defaults(self):
|
|
# Test PyFunction_GetDefaults()
|
|
def some(
|
|
pos_only1, pos_only2='p',
|
|
/,
|
|
zero=0, optional=None,
|
|
*,
|
|
kw1,
|
|
kw2=True,
|
|
):
|
|
pass
|
|
|
|
defaults = _testcapi.function_get_defaults(some)
|
|
self.assertEqual(defaults, ('p', 0, None))
|
|
self.assertEqual(defaults, some.__defaults__)
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_get_defaults(None) # not a function
|
|
|
|
def test_function_set_defaults(self):
|
|
# Test PyFunction_SetDefaults()
|
|
def some(
|
|
pos_only1, pos_only2='p',
|
|
/,
|
|
zero=0, optional=None,
|
|
*,
|
|
kw1,
|
|
kw2=True,
|
|
):
|
|
pass
|
|
|
|
old_defaults = ('p', 0, None)
|
|
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
|
|
self.assertEqual(some.__defaults__, old_defaults)
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_set_defaults(some, 1) # not tuple or None
|
|
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
|
|
self.assertEqual(some.__defaults__, old_defaults)
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_set_defaults(1, ()) # not a function
|
|
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
|
|
self.assertEqual(some.__defaults__, old_defaults)
|
|
|
|
new_defaults = ('q', 1, None)
|
|
_testcapi.function_set_defaults(some, new_defaults)
|
|
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
|
|
self.assertEqual(some.__defaults__, new_defaults)
|
|
|
|
# Empty tuple is fine:
|
|
new_defaults = ()
|
|
_testcapi.function_set_defaults(some, new_defaults)
|
|
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
|
|
self.assertEqual(some.__defaults__, new_defaults)
|
|
|
|
class tuplesub(tuple): ... # tuple subclasses must work
|
|
|
|
new_defaults = tuplesub(((1, 2), ['a', 'b'], None))
|
|
_testcapi.function_set_defaults(some, new_defaults)
|
|
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
|
|
self.assertEqual(some.__defaults__, new_defaults)
|
|
|
|
# `None` is special, it sets `defaults` to `NULL`,
|
|
# it needs special handling in `_testcapi`:
|
|
_testcapi.function_set_defaults(some, None)
|
|
self.assertEqual(_testcapi.function_get_defaults(some), None)
|
|
self.assertEqual(some.__defaults__, None)
|
|
|
|
def test_function_get_kw_defaults(self):
|
|
# Test PyFunction_GetKwDefaults()
|
|
def some(
|
|
pos_only1, pos_only2='p',
|
|
/,
|
|
zero=0, optional=None,
|
|
*,
|
|
kw1,
|
|
kw2=True,
|
|
):
|
|
pass
|
|
|
|
defaults = _testcapi.function_get_kw_defaults(some)
|
|
self.assertEqual(defaults, {'kw2': True})
|
|
self.assertEqual(defaults, some.__kwdefaults__)
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_get_kw_defaults(None) # not a function
|
|
|
|
def test_function_set_kw_defaults(self):
|
|
# Test PyFunction_SetKwDefaults()
|
|
def some(
|
|
pos_only1, pos_only2='p',
|
|
/,
|
|
zero=0, optional=None,
|
|
*,
|
|
kw1,
|
|
kw2=True,
|
|
):
|
|
pass
|
|
|
|
old_defaults = {'kw2': True}
|
|
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
|
|
self.assertEqual(some.__kwdefaults__, old_defaults)
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_set_kw_defaults(some, 1) # not dict or None
|
|
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
|
|
self.assertEqual(some.__kwdefaults__, old_defaults)
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_set_kw_defaults(1, {}) # not a function
|
|
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
|
|
self.assertEqual(some.__kwdefaults__, old_defaults)
|
|
|
|
new_defaults = {'kw2': (1, 2, 3)}
|
|
_testcapi.function_set_kw_defaults(some, new_defaults)
|
|
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
|
|
self.assertEqual(some.__kwdefaults__, new_defaults)
|
|
|
|
# Empty dict is fine:
|
|
new_defaults = {}
|
|
_testcapi.function_set_kw_defaults(some, new_defaults)
|
|
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
|
|
self.assertEqual(some.__kwdefaults__, new_defaults)
|
|
|
|
class dictsub(dict): ... # dict subclasses must work
|
|
|
|
new_defaults = dictsub({'kw2': None})
|
|
_testcapi.function_set_kw_defaults(some, new_defaults)
|
|
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
|
|
self.assertEqual(some.__kwdefaults__, new_defaults)
|
|
|
|
# `None` is special, it sets `kwdefaults` to `NULL`,
|
|
# it needs special handling in `_testcapi`:
|
|
_testcapi.function_set_kw_defaults(some, None)
|
|
self.assertEqual(_testcapi.function_get_kw_defaults(some), None)
|
|
self.assertEqual(some.__kwdefaults__, None)
|
|
|
|
def test_function_get_closure(self):
|
|
# Test PyFunction_GetClosure()
|
|
from types import CellType
|
|
|
|
def regular_function(): ...
|
|
def unused_one_level(arg1):
|
|
def inner(arg2, arg3): ...
|
|
return inner
|
|
def unused_two_levels(arg1, arg2):
|
|
def decorator(arg3, arg4):
|
|
def inner(arg5, arg6): ...
|
|
return inner
|
|
return decorator
|
|
def with_one_level(arg1):
|
|
def inner(arg2, arg3):
|
|
return arg1 + arg2 + arg3
|
|
return inner
|
|
def with_two_levels(arg1, arg2):
|
|
def decorator(arg3, arg4):
|
|
def inner(arg5, arg6):
|
|
return arg1 + arg2 + arg3 + arg4 + arg5 + arg6
|
|
return inner
|
|
return decorator
|
|
|
|
# Functions without closures:
|
|
self.assertIsNone(_testcapi.function_get_closure(regular_function))
|
|
self.assertIsNone(regular_function.__closure__)
|
|
|
|
func = unused_one_level(1)
|
|
closure = _testcapi.function_get_closure(func)
|
|
self.assertIsNone(closure)
|
|
self.assertIsNone(func.__closure__)
|
|
|
|
func = unused_two_levels(1, 2)(3, 4)
|
|
closure = _testcapi.function_get_closure(func)
|
|
self.assertIsNone(closure)
|
|
self.assertIsNone(func.__closure__)
|
|
|
|
# Functions with closures:
|
|
func = with_one_level(5)
|
|
closure = _testcapi.function_get_closure(func)
|
|
self.assertEqual(closure, func.__closure__)
|
|
self.assertIsInstance(closure, tuple)
|
|
self.assertEqual(len(closure), 1)
|
|
self.assertEqual(len(closure), len(func.__code__.co_freevars))
|
|
for cell in closure:
|
|
self.assertIsInstance(cell, CellType)
|
|
self.assertTrue(closure[0].cell_contents, 5)
|
|
|
|
func = with_two_levels(1, 2)(3, 4)
|
|
closure = _testcapi.function_get_closure(func)
|
|
self.assertEqual(closure, func.__closure__)
|
|
self.assertIsInstance(closure, tuple)
|
|
self.assertEqual(len(closure), 4)
|
|
self.assertEqual(len(closure), len(func.__code__.co_freevars))
|
|
for cell in closure:
|
|
self.assertIsInstance(cell, CellType)
|
|
self.assertEqual([cell.cell_contents for cell in closure],
|
|
[1, 2, 3, 4])
|
|
|
|
def test_function_get_closure_error(self):
|
|
# Test PyFunction_GetClosure()
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_get_closure(1)
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_get_closure(None)
|
|
|
|
def test_function_set_closure(self):
|
|
# Test PyFunction_SetClosure()
|
|
from types import CellType
|
|
|
|
def function_without_closure(): ...
|
|
def function_with_closure(arg):
|
|
def inner():
|
|
return arg
|
|
return inner
|
|
|
|
func = function_without_closure
|
|
_testcapi.function_set_closure(func, (CellType(1), CellType(1)))
|
|
closure = _testcapi.function_get_closure(func)
|
|
self.assertEqual([c.cell_contents for c in closure], [1, 1])
|
|
self.assertEqual([c.cell_contents for c in func.__closure__], [1, 1])
|
|
|
|
func = function_with_closure(1)
|
|
_testcapi.function_set_closure(func,
|
|
(CellType(1), CellType(2), CellType(3)))
|
|
closure = _testcapi.function_get_closure(func)
|
|
self.assertEqual([c.cell_contents for c in closure], [1, 2, 3])
|
|
self.assertEqual([c.cell_contents for c in func.__closure__], [1, 2, 3])
|
|
|
|
def test_function_set_closure_none(self):
|
|
# Test PyFunction_SetClosure()
|
|
def function_without_closure(): ...
|
|
def function_with_closure(arg):
|
|
def inner():
|
|
return arg
|
|
return inner
|
|
|
|
_testcapi.function_set_closure(function_without_closure, None)
|
|
self.assertIsNone(
|
|
_testcapi.function_get_closure(function_without_closure))
|
|
self.assertIsNone(function_without_closure.__closure__)
|
|
|
|
_testcapi.function_set_closure(function_with_closure, None)
|
|
self.assertIsNone(
|
|
_testcapi.function_get_closure(function_with_closure))
|
|
self.assertIsNone(function_with_closure.__closure__)
|
|
|
|
def test_function_set_closure_errors(self):
|
|
# Test PyFunction_SetClosure()
|
|
def function_without_closure(): ...
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_set_closure(None, ()) # not a function
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_set_closure(function_without_closure, 1)
|
|
self.assertIsNone(function_without_closure.__closure__) # no change
|
|
|
|
# NOTE: this works, but goes against the docs:
|
|
_testcapi.function_set_closure(function_without_closure, (1, 2))
|
|
self.assertEqual(
|
|
_testcapi.function_get_closure(function_without_closure), (1, 2))
|
|
self.assertEqual(function_without_closure.__closure__, (1, 2))
|
|
|
|
def test_function_get_annotations(self):
|
|
# Test PyFunction_GetAnnotations()
|
|
def normal():
|
|
pass
|
|
|
|
def annofn(arg: int) -> str:
|
|
return f'arg = {arg}'
|
|
|
|
annotations = _testcapi.function_get_annotations(normal)
|
|
self.assertIsNone(annotations)
|
|
|
|
annotations = _testcapi.function_get_annotations(annofn)
|
|
self.assertIsInstance(annotations, dict)
|
|
self.assertEqual(annotations, annofn.__annotations__)
|
|
|
|
with self.assertRaises(SystemError):
|
|
_testcapi.function_get_annotations(None)
|
|
|
|
# TODO: test PyFunction_New()
|
|
# TODO: test PyFunction_NewWithQualName()
|
|
# TODO: test PyFunction_SetVectorcall()
|
|
# TODO: test PyFunction_SetAnnotations()
|
|
# TODO: test PyClassMethod_New()
|
|
# TODO: test PyStaticMethod_New()
|
|
#
|
|
# PyFunction_AddWatcher() and PyFunction_ClearWatcher() are tested by
|
|
# test_capi.test_watchers.
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|