Skip to content

gh-122634: Deprecate __class_getitem__ on metaclasses #122743

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ asyncio
Deprecated
==========

* Currently, subscripting a class object with a metaclass
that defines the :meth:`~object.__class_getitem__` method
would invoke the metaclass's method. This behavior is now deprecated
and will be removed in Python 3.16.
To make instances of a metaclass subscriptable,
define a :meth:`~object.__getitem__` method on the metaclass.

* :mod:`builtins`:
Passing a complex number as the *real* or *imag* argument in the
:func:`complex` constructor is now deprecated; it should only be passed
Expand Down
31 changes: 31 additions & 0 deletions Lib/test/test_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,37 @@ def __init__(self, obj):
Type(i)
self.assertEqual(calls, 100)

def testClassGetItemIsDeprecatedOnMetaclasses(self):
import warnings

class Meta(type):
def __class_getitem__(self, arg):
return 1
class NoMethod(metaclass=Meta): ...
class HasOwnMethod(metaclass=Meta):
def __class_getitem__(self, arg):
return 2

with self.assertWarnsRegex(
DeprecationWarning,
"Accessing __class_getitem__ on a metaclass",
):
res = NoMethod[int]
self.assertEqual(res, 1)

with warnings.catch_warnings(record=True) as w:
self.assertEqual(HasOwnMethod[int], 2)
self.assertEqual(len(w), 0)

class CorrectMeta(type):
def __getitem__(self, arg):
return 3
class NoClassMethod(metaclass=CorrectMeta): ...

with warnings.catch_warnings(record=True) as w:
self.assertEqual(NoClassMethod[int], 3)
self.assertEqual(len(w), 0)


from _testinternalcapi import has_inline_values

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Currently, subscripting a class object with a metaclass
that defines the :meth:`~object.__class_getitem__` method
would invoke the metaclass's method. This behavior is now deprecated
and will be removed in Python 3.16.
To make instances of a metaclass subscriptable,
define a :meth:`~object.__getitem__` method on the metaclass.
10 changes: 10 additions & 0 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -5616,6 +5616,16 @@ _Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int * suppress_missin
res = meta_get(meta_attribute, (PyObject *)type,
(PyObject *)metatype);
Py_DECREF(meta_attribute);
if (res && PyUnicode_EqualToUTF8(name, "__class_getitem__")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it so the deprecation gets triggered if you use regular attribute access to access the attribute, but I don't think that's right:

>>> class M(type):
...     def __class_getitem__(*args):
...         return "CGI"
...         
>>> class A(metaclass=M): pass
... 
>>> A.__class_getitem__
<python-input-7>:1: DeprecationWarning: Accessing __class_getitem__ on a metaclass is deprecated since 3.14 and will be removed in 3.16. Instead, define a regular __getitem__ method on a metaclass, if you need this behavior.
  A.__class_getitem__
<bound method M.__class_getitem__ of <class '__main__.M'>>

Instead, the deprecation should trigger only if the lookup is triggered by the subscript operator, like in this example:

>>> A["x"]
<python-input-8>:1: DeprecationWarning: Accessing __class_getitem__ on a metaclass is deprecated since 3.14 and will be removed in 3.16. Instead, define a regular __getitem__ method on a metaclass, if you need this behavior.
  A["x"]
'CGI'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that there's any clean way to do that. We don't have any info about that in _Py_type_getattro_impl (nor in its public version).

Passing this info would require either a global state or a new parameter in several functions (some of which are public).

Maybe I am missing something?

I don't think that .__class_getitem__ access should really bother us, because:

  1. Users should not access magic method directly
  2. Warning is valid in some cases

if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Accessing __class_getitem__ on a metaclass is deprecated "
"since 3.14 and will be removed in 3.16. "
"Instead, define a regular __getitem__ method on a metaclass, "
"if you need this behavior.",
1) < 0) {
return NULL;
}
}
return res;
}

Expand Down
Loading