`__annotations__` was removed from 'members' in `type.__new__` without alternative in Python 3.14
还没有人认领这个 Issue。
- 主要语言
- Python
- 星标
- 77.2k
- 派生
- 36k
- PR 合并指标
- PR 指标待抓取
描述
Bug report
Bug description:
Python 3.14 changes the behavior of annotation in PEP649 - Deferred Evaluation Of Annotations Using Descriptors. This causes my metaclass ExtendedType to fail (part of my collection of Python classes, decorators, functions and metaclasses in pyTooling).
This metaclass infers __slots__ automatically from type annotations and significantly speeds up any Python code by using slots. Furthermore, it enforces good quality code as all members must exist in __slots__. My implementation also capable of handling multiple-inheritance for slotted classes.
Because of this, pyTooling offers currently the fastest tree implementation outperforming other libraries by 2x and more (itertree, anytree, treelib, ...).
By reading:
- https://github.com/python/cpython/issues/139140, and
- PEP649 - Deferred Evaluation Of Annotations Using Descriptors
I can't see how the behavior of Python 3.9..3.13 can be preserved. It's essential to access class annotations while constructing a new class using a metaclass!
The proposed API inpect.get_annotations is defined for existing classes. I need an API accessing annotations before the class exists. There is also a new annotationlib. Again, this is for existing classes and objects.
My Python debugger lists a __annotate_func__ member:
- According to https://github.com/astral-sh/ruff/issues/17859, it shouldn't be used.
The complete code is quite complicated. If needed I could see if I can strip it down to a more minimal example with multiple inheritance, singleton and abstract behavior features.
Here are some essential parts:
class ExtendedType(type):
def __new__(self, className: str, baseClasses: Tuple[type], members: Dict[str, Any], slots: bool = False, mixin: bool = False, singleton: bool = False) -> "ExtendedType":
# Compute slots and mixin-slots from annotated fields as well as class- and object-fields with initial values.
classFields, objectFields = self._computeSlots(className, baseClasses, members, slots, mixin)
# Create a new class
newClass = type.__new__(self, className, baseClasses, members)
# Apply class fields
for fieldName, typeAnnotation in classFields.items():
setattr(newClass, fieldName, typeAnnotation)
return newClass
@classmethod
def _computeSlots(self, className, baseClasses, members, slots, mixin):
# Compute which field are listed in __slots__ and which need to be initialized in an instance or class.
slottedFields = []
objectFields = {}
classFields = {}
if slots or mixin:
# If slots are used, all base classes must use __slots__.
for baseClass in self._iterateBaseClasses(baseClasses):
# Exclude object as a special case
if baseClass is object or baseClass is Generic:
continue
if not hasattr(baseClass, "__slots__"):
ex = BaseClassWithoutSlotsError(f"Base-classes '{baseClass.__name__}' doesn't use '__slots__'.")
ex.add_note(f"All base-classes of a class using '__slots__' must use '__slots__' itself.")
raise ex
# Copy all field names from primary base-class' __slots__, which are later needed for error checking.
inheritedSlottedFields = {}
if len(baseClasses) > 0:
for base in reversed(baseClasses[0].mro()):
# Exclude object as a special case
if base is object or base is Generic:
continue
for annotation in base.__slots__:
inheritedSlottedFields[annotation] = base
# When adding annotated fields to slottedFields, check if name was not used in inheritance hierarchy.
annotations: Dict[str, Any] = members.get("__annotations__", {})
from inspect import get_annotations
anno = members.get("__annotation__")
for fieldName, typeAnnotation in annotations.items():
if fieldName in inheritedSlottedFields:
cls = inheritedSlottedFields[fieldName]
raise AttributeError(f"Slot '{fieldName}' already exists in base-class '{cls.__module__}.{cls.__name__}'.")
# If annotated field is a ClassVar, and it has an initial value
# * copy field and initial value to classFields dictionary
# * remove field from members
if isinstance(typeAnnotation, _GenericAlias) and typeAnnotation.__origin__ is ClassVar and fieldName in members:
classFields[fieldName] = members[fieldName]
del members[fieldName]
# If an annotated field has an initial value
# * copy field and initial value to objectFields dictionary
# * remove field from members
elif fieldName in members:
slottedFields.append(fieldName)
objectFields[fieldName] = members[fieldName]
del members[fieldName]
else:
slottedFields.append(fieldName)
mixinSlots = self._aggregateMixinSlots(className, baseClasses)
else:
# When adding annotated fields to slottedFields, check if name was not used in inheritance hierarchy.
annotations: Dict[str, Any] = members.get("__annotations__", {})
for fieldName, typeAnnotation in annotations.items():
# If annotated field is a ClassVar, and it has an initial value
# * copy field and initial value to classFields dictionary
# * remove field from members
if isinstance(typeAnnotation, _GenericAlias) and typeAnnotation.__origin__ is ClassVar and fieldName in members:
classFields[fieldName] = members[fieldName]
del members[fieldName]
# FIXME: search for fields without annotation
if mixin:
mixinSlots.extend(slottedFields)
members["__slotted__"] = True
members["__slots__"] = tuple()
members["__isMixin__"] = True
members["__mixinSlots__"] = tuple(mixinSlots)
elif slots:
slottedFields.extend(mixinSlots)
members["__slotted__"] = True
members["__slots__"] = tuple(slottedFields)
members["__isMixin__"] = False
members["__mixinSlots__"] = tuple()
else:
members["__slotted__"] = False
# NO __slots__
members["__isMixin__"] = False
members["__mixinSlots__"] = tuple()
return classFields, objectFields
So my question can be summarized as follows:
How to access class annotation in a metaclass creating that class?
CPython versions tested on:
3.14
Operating systems tested on:
Windows
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
从 PEP 649、链接的 CPython issue 139140 以及 annotationlib 文档开始;报告没有指出任何 CPython 源文件或测试。使用提供的 ExtendedType 模式复现该行为,然后确定是否需要受支持的类创建前注解 API 或有文档说明的解析方案。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- python
- 领域
- compilers
- Issue 类型
- 缺陷
- 难度
- 5/5
- 预计耗时
- 一周以上
- 活跃度
- 停滞
- 描述清晰度
- 需要澄清
- 新手友好度
- 25/100