python / python/cpython

`__annotations__` was removed from 'members' in `type.__new__` without alternative in Python 3.14

Đang mở
#139,186 3 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

3.14 docs topic-typing
Ngôn ngữ chính
Python
Star
77.2k
Fork
35.9k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

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:

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:

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

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với PEP 649, CPython issue 139140 được liên kết và tài liệu annotationlib; báo cáo không nêu tên tệp mã nguồn hay bài kiểm thử nào của CPython. Tái hiện hành vi bằng mẫu ExtendedType được cung cấp, sau đó xác định xem có cần một annotation API được hỗ trợ trước khi tạo lớp hay một cách phân giải được ghi chép hay không.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
compilers
Loại issue
Lỗi
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Cần làm rõ
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.