Skip to content
Closed
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
37 changes: 26 additions & 11 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3037,9 +3037,8 @@ the class method :meth:`~object.__class_getitem__` may be called instead.
object if it is properly defined.

Presented with the :term:`expression` ``obj[x]``, the Python interpreter
follows something like the following process to decide whether
:meth:`~object.__getitem__` or :meth:`~object.__class_getitem__` should be
called::
follows a process similar to the following to determine which operation should
be used to perform the subscription::

from inspect import isclass

Expand All @@ -3048,21 +3047,37 @@ called::

class_of_obj = type(obj)

# If the class of obj defines __getitem__,
# call class_of_obj.__getitem__(obj, x)
# 1. If the class defines a mapping slot (__getitem__),
# call it directly.
if hasattr(class_of_obj, '__getitem__'):
return class_of_obj.__getitem__(obj, x)

# Else, if obj is a class and defines __class_getitem__,
# 2. If the class defines a sequence slot (sq_item)
# and the key is an integer, call sq_item.
if hasattr(class_of_obj, '__getitem__') is False:
# simplified model: CPython checks tp_as_sequence->sq_item
if isinstance(x, int) and hasattr(class_of_obj, '__len__'):
return obj.__getitem__(x)

# 3. If obj is a class and defines __class_getitem__,
# call obj.__class_getitem__(x)
elif isclass(obj) and hasattr(obj, '__class_getitem__'):
if isclass(obj) and hasattr(obj, '__class_getitem__'):
return obj.__class_getitem__(x)

# Else, raise an exception
# 4. Attribute-based fallback: if __getitem__ is provided
# by __getattribute__ or __getattr__, call it.
try:
attr_getitem = obj.__getattribute__('__getitem__')
except AttributeError:
pass
else:
raise TypeError(
f"'{class_of_obj.__name__}' object is not subscriptable"
)
if callable(attr_getitem):
return attr_getitem(x)

# 5. Otherwise, raise an exception.
raise TypeError(
f"'{class_of_obj.__name__}' object is not subscriptable"
)

In Python, all classes are themselves instances of other classes. The class of
a class is known as that class's :term:`metaclass`, and most classes have the
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -6261,5 +6261,23 @@ class IntSubclass(int):
weakref_descriptor.__get__(IntSubclass(), IntSubclass)


class TestGetItemAttributeFallback(unittest.TestCase):

def test_attribute_fallback_for_configview(self):
class ConfigView:
def __init__(self, target):
self.target = target

def __getattr__(self, name):
return getattr(self.target, name)

class Config:
def __getitem__(self, key):
return ("view", key)

cfg = ConfigView(Config())
self.assertEqual(cfg["x"], ("view", "x"))


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
The subscript operator (``obj[key]``) now falls back to an attribute-based
lookup of ``__getitem__`` when the type does not define a mapping or
sequence slot. This lookup follows normal attribute resolution rules,
including ``__getattribute__`` and ``__getattr__``. This allows classes
that provide ``__getitem__`` purely as an attribute or through dynamic
attribute handlers to support subscripting.
14 changes: 14 additions & 0 deletions Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,20 @@ PyObject_GetItem(PyObject *o, PyObject *key)
return NULL;
}

// Try attribute-based fallback: obj.__getitem__(key)
PyObject *attr_getitem = PyObject_GetAttr(o, &_Py_ID(__getitem__));
if (attr_getitem != NULL) {
if (PyCallable_Check(attr_getitem)) {
PyObject *res = PyObject_CallOneArg(attr_getitem, key);
Py_DECREF(attr_getitem);
return res;
}
Py_DECREF(attr_getitem);
}
else {
PyErr_Clear();
}

return type_error("'%.200s' object is not subscriptable", o);
}

Expand Down
Loading