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 98893ceb6d97b..a0e9427ada125 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1031,13 +1031,19 @@ 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 #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 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); diff --git a/py/objtype.c b/py/objtype.c index 16ff5310dac6f..65bd82b0aec05 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -1234,6 +1234,50 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) } } + #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)) { + 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 + + #if MICROPY_PY_METACLASSES + // call __init_subclass__ from each base class + for (size_t i = 0; i < bases_len; i++) { + mp_obj_t init_subclass_method[2]; + 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); + } + } + #endif + return MP_OBJ_FROM_PTR(o); } 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"]) 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 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"]) 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 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