From 4875354d0340ae99bc223fc1707de703fc4a6e81 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 19 Jul 2024 13:01:09 -0400 Subject: [PATCH 01/11] tests/basic/class_setname: Test for __set_name__ functionality. Signed-off-by: Anson Mansfield --- tests/basics/class_setname.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/basics/class_setname.py diff --git a/tests/basics/class_setname.py b/tests/basics/class_setname.py new file mode 100644 index 0000000000000..f97a2dc577af3 --- /dev/null +++ b/tests/basics/class_setname.py @@ -0,0 +1,18 @@ +# test calling __set_name__ during class creation +# https://docs.python.org/3/reference/datamodel.html#object.__set_name__ + +class A: + def __set_name__(self, owner, name): + print("owner", owner.__name__) + print("name", name) + +class B: + a = A() + +# skip failing on minimal port without descriptor support +b = B() +try: + b.__class__ +except AttributeError: + print("SKIP") + raise SystemExit From 1b8383a2a14138f47bd011c1d7b38614561180db Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 19 Jul 2024 18:23:45 -0400 Subject: [PATCH 02/11] py/objtype: Implement __set_name__ functionality. Signed-off-by: Anson Mansfield --- py/objtype.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/py/objtype.c b/py/objtype.c index 16ff5310dac6f..7df0cc1433878 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -1234,6 +1234,23 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) } } + #if MICROPY_PY_DESCRIPTORS + // call __set_name__ on all entries (especially descriptors) + for (size_t i = 0; i < locals_map->alloc; i++) { + if (mp_map_slot_is_filled(locals_map, i)) { + elem = &(locals_map->table[i]); + + mp_obj_t set_name_method[4]; + mp_load_method_maybe(elem->value, MP_QSTR___set_name__, set_name_method); + if (set_name_method[1] != MP_OBJ_NULL) { + set_name_method[2] = MP_OBJ_FROM_PTR(o); + set_name_method[3] = elem->key; + mp_call_method_n_kw(2, 0, set_name_method); + } + } + } + #endif + return MP_OBJ_FROM_PTR(o); } From 55708121283c2df4b4e78a22153f0dd58ccc223a Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Sat, 20 Jul 2024 07:52:49 -0400 Subject: [PATCH 03/11] py/mpconfig: Add __set_name__ to feature flag description. Signed-off-by: Anson Mansfield --- py/mpconfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/mpconfig.h b/py/mpconfig.h index 98893ceb6d97b..bd32e84e95714 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1031,7 +1031,7 @@ typedef double mp_float_t; #define MICROPY_PY_FUNCTION_ATTRS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif -// Whether to support the descriptors __get__, __set__, __delete__ +// Whether to support the descriptors __get__, __set__, __delete__, __set_name__ // This costs some code size and makes load/store/delete of instance // attributes slower for the classes that use this feature #ifndef MICROPY_PY_DESCRIPTORS From 841dc8a799b1f707895f52f4bc4cbfad26e7506f Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 19 Jul 2024 21:24:05 -0400 Subject: [PATCH 04/11] tests/basic/class_initsubclass: Test for __init_subclass__. Signed-off-by: Anson Mansfield --- tests/basics/class_initsubclass.py | 35 +++++++++++++ tests/basics/class_initsubclass_recursive.py | 52 ++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 tests/basics/class_initsubclass.py create mode 100644 tests/basics/class_initsubclass_recursive.py diff --git a/tests/basics/class_initsubclass.py b/tests/basics/class_initsubclass.py new file mode 100644 index 0000000000000..34fec2e668295 --- /dev/null +++ b/tests/basics/class_initsubclass.py @@ -0,0 +1,35 @@ +# skip failing on minimal port +try: + + class T: + pass + + T().__class__ + getattr + + def print_check_all_attrs(obj, name: str, attrs: "list[str]"): + for attr in attrs: + print(name, attr, getattr(obj, attr, "missing")) + +except AttributeError: + print("SKIP") + raise SystemExit + + +class A: + a = "A" + + def __init_subclass__(cls): + print("A init_subclass", cls.__name__) + cls.aa = "AA" + + print("class A") + + +class B(A): + b = "B" + print("class B") + + +print_check_all_attrs(A, "A", ["a", "aa", "b"]) +print_check_all_attrs(B, "B", ["a", "aa", "b"]) diff --git a/tests/basics/class_initsubclass_recursive.py b/tests/basics/class_initsubclass_recursive.py new file mode 100644 index 0000000000000..84c03a6cbc5ee --- /dev/null +++ b/tests/basics/class_initsubclass_recursive.py @@ -0,0 +1,52 @@ +# skip failing on minimal port +try: + + class T: + pass + + T().__class__ + getattr + + def print_check_all_attrs(obj, name: str, attrs: "list[str]"): + for attr in attrs: + print(name, attr, getattr(obj, attr, "missing")) + +except AttributeError: + print("SKIP") + raise SystemExit + + +class A: + a = "A" + + def __init_subclass__(cls): + print("A init_subclass", cls.__name__) + cls.aa = "AA" + + print("class A") + + +class B(A): + b = "B" + + def __init_subclass__(cls): + super(B, cls).__init_subclass__() + print("B init_subclass", cls.__name__) + cls.bb = "BB" + + print("class B") + + +class C(B): + c = "C" + print("class C") + + +def print_check_all_attrs(obj, name: str, attrs: "list[str]"): + for attr in attrs: + print(name, attr, getattr(obj, attr, "missing")) + + +print_check_all_attrs(A, "A", ["a", "aa", "b", "bb", "c"]) +print_check_all_attrs(B, "B", ["a", "aa", "b", "bb", "c"]) +print_check_all_attrs(C, "C", ["a", "aa", "b", "bb", "c"]) From df384e192c143ca64ca71b00ca1bb596496b867e Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Thu, 25 Jul 2024 08:18:00 -0400 Subject: [PATCH 05/11] tests/cpydiff/core_class: Comprehensive test of super resolution order. Signed-off-by: Anson Mansfield --- tests/cpydiff/core_class_super_mro.py | 284 ++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 tests/cpydiff/core_class_super_mro.py diff --git a/tests/cpydiff/core_class_super_mro.py b/tests/cpydiff/core_class_super_mro.py new file mode 100644 index 0000000000000..b09ff9140d969 --- /dev/null +++ b/tests/cpydiff/core_class_super_mro.py @@ -0,0 +1,284 @@ + + +class Indent: + class IndentLabel: + def __init__(self, parent, label, **kw): + self.parent = parent + self.label = label + self.kw = kw + def __enter__(self): + print(self.parent, self.label, "{") + self.parent.__enter__() + def __exit__(self, exc_type, exc_value, traceback): + self.parent.__exit__(exc_type, exc_value, traceback) + if exc_value: + print(self.parent, "} ->", repr(exc_value)) + else: + print(self.parent, "}", **self.kw ) + + def __init__(self, init_count=0, multiplier=2): + self.count = init_count + self.multiplier = multiplier + def __str__(self): + return " "*(self.multiplier*self.count) + def __call__(self, *a, **kw): + return self.IndentLabel(self, *a, **kw) + def __enter__(self): + self.count += 1 + def __exit__(self, exc_type, exc_value, traceback): + self.count -= 1 + +indent = Indent(multiplier=2) + +def repr_class(cls): + return f"" +def repr_super(s): + return repr(s) + +try: + with indent("AAA", end=" = "): + class AAA: + """Class Inheritance Tree to test MRO Traversal Order............................................................""" + + def __init__(self) -> None: + global indent + with indent("AAA.__init__"): + s = super(AAA, self) + print(indent, "super =", repr_super(s)) + s.__init__() + + def __init_subclass__(cls) -> None: + global indent + with indent("AAA.__init_subclass__"): + print(indent, "cls =", repr_class(cls)) + s = super(AAA, cls) + print(indent, "super =", repr_super(s)) + s.__init_subclass__() + + def __repr__(self) -> str: + return "" + + print(repr_class(AAA)) +except Exception as e: + pass + +try: + with indent("AAB", end=" = "): + class AAB: + """Class Inheritance Tree to test MRO Traversal Order............................................................""" + + def __init__(self) -> None: + global indent + with indent("AAB.__init__"): + s = super(AAB, self) + print(indent, "super =", repr_super(s)) + s.__init__() + + def __init_subclass__(cls) -> None: + global indent + with indent("AAB.__init_subclass__"): + print(indent, "cls =", repr_class(cls)) + s = super(AAB, cls) + print(indent, "super =", repr_super(s)) + s.__init_subclass__() + + def __repr__(self) -> str: + return "" + + print(repr_class(AAB)) +except Exception as e: + pass + +try: + with indent("ABA", end=" = "): + class ABA: + """Class Inheritance Tree to test MRO Traversal Order............................................................""" + + def __init__(self) -> None: + global indent + with indent("ABA.__init__"): + s = super(ABA, self) + print(indent, "super =", repr_super(s)) + s.__init__() + + def __init_subclass__(cls) -> None: + global indent + with indent("ABA.__init_subclass__"): + print(indent, "cls =", repr_class(cls)) + s = super(ABA, cls) + print(indent, "super =", repr_super(s)) + s.__init_subclass__() + + def __repr__(self) -> str: + return "" + + print(repr_class(ABA)) +except Exception as e: + pass + +try: + with indent("ABB", end=" = "): + class ABB: + """Class Inheritance Tree to test MRO Traversal Order............................................................""" + + def __init__(self) -> None: + global indent + with indent("ABB.__init__"): + s = super(ABB, self) + print(indent, "super =", repr_super(s)) + s.__init__() + + def __init_subclass__(cls) -> None: + global indent + with indent("ABB.__init_subclass__"): + print(indent, "cls =", repr_class(cls)) + s = super(ABB, cls) + print(indent, "super =", repr_super(s)) + s.__init_subclass__() + + def __repr__(self) -> str: + return "" + + print(repr_class(ABB)) +except Exception as e: + pass + +try: + with indent("AAx", end=" = "): + class AAx(AAA, AAB): + """Class Inheritance Tree to test MRO Traversal Order............................................................""" + + def __init__(self) -> None: + global indent + with indent("AAx.__init__"): + s = super(AAx, self) + print(indent, "super =", repr_super(s)) + s.__init__() + + def __init_subclass__(cls) -> None: + global indent + with indent("AAx.__init_subclass__"): + print(indent, "cls =", repr_class(cls)) + s = super(AAx, cls) + print(indent, "super =", repr_super(s)) + s.__init_subclass__() + + def __repr__(self) -> str: + return "" + + print(repr_class(AAx)) +except Exception as e: + pass + +try: + with indent("ABx", end=" = "): + class ABx(ABA, ABB): + """Class Inheritance Tree to test MRO Traversal Order............................................................""" + + def __init__(self) -> None: + global indent + with indent("ABx.__init__"): + s = super(ABx, self) + print(indent, "super =", repr_super(s)) + s.__init__() + + def __init_subclass__(cls) -> None: + global indent + with indent("ABx.__init_subclass__"): + print(indent, "cls =", repr_class(cls)) + s = super(ABx, cls) + print(indent, "super =", repr_super(s)) + s.__init_subclass__() + + def __repr__(self) -> str: + return "" + + print(repr_class(ABx)) +except Exception as e: + pass + +try: + with indent("Axx", end=" = "): + class Axx(AAx, ABx): + """Class Inheritance Tree to test MRO Traversal Order............................................................""" + + def __init__(self) -> None: + global indent + with indent("Axx.__init__"): + s = super(Axx, self) + print(indent, "super =", repr_super(s)) + s.__init__() + + def __init_subclass__(cls) -> None: + global indent + with indent("Axx.__init_subclass__"): + print(indent, "cls =", repr_class(cls)) + s = super(Axx, cls) + print(indent, "super =", repr_super(s)) + s.__init_subclass__() + + def __repr__(self) -> str: + return "" + + print(repr_class(Axx)) +except Exception as e: + pass + + + + +try: + """....................................................................................................""" + with indent("AAA()", end=" = "): + aaa = AAA() + print(repr(aaa)) +except Exception as e: + pass + +try: + """....................................................................................................""" + with indent("AAB()", end=" = "): + aab = AAB() + print(repr(aab)) +except Exception as e: + pass + +try: + """....................................................................................................""" + with indent("ABA()", end=" = "): + aba = ABA() + print(repr(aba)) +except Exception as e: + pass + +try: + """....................................................................................................""" + with indent("ABB()", end=" = "): + abb = ABB() + print(repr(abb)) +except Exception as e: + pass + +try: + """....................................................................................................""" + with indent("AAx()", end=" = "): + aax = AAx() + print(repr(aax)) +except Exception as e: + pass + +try: + """....................................................................................................""" + with indent("ABx()", end=" = "): + abx = ABx() + print(repr(abx)) +except Exception as e: + pass + +try: + """....................................................................................................""" + with indent("Axx()", end=" = "): + axx = Axx() + print(repr(axx)) +except Exception as e: + pass From 46e43652a071cef74eb47edf1eeeb78ed57f8acf Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 19 Jul 2024 21:27:32 -0400 Subject: [PATCH 06/11] py/mpconfig: Add feature flag for metaclasses. Signed-off-by: Anson Mansfield --- ports/windows/mpconfigport.h | 1 + py/mpconfig.h | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index fabc9072d6c70..3829308814ec3 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -81,6 +81,7 @@ #define MICROPY_VFS_POSIX (1) #define MICROPY_PY_FUNCTION_ATTRS (1) #define MICROPY_PY_DESCRIPTORS (1) +#define MICROPY_PY_METACLASSES (1) #define MICROPY_PY_DELATTR_SETATTR (1) #define MICROPY_PY_FSTRINGS (1) #define MICROPY_PY_BUILTINS_BYTES_HEX (1) diff --git a/py/mpconfig.h b/py/mpconfig.h index bd32e84e95714..a0e9427ada125 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1038,6 +1038,12 @@ typedef double mp_float_t; #define MICROPY_PY_DESCRIPTORS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif +// Whether to support metaclass functionality (currently just __init_subclass__) +// This costs some code size and makes class creation slower +#ifndef MICROPY_PY_METACLASSES +#define MICROPY_PY_METACLASSES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#endif + // Whether to support class __delattr__ and __setattr__ methods // This costs some code size and makes store/delete of instance // attributes slower for the classes that use this feature From 2c4d8d2d57606ef9931ff30b166e3c58d33ce7d0 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 19 Jul 2024 21:25:41 -0400 Subject: [PATCH 07/11] py/objtype: Implement __init_subclass__ functionality. Signed-off-by: Anson Mansfield --- py/objtype.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/py/objtype.c b/py/objtype.c index 7df0cc1433878..7a22836a9ac34 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -1234,7 +1234,22 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) } } - #if MICROPY_PY_DESCRIPTORS + #if MICROPY_PY_METACLASSES + // __init_subclass__ is a special-cased classmethod in CPython + // See: https://github.com/python/cpython/blob/3de092b82f5aa02fa293cd654c2ab26556ecf703/Objects/typeobject.c#L4247 + elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(MP_QSTR___init_subclass__), MP_MAP_LOOKUP); + if (elem != NULL) { + // __init_subclass__ slot exists; check if it is a function + if (mp_obj_is_fun(elem->value)) { + // __init_subclass__ is a function, wrap it in a classmethod decorator + elem->value = static_class_method_make_new(&mp_type_classmethod, 1, 0, &elem->value); + } + } + #endif + + // CPython calls __set_name__ on class members before calling __init_subclass__ on bases. + // See: https://github.com/python/cpython/blob/3de092b82f5aa02fa293cd654c2ab26556ecf703/Objects/typeobject.c#L4369-L4375 + #if MICROPY_PY_DESCRIPTORS | MICROPY_PY_METACLASSES // call __set_name__ on all entries (especially descriptors) for (size_t i = 0; i < locals_map->alloc; i++) { if (mp_map_slot_is_filled(locals_map, i)) { @@ -1251,6 +1266,19 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) } #endif + #if MICROPY_PY_METACLASSES + // call __init_subclass__ from each base class + for (size_t i = 0; i < bases_len; i++) { + size_t j = bases_len - i - 1; // reversed iteration order to match the usual recursion + mp_obj_t init_subclass_method[2]; + mp_load_method_maybe(bases_items[j], MP_QSTR___init_subclass__, init_subclass_method); + if (init_subclass_method[1] != MP_OBJ_NULL) { + init_subclass_method[1] = MP_OBJ_FROM_PTR(o); + mp_call_method_n_kw(0, 0, init_subclass_method); + } + } + #endif + return MP_OBJ_FROM_PTR(o); } From 136f2ad9bfc48eb5ba4ce7a8e205ef78395785ba Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Wed, 24 Jul 2024 16:05:57 -0400 Subject: [PATCH 08/11] fixup! py/objtype: Implement __init_subclass__ functionality. --- py/objtype.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/objtype.c b/py/objtype.c index 7a22836a9ac34..8610eade81a95 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -1269,12 +1269,12 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) #if MICROPY_PY_METACLASSES // call __init_subclass__ from each base class for (size_t i = 0; i < bases_len; i++) { - size_t j = bases_len - i - 1; // reversed iteration order to match the usual recursion mp_obj_t init_subclass_method[2]; - mp_load_method_maybe(bases_items[j], MP_QSTR___init_subclass__, init_subclass_method); + mp_load_method_maybe(bases_items[i], MP_QSTR___init_subclass__, init_subclass_method); if (init_subclass_method[1] != MP_OBJ_NULL) { init_subclass_method[1] = MP_OBJ_FROM_PTR(o); mp_call_method_n_kw(0, 0, init_subclass_method); + break; } } #endif From eea5a9cb52cb759a9b625661f5f2c61bd0f1685a Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Fri, 19 Jul 2024 21:37:50 -0400 Subject: [PATCH 09/11] tests/cpydiff/core_class: Document limitations of __init_subclass__. Signed-off-by: Anson Mansfield --- tests/cpydiff/core_class_initsubclass_kw.py | 19 +++++ .../cpydiff/core_class_initsubclass_multi.py | 77 +++++++++++++++++++ .../core_class_initsubclass_recursive.py | 42 ++++++++++ 3 files changed, 138 insertions(+) create mode 100644 tests/cpydiff/core_class_initsubclass_kw.py create mode 100644 tests/cpydiff/core_class_initsubclass_multi.py create mode 100644 tests/cpydiff/core_class_initsubclass_recursive.py diff --git a/tests/cpydiff/core_class_initsubclass_kw.py b/tests/cpydiff/core_class_initsubclass_kw.py new file mode 100644 index 0000000000000..e044ab66c9e14 --- /dev/null +++ b/tests/cpydiff/core_class_initsubclass_kw.py @@ -0,0 +1,19 @@ +""" +categories: Core,Classes +description: Keyword arguments are not passed to __init_subclass__. +cause: Micropython doesn't allow kwargs in a base class list. +workaround: Unknown +""" + + +class Philosopher: + def __init_subclass__(cls, default_name, **kwargs): + super().__init_subclass__(**kwargs) + cls.default_name = default_name + + +class AustralianPhilosopher(Philosopher, default_name="Bruce"): + pass + + +print(AustralianPhilosopher.default_name) diff --git a/tests/cpydiff/core_class_initsubclass_multi.py b/tests/cpydiff/core_class_initsubclass_multi.py new file mode 100644 index 0000000000000..8b85999011404 --- /dev/null +++ b/tests/cpydiff/core_class_initsubclass_multi.py @@ -0,0 +1,77 @@ +""" +categories: Core,Classes +description: Micropython calls __init_subclass__ directly for all direct base classes. +cause: Micropython can't rely on __init_subclass__ implementations calling super().__init_subclass__() to recurse through ancestor classes like CPython. +workaround: Omit calling super().__init_subclass__() in __init_subclass__ implementations. +""" + + +# In CPython, only the first base__init_subclass__ +class A1: + a = "A1" + + def __init_subclass__(cls): + print("A1 init_subclass", cls.__name__) + cls.aa = "AA" + + print("class A1") + + +class B1: + b = "B1" + + def __init_subclass__(cls): + print("B1 init_subclass", cls.__name__) + cls.bb = "BB" + + print("class B1") + + +class C1(A1, B1): + c = "C1" + print("class C1") + + +# In CPython it's specified to call super().__init_subclass__() in __init_subclass__. +# But the presence of super() makes the invocation of __init_subclass__ fail. +class A2: + a = "A2" + + @classmethod + def __init_subclass__(cls): + super().__init_subclass__() + print("A2 init_subclass", cls.__name__) + cls.aa = "AA" + + print("class A2") + + +class B2: + b = "B2" + + @classmethod + def __init_subclass__(cls): + super().__init_subclass__() + print("B2 init_subclass", cls.__name__) + cls.bb = "BB" + + print("class B2") + + +class C2(A2, B2): + c = "C2" + print("class C2") + + +def print_check_all_attrs(obj, name: str, attrs: "list[str]"): + for attr in attrs: + print(name, attr, getattr(obj, attr, "missing")) + + +print_check_all_attrs(A1, "A1", ["a", "aa", "b", "bb", "c"]) +print_check_all_attrs(B1, "B1", ["a", "aa", "b", "bb", "c"]) +print_check_all_attrs(C1, "C1", ["a", "aa", "b", "bb", "c"]) + +print_check_all_attrs(A2, "A2", ["a", "aa", "b", "bb", "c"]) +print_check_all_attrs(B2, "B2", ["a", "aa", "b", "bb", "c"]) +print_check_all_attrs(C2, "C2", ["a", "aa", "b", "bb", "c"]) diff --git a/tests/cpydiff/core_class_initsubclass_recursive.py b/tests/cpydiff/core_class_initsubclass_recursive.py new file mode 100644 index 0000000000000..19b6db26b503e --- /dev/null +++ b/tests/cpydiff/core_class_initsubclass_recursive.py @@ -0,0 +1,42 @@ +""" +categories: Core,Classes +description: Micropython can't recurse through base classes for __init_subclass__. +cause: Micropython doesn't support super() inside classmethods. +workaround: Unknown +""" + + +class A: + a = "A" + + def __init_subclass__(cls): + print("A init_subclass", cls.__name__) + cls.aa = "AA" + + print("class A") + + +class B(A): + b = "B" + + def __init_subclass__(cls): + super().__init_subclass__() + print("B init_subclass", cls.__name__) + cls.bb = "BB" + + print("class B") + + +class C(B): + c = "C" + print("class C") + + +def print_check_all_attrs(obj, name: str, attrs: "list[str]"): + for attr in attrs: + print(name, attr, getattr(obj, attr, "missing")) + + +print_check_all_attrs(A, "A", ["a", "aa", "b", "bb", "c"]) +print_check_all_attrs(B, "B", ["a", "aa", "b", "bb", "c"]) +print_check_all_attrs(C, "C", ["a", "aa", "b", "bb", "c"]) From 5db3599f5cb9f7eb9441d733adda759ea70dafc0 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Wed, 24 Jul 2024 15:58:19 -0400 Subject: [PATCH 10/11] py/objobject: Add default __init_subclass__. Signed-off-by: Anson Mansfield --- py/objobject.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/py/objobject.c b/py/objobject.c index ff93fd0827d2f..b0890b3f7fb60 100644 --- a/py/objobject.c +++ b/py/objobject.c @@ -95,6 +95,21 @@ static mp_obj_t object___delattr__(mp_obj_t self_in, mp_obj_t attr) { static MP_DEFINE_CONST_FUN_OBJ_2(object___delattr___obj, object___delattr__); #endif +#if MICROPY_PY_METACLASSES +static mp_obj_t object___init_subclass__(mp_obj_t cls_in) { + // call over to the next base, essentially `super(object, cls).__init_subclass__()` + // (but if this is the last base, we're done) + // mp_obj_t init_subclass_method[2] = {&mp_type_object, cls_in}; + // mp_load_super_method_maybe(MP_QSTR___init_subclass__, init_subclass_method); + // if (init_subclass_method[1] != MP_OBJ_NULL) { + // mp_call_method_n_kw(0, 0, init_subclass_method); + // } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(object___init_subclass___fun_obj, object___init_subclass__); +static MP_DEFINE_CONST_CLASSMETHOD_OBJ(object___init_subclass___obj, MP_ROM_PTR(&object___init_subclass___fun_obj)); +#endif + static const mp_rom_map_elem_t object_locals_dict_table[] = { #if MICROPY_CPYTHON_COMPAT { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&object___init___obj) }, @@ -106,6 +121,9 @@ static const mp_rom_map_elem_t object_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___setattr__), MP_ROM_PTR(&object___setattr___obj) }, { MP_ROM_QSTR(MP_QSTR___delattr__), MP_ROM_PTR(&object___delattr___obj) }, #endif + #if MICROPY_PY_METACLASSES + { MP_ROM_QSTR(MP_QSTR___init_subclass__), MP_ROM_PTR(&object___init_subclass___obj) }, + #endif }; static MP_DEFINE_CONST_DICT(object_locals_dict, object_locals_dict_table); From 8cf652d73a951f9cd739a027dee6708ecb5fabb5 Mon Sep 17 00:00:00 2001 From: Anson Mansfield Date: Thu, 25 Jul 2024 09:44:15 -0400 Subject: [PATCH 11/11] fixup! fixup! py/objtype: Implement __init_subclass__ functionality. --- py/objtype.c | 1 - 1 file changed, 1 deletion(-) diff --git a/py/objtype.c b/py/objtype.c index 8610eade81a95..65bd82b0aec05 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -1274,7 +1274,6 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) if (init_subclass_method[1] != MP_OBJ_NULL) { init_subclass_method[1] = MP_OBJ_FROM_PTR(o); mp_call_method_n_kw(0, 0, init_subclass_method); - break; } } #endif pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy