Skip to content

Conversation

@bilibili12433014
Copy link

@bilibili12433014 bilibili12433014 commented Nov 26, 2025

This PR introduces an attribute-based fallback for the subscript operator (obj[key]) when a type does not implement a mapping (tp_as_mapping->mp_subscript) or sequence (tp_as_sequence->sq_item) slot.

related to: issue-141980
related to (same as issue): discuss

Problem

Currently, Python only allows obj[key] if the type provides a C-level mapping or sequence slot. This means that objects implementing __getitem__ purely through attribute resolution—such as through __getattr__, delegation, proxies, or wrapper objects—cannot support subscripting even though they logically provide the method.

A simple example using a proxy-like object:

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())
cfg["x"]    # Expected: ('view', 'x')
            # Current behavior: TypeError

The proxy delegates all attribute lookups to Config, including __getitem__, but cfg["x"] still raises TypeError because Python never attempts attribute-based resolution for subscripting.

This behavior is inconsistent with many other special methods, which already fall back to attribute resolution when a slot is not defined.

Proposed Solution

If the type does not define either:

  • tp_as_mapping->mp_subscript, or
  • tp_as_sequence->sq_item

then obj[key] will now perform an attribute lookup for __getitem__ using Python’s normal attribute resolution rules (__getattribute__, __getattr__, delegation, descriptors, etc.).

If found and callable, it is invoked as:

obj.__getitem__(key)

If not found or not callable, the existing TypeError behavior is preserved.

This is a strictly additive and backward-compatible enhancement.

Tests Added

A new test suite validates the fallback behavior using a proxy-style example:

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"))

Additional tests verify:

  • Class-defined fallback __getitem__
  • Instance-level fallback through __getattr__
  • Correct precedence (mapping/sequence slots override fallback)
  • Correct error behavior when fallback is missing or not callable

Documentation

The data model documentation (Doc/reference/datamodel.rst) is updated to reflect that:

  • When no mapping or sequence slot is present, subscripting will fall back to resolving __getitem__ as an attribute.

NEWS Entry

Added under:

Misc/NEWS.d/next/Core and Builtins/2025-11-26-23-15-10.gh-issue-141980.A8cX7Q.rst

Contents:

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.

Advantages

This change improves the expressiveness and flexibility of Python’s object model by allowing objects to participate in subscripting through normal attribute-resolution mechanisms. It becomes much easier to implement proxy, wrapper, or delegation patterns—mapping an object’s behavior to another object—without needing to define a C-level mapping slot. Any class that logically provides __getitem__ through __getattr__, or via dynamic delegation, can now support obj[key] in a natural and predictable way.

Importantly, the feature is strictly additive and does not alter the behavior of any existing code. Types that already define a mapping or sequence slot continue to use them with full precedence. Code that relies on TypeError for detecting subscriptability is unaffected, because the fallback is only considered for types that would otherwise raise the same TypeError.

Performance considerations

The performance impact is effectively negligible. In practical Python code, it is extremely rare for TypeError: 'X' object is not subscriptable to be intentionally raised or used as part of a hot-path control flow. Outside of debugging scenarios, this error is almost never intentionally triggered.

The fallback only executes in the exact situation where a TypeError would have been raised anyway, and attribute lookup is already a highly optimized operation in CPython. As a result, typical workloads will not experience any measurable slowdown, and no existing high-performance paths (such as list or dict subscripting) are affected at all.

Overall assessment

This enhancement provides a clear benefit—expanded flexibility and better alignment with Python’s dynamic attribute model—while introducing almost no downside. It preserves full backward compatibility, maintains existing precedence rules, and avoids affecting any commonly executed performance-critical code paths. As such, the change offers meaningful expressive power with minimal cost.


📚 Documentation preview 📚: https://cpython-previews--141981.org.readthedocs.build/

@python-cla-bot
Copy link

python-cla-bot bot commented Nov 26, 2025

All commit authors signed the Contributor License Agreement.

CLA signed

@bedevere-app
Copy link

bedevere-app bot commented Nov 26, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@encukou
Copy link
Member

encukou commented Nov 26, 2025

A pull request is very premature at this stage. A change of this magnitude will almost certainly need a PEP. Please go through the discussion on Discourse first.

@encukou encukou closed this Nov 26, 2025
@bilibili12433014
Copy link
Author

A pull request is very premature at this stage. A change of this magnitude will almost certainly need a PEP. Please go through the discussion on Discourse first.

@encukou

That’s fair — and to be clear, I see this more as a small piece of syntactic sugar to avoid boilerplate in specific delegation patterns, rather than a change that would redefine broader semantics. But I fully understand the need to explore it thoroughly before deciding anything.

I’ll continue the discussion on Discourse and won’t push the PR forward until there’s a clear direction. I’m happy to follow the proper process, including a PEP if the community ultimately thinks it’s appropriate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants