mirror of
https://github.com/python/cpython.git
synced 2024-11-23 09:54:58 +08:00
gh-115775: Compiler adds __static_attributes__ field to classes (#115913)
This commit is contained in:
parent
70969d53a7
commit
79be75735c
@ -724,6 +724,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__slotnames__));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__slots__));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__spec__));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__static_attributes__));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__str__));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__sub__));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__subclasscheck__));
|
||||
|
@ -213,6 +213,7 @@ struct _Py_global_strings {
|
||||
STRUCT_FOR_ID(__slotnames__)
|
||||
STRUCT_FOR_ID(__slots__)
|
||||
STRUCT_FOR_ID(__spec__)
|
||||
STRUCT_FOR_ID(__static_attributes__)
|
||||
STRUCT_FOR_ID(__str__)
|
||||
STRUCT_FOR_ID(__sub__)
|
||||
STRUCT_FOR_ID(__subclasscheck__)
|
||||
|
1
Include/internal/pycore_runtime_init_generated.h
generated
1
Include/internal/pycore_runtime_init_generated.h
generated
@ -722,6 +722,7 @@ extern "C" {
|
||||
INIT_ID(__slotnames__), \
|
||||
INIT_ID(__slots__), \
|
||||
INIT_ID(__spec__), \
|
||||
INIT_ID(__static_attributes__), \
|
||||
INIT_ID(__str__), \
|
||||
INIT_ID(__sub__), \
|
||||
INIT_ID(__subclasscheck__), \
|
||||
|
@ -480,6 +480,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
|
||||
string = &_Py_ID(__spec__);
|
||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||
_PyUnicode_InternInPlace(interp, &string);
|
||||
string = &_Py_ID(__static_attributes__);
|
||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||
_PyUnicode_InternInPlace(interp, &string);
|
||||
string = &_Py_ID(__str__);
|
||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||
_PyUnicode_InternInPlace(interp, &string);
|
||||
|
@ -2018,7 +2018,8 @@ def _test_simple_enum(checked_enum, simple_enum):
|
||||
+ list(simple_enum._member_map_.keys())
|
||||
)
|
||||
for key in set(checked_keys + simple_keys):
|
||||
if key in ('__module__', '_member_map_', '_value2member_map_', '__doc__'):
|
||||
if key in ('__module__', '_member_map_', '_value2member_map_', '__doc__',
|
||||
'__static_attributes__'):
|
||||
# keys known to be different, or very long
|
||||
continue
|
||||
elif key in member_names:
|
||||
|
@ -313,7 +313,8 @@ def visiblename(name, all=None, obj=None):
|
||||
if name in {'__author__', '__builtins__', '__cached__', '__credits__',
|
||||
'__date__', '__doc__', '__file__', '__spec__',
|
||||
'__loader__', '__module__', '__name__', '__package__',
|
||||
'__path__', '__qualname__', '__slots__', '__version__'}:
|
||||
'__path__', '__qualname__', '__slots__', '__version__',
|
||||
'__static_attributes__'}:
|
||||
return 0
|
||||
# Private names are hidden, but special names are displayed.
|
||||
if name.startswith('__') and name.endswith('__'): return 1
|
||||
|
@ -1960,6 +1960,64 @@ class TestSourcePositions(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestExpectedAttributes(unittest.TestCase):
|
||||
|
||||
def test_basic(self):
|
||||
class C:
|
||||
def f(self):
|
||||
self.a = self.b = 42
|
||||
|
||||
self.assertIsInstance(C.__static_attributes__, tuple)
|
||||
self.assertEqual(sorted(C.__static_attributes__), ['a', 'b'])
|
||||
|
||||
def test_nested_function(self):
|
||||
class C:
|
||||
def f(self):
|
||||
self.x = 1
|
||||
self.y = 2
|
||||
self.x = 3 # check deduplication
|
||||
|
||||
def g(self, obj):
|
||||
self.y = 4
|
||||
self.z = 5
|
||||
|
||||
def h(self, a):
|
||||
self.u = 6
|
||||
self.v = 7
|
||||
|
||||
obj.self = 8
|
||||
|
||||
self.assertEqual(sorted(C.__static_attributes__), ['u', 'v', 'x', 'y', 'z'])
|
||||
|
||||
def test_nested_class(self):
|
||||
class C:
|
||||
def f(self):
|
||||
self.x = 42
|
||||
self.y = 42
|
||||
|
||||
class D:
|
||||
def g(self):
|
||||
self.y = 42
|
||||
self.z = 42
|
||||
|
||||
self.assertEqual(sorted(C.__static_attributes__), ['x', 'y'])
|
||||
self.assertEqual(sorted(C.D.__static_attributes__), ['y', 'z'])
|
||||
|
||||
def test_subclass(self):
|
||||
class C:
|
||||
def f(self):
|
||||
self.x = 42
|
||||
self.y = 42
|
||||
|
||||
class D(C):
|
||||
def g(self):
|
||||
self.y = 42
|
||||
self.z = 42
|
||||
|
||||
self.assertEqual(sorted(C.__static_attributes__), ['x', 'y'])
|
||||
self.assertEqual(sorted(D.__static_attributes__), ['y', 'z'])
|
||||
|
||||
|
||||
class TestExpressionStackSize(unittest.TestCase):
|
||||
# These tests check that the computed stack size for a code object
|
||||
# stays within reasonable bounds (see issue #21523 for an example
|
||||
|
@ -5080,7 +5080,8 @@ class DictProxyTests(unittest.TestCase):
|
||||
keys = list(it)
|
||||
keys.sort()
|
||||
self.assertEqual(keys, ['__dict__', '__doc__', '__module__',
|
||||
'__weakref__', 'meth'])
|
||||
'__static_attributes__', '__weakref__',
|
||||
'meth'])
|
||||
|
||||
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
|
||||
'trace function introduces __local__')
|
||||
@ -5089,7 +5090,7 @@ class DictProxyTests(unittest.TestCase):
|
||||
it = self.C.__dict__.values()
|
||||
self.assertNotIsInstance(it, list)
|
||||
values = list(it)
|
||||
self.assertEqual(len(values), 5)
|
||||
self.assertEqual(len(values), 6)
|
||||
|
||||
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
|
||||
'trace function introduces __local__')
|
||||
@ -5100,7 +5101,8 @@ class DictProxyTests(unittest.TestCase):
|
||||
keys = [item[0] for item in it]
|
||||
keys.sort()
|
||||
self.assertEqual(keys, ['__dict__', '__doc__', '__module__',
|
||||
'__weakref__', 'meth'])
|
||||
'__static_attributes__', '__weakref__',
|
||||
'meth'])
|
||||
|
||||
def test_dict_type_with_metaclass(self):
|
||||
# Testing type of __dict__ when metaclass set...
|
||||
|
@ -1160,7 +1160,7 @@ class APIMismatchTest(unittest.TestCase):
|
||||
def test_RawIOBase_io_in_pyio_match(self):
|
||||
"""Test that pyio RawIOBase class has all c RawIOBase methods"""
|
||||
mismatch = support.detect_api_mismatch(pyio.RawIOBase, io.RawIOBase,
|
||||
ignore=('__weakref__',))
|
||||
ignore=('__weakref__', '__static_attributes__'))
|
||||
self.assertEqual(mismatch, set(), msg='Python RawIOBase does not have all C RawIOBase methods')
|
||||
|
||||
def test_RawIOBase_pyio_in_io_match(self):
|
||||
|
@ -167,6 +167,7 @@ Use a __prepare__ method that returns an instrumented dict.
|
||||
d['foo'] = 4
|
||||
d['foo'] = 42
|
||||
d['bar'] = 123
|
||||
d['__static_attributes__'] = ()
|
||||
>>>
|
||||
|
||||
Use a metaclass that doesn't derive from type.
|
||||
@ -182,12 +183,12 @@ Use a metaclass that doesn't derive from type.
|
||||
... b = 24
|
||||
...
|
||||
meta: C ()
|
||||
ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)]
|
||||
ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('__static_attributes__', ()), ('a', 42), ('b', 24)]
|
||||
kw: []
|
||||
>>> type(C) is dict
|
||||
True
|
||||
>>> print(sorted(C.items()))
|
||||
[('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)]
|
||||
[('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('__static_attributes__', ()), ('a', 42), ('b', 24)]
|
||||
>>>
|
||||
|
||||
And again, with a __prepare__ attribute.
|
||||
@ -208,8 +209,9 @@ And again, with a __prepare__ attribute.
|
||||
d['a'] = 1
|
||||
d['a'] = 2
|
||||
d['b'] = 3
|
||||
d['__static_attributes__'] = ()
|
||||
meta: C ()
|
||||
ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 2), ('b', 3)]
|
||||
ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('__static_attributes__', ()), ('a', 2), ('b', 3)]
|
||||
kw: [('other', 'booh')]
|
||||
>>>
|
||||
|
||||
|
@ -1717,7 +1717,7 @@ _SPECIAL_NAMES = frozenset({
|
||||
'__abstractmethods__', '__annotations__', '__dict__', '__doc__',
|
||||
'__init__', '__module__', '__new__', '__slots__',
|
||||
'__subclasshook__', '__weakref__', '__class_getitem__',
|
||||
'__match_args__',
|
||||
'__match_args__', '__static_attributes__',
|
||||
})
|
||||
|
||||
# These special attributes will be not collected as protocol members.
|
||||
|
@ -0,0 +1,3 @@
|
||||
Compiler populates the new ``__static_attributes__`` field on a class with
|
||||
the names of attributes of this class which are accessed through self.X from
|
||||
any function in its body.
|
@ -358,7 +358,8 @@ struct compiler_unit {
|
||||
|
||||
int u_scope_type;
|
||||
|
||||
PyObject *u_private; /* for private name mangling */
|
||||
PyObject *u_private; /* for private name mangling */
|
||||
PyObject *u_static_attributes; /* for class: attributes accessed via self.X */
|
||||
|
||||
instr_sequence u_instr_sequence; /* codegen output */
|
||||
|
||||
@ -690,9 +691,26 @@ compiler_unit_free(struct compiler_unit *u)
|
||||
Py_CLEAR(u->u_metadata.u_cellvars);
|
||||
Py_CLEAR(u->u_metadata.u_fasthidden);
|
||||
Py_CLEAR(u->u_private);
|
||||
Py_CLEAR(u->u_static_attributes);
|
||||
PyMem_Free(u);
|
||||
}
|
||||
|
||||
static struct compiler_unit *
|
||||
get_class_compiler_unit(struct compiler *c)
|
||||
{
|
||||
Py_ssize_t stack_size = PyList_GET_SIZE(c->c_stack);
|
||||
for (Py_ssize_t i = stack_size - 1; i >= 0; i--) {
|
||||
PyObject *capsule = PyList_GET_ITEM(c->c_stack, i);
|
||||
struct compiler_unit *u = (struct compiler_unit *)PyCapsule_GetPointer(
|
||||
capsule, CAPSULE_NAME);
|
||||
assert(u);
|
||||
if (u->u_scope_type == COMPILER_SCOPE_CLASS) {
|
||||
return u;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
compiler_set_qualname(struct compiler *c)
|
||||
{
|
||||
@ -1336,6 +1354,16 @@ compiler_enter_scope(struct compiler *c, identifier name,
|
||||
}
|
||||
|
||||
u->u_private = NULL;
|
||||
if (scope_type == COMPILER_SCOPE_CLASS) {
|
||||
u->u_static_attributes = PySet_New(0);
|
||||
if (!u->u_static_attributes) {
|
||||
compiler_unit_free(u);
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
else {
|
||||
u->u_static_attributes = NULL;
|
||||
}
|
||||
|
||||
/* Push the old compiler_unit on the stack. */
|
||||
if (c->u) {
|
||||
@ -2517,6 +2545,18 @@ compiler_class_body(struct compiler *c, stmt_ty s, int firstlineno)
|
||||
compiler_exit_scope(c);
|
||||
return ERROR;
|
||||
}
|
||||
assert(c->u->u_static_attributes);
|
||||
PyObject *static_attributes = PySequence_Tuple(c->u->u_static_attributes);
|
||||
if (static_attributes == NULL) {
|
||||
compiler_exit_scope(c);
|
||||
return ERROR;
|
||||
}
|
||||
ADDOP_LOAD_CONST(c, NO_LOCATION, static_attributes);
|
||||
Py_CLEAR(static_attributes);
|
||||
if (compiler_nameop(c, NO_LOCATION, &_Py_ID(__static_attributes__), Store) < 0) {
|
||||
compiler_exit_scope(c);
|
||||
return ERROR;
|
||||
}
|
||||
/* The following code is artificial */
|
||||
/* Set __classdictcell__ if necessary */
|
||||
if (c->u->u_ste->ste_needs_classdict) {
|
||||
@ -2657,6 +2697,7 @@ compiler_class(struct compiler *c, stmt_ty s)
|
||||
s->v.ClassDef.keywords));
|
||||
|
||||
PyCodeObject *co = optimize_and_assemble(c, 0);
|
||||
|
||||
compiler_exit_scope(c);
|
||||
if (co == NULL) {
|
||||
return ERROR;
|
||||
@ -6246,6 +6287,17 @@ compiler_visit_expr1(struct compiler *c, expr_ty e)
|
||||
ADDOP(c, loc, NOP);
|
||||
return SUCCESS;
|
||||
}
|
||||
if (e->v.Attribute.value->kind == Name_kind &&
|
||||
_PyUnicode_EqualToASCIIString(e->v.Attribute.value->v.Name.id, "self"))
|
||||
{
|
||||
struct compiler_unit *class_u = get_class_compiler_unit(c);
|
||||
if (class_u != NULL) {
|
||||
assert(class_u->u_scope_type == COMPILER_SCOPE_CLASS);
|
||||
assert(class_u->u_static_attributes);
|
||||
RETURN_IF_ERROR(
|
||||
PySet_Add(class_u->u_static_attributes, e->v.Attribute.attr));
|
||||
}
|
||||
}
|
||||
VISIT(c, expr, e->v.Attribute.value);
|
||||
loc = LOC(e);
|
||||
loc = update_start_location_to_match_attr(c, loc, e);
|
||||
|
Loading…
Reference in New Issue
Block a user