python / python/cpython

__class_getitem__ Unexpectedly Falls Back to the Metaclass

Đang mở
#122,634 7 bình luận 2 reaction 1 người được giao Xem trên GitHub

@sobolevn đang làm issue này rồi.

Từ ngày 6/8/2024.

3.10 3.11 3.12 3.13 3.14 3.7 (EOL) 3.8 (EOL) 3.9 (EOL) interpreter-core topic-typing type-bug
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:

A major point of __class_getitem__ (PEP 560) is to avoid metaclasses. This implies that the metaclass should never be involved in the mechanism. However, currently (and since the feature landed) we actually do fall back to the metaclass:

(example)
class Spam:
    def __class_getitem__(cls, key):
        return f'spam got {key!r}'


class Meta(type):
    def __class_getitem__(cls, key):
        return f'meta got {key!r}'


class Eggs(metaclass=Meta):
    def __class_getitem__(cls, key):
        return f'eggs got {key!r}'


class Ham(metaclass=Meta):
    pass

print(Spam[10])
print(Eggs[10])
print(Ham[10])

Output:

spam got 10
eggs got 10
meta got 10

I was expecting the last one to raise TypeError: type 'object' is not subscriptable, since Ham does not implement __class_getitem__. The actual outcome is surprising because the metaclass can already define __getitem__. Falling back to the metaclass __class_getitem__ doesn't make much sense and can be confusing. The metaclass __class_getitem__ should only be used when subscripting the metaclass, not its instances.

The PEP doesn't really address the question of a metaclass that defines __class_getiem__ [^1] (nor does the documentation as far as I noticed). Overall, it seems like this was simply not noticed nor considered. It's certainly not an obvious case. Regardless, I think we should fix it.

[^1]: The PEP does say Note that this method is used as a fallback, so if a metaclass defines __getitem__, then that will have the priority. but that's specifically about falling back to meta.__getitem__.

The fix would involve skipping the metaclass part. That would be in the implementation for the subscript syntax (PyObject_GetItem() in Objects/abstract.c). There's a part where it specially handles the case where a class is being subscripted. In that case it looks up __class_getitem__ on the class. (See gh-4732.) However, currently it uses PyObject_GetOptionalAttr(), which involves descriptors and the metaclass (the object's type) and the type's __mro__. Again, the fix is to skip the metaclass part.

FWIW, __init_subclass__ is fairly similar, but it doesn't fall back to the metaclass. Instead, it effectively does getattr(super(cls), '__init_subclass__'). (See type_new_init_subclass() in Objects/typeobject.c.) We should probably do something similar in PyObject_GetItem().

CC @ilevkivskyi @gvanrossum

CPython versions tested on:

CPython main branch

Operating systems tested on:

No response

Linked PRs
  • gh-122743

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.

Đánh giá

Issue này chưa được đánh giá.

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.