Skip to content

All exceptions are swallowed when looking up __set_name__ on an object in a class namespace during construction of that class #112453

Closed as not planned

Description

@AlexWaygood

Bug report

Bug description:

During construction of a class Foo, all objects in the class dictionary are checked to see if they have a __set_name__ attribute. If an object in the dictionary does have a __set_name__ attribute, it is called as part of the construction of Foo:

>>> class Vanilla:
...     def __set_name__(self, owner, name):
...         print('set_name called')
...
>>> class Foo:
...     attr = Vanilla()
...
set_name called

But what if an object in a class namespace is an instance of class that has a metaclass that raises a non-AttributeError exception if __set_name__ is looked up on the class object? In this case, the exception is silently ignored when __set_name__ is looked up:

>>> class Meta(type):
...     def __getattribute__(self, attr):
...         if attr == "__set_name__":
...             raise SystemExit('NO')
...         return object.__getattribute__(self, attr)
...
>>> class Problematic(metaclass=Meta): pass
...
>>> try:
...     Problematic.__set_name__
... except SystemExit:
...     print('exception was raised')
...
exception was raised
>>> class Bar:
...     attr = Problematic()
...
>>>

__set_name__, like all dunders, is looked up on the Problematic class object itself, rather than the instance of Problematic found in the class dictionary of Foo, so SystemExit should be raised during the creation of the Bar class when the interpreter tries to determine whether or not the Problematic class has a __set_name__ attribute. Instead, however, the SystemExit is silently swallowed.

I think the issue lies in this section of code here:

cpython/Objects/typeobject.c

Lines 9989 to 9992 in d44ee42

if (set_name == NULL) {
if (PyErr_Occurred()) {
goto error;
}

I'm interested in working on this.

CPython versions tested on:

3.8, CPython main branch

Operating systems tested on:

Windows

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    on Nov 27, 2023
  2. self-assigned this
    on Nov 27, 2023
  3. AlexWaygood commented on Nov 27, 2023

    @AlexWaygood
    MemberAuthor

    Or is the issue with the way Python looks up special names in general? It seems like there may be lots of other possible examples of this bug:

    >>> class Meta(type):
    ...     def __getattribute__(self, attr):
    ...         if attr in {"__complex__", "__enter__", "__iter__"}:
    ...             raise SystemExit()
    ...         return object.__getattribute__(self, attr)
    ...
    >>> class Foo(metaclass=Meta): pass
    ...
    >>> with Foo(): pass
    ...
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
        with Foo(): pass
    TypeError: 'Foo' object does not support the context manager protocol
    >>> complex(Foo())
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
        complex(Foo())
    TypeError: complex() first argument must be a string or a number, not 'Foo'
    >>> iter(Foo())
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
        iter(Foo())
    TypeError: 'Foo' object is not iterable
    >>> Foo.__iter__  # raises SystemExit...

    @serhiy-storchaka, thoughts?

  4. removed their assignment
    on Nov 27, 2023
  5. AlexWaygood commented on Nov 27, 2023

    @AlexWaygood
    MemberAuthor

    In fact, it seems like this behaviour is documented in the data model: https://docs.python.org/3/reference/datamodel.html#special-method-lookup

    In addition to bypassing any instance attributes in the interest of correctness, implicit special method lookup generally also bypasses the __getattribute__() method even of the object’s metaclass:

    >>> class Meta(type):
    ...     def __getattribute__(*args):
    ...         print("Metaclass getattribute invoked")
    ...         return type.__getattribute__(*args)
    ... 
    >>> class C(object, metaclass=Meta):
    ...     def __len__(self):
    ...         return 10
    ...     def __getattribute__(*args):
    ...         print("Class getattribute invoked")
    ...         return object.__getattribute__(*args)
    ... 
    >>> c = C()
    >>> c.__len__()                 # Explicit lookup via instance
    Class getattribute invoked
    10
    >>> type(c).__len__(c)          # Explicit lookup via type
    Metaclass getattribute invoked
    10
    >>> len(c)                      # Implicit lookup
    10

    Bypassing the __getattribute__() machinery in this fashion provides significant scope for speed optimisations within the interpreter, at the cost of some flexibility in the handling of special methods (the special method must be set on the class object itself in order to be consistently invoked by the interpreter).

    So perhaps this is not a bug at all, @serhiy-storchaka?

  6. added
    pendingThe issue will be closed if no feedback is provided
    on Nov 27, 2023
  7. serhiy-storchaka commented on Nov 27, 2023

    @serhiy-storchaka
    Member

    If it simply bypasses the __getattribute__() machinery, it is not a bug.

    But there is an old bug in _PyType_Lookup():

    cpython/Objects/typeobject.c

    Lines 4776 to 4783 in d44ee42

    /* It's not ideal to clear the error condition,
    but this function is documented as not setting
    an exception, and I don't want to change that.
    E.g., when PyType_Ready() can't proceed, it won't
    set the "ready" flag, so future attempts to ready
    the same type will call it again -- hopefully
    in a context that propagates the exception out.
    */

    There should be an existing old issue for this. I once tried to solve it (or maybe similar issue in other place of this file), but I was not able to prove that the solution was correct, so it was not applied.

    If errors are ignored, it is because of technical limitations and the design flaws. It should not be reproduced in Python.

  8. AlexWaygood commented on Nov 27, 2023

    @AlexWaygood
    MemberAuthor

    There should be an existing old issue for this.

    Possibly #75646 / #3616?

    If errors are ignored, it is because of technical limitations and the design flaws. It should not be reproduced in Python.

    I see, thanks. So this issue should be closed, correct?

  9. serhiy-storchaka commented on Nov 27, 2023

    @serhiy-storchaka
    Member

    It looks like that.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)pendingThe issue will be closed if no feedback is providedtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions