Skip to content
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

Unexpected behavior accessing attributes provided by tp_getset #126752

Closed
zhangyx1998 opened this issue Nov 12, 2024 · 1 comment
Closed

Unexpected behavior accessing attributes provided by tp_getset #126752

zhangyx1998 opened this issue Nov 12, 2024 · 1 comment
Labels
type-bug An unexpected behavior, bug, or error

Comments

@zhangyx1998
Copy link
Contributor

zhangyx1998 commented Nov 12, 2024

Bug report

Bug description:

The problem is initially presented in this post on python discussion forum (under topic "python help")

========== BEGIN Mirror of original post ==========

Background:

In my latest commit 3375c80, I have a builtin function expose that creates a DeferExprExposed object which provides access to otherwise hidden attributes on DeferExpr objects (link to code).

Observed Problem:

The attributes are provided through PyTypeObject.tp_getset hooks (link to code). However, I observed some really wired behavior with them:

  1. First attribute access fails, 2nd time OK (and so on):

    >>> x => 1 # Creates a DeferExpr object `x`
    >>> expose(x).callable # 1st try fails
    AttributeError: 'DeferExprExposed' object has no attribute 'callable'
    >>> expose(x).callable # 2nd try works
    <function <lambda> at 0x10395a560>
    

    Note that expose(x) returns a new DeferExprExposed object each time. It does not reuse the same exposed object. (i.e. two tries happens on two different objects).

  2. Accessing attribute on the same object fails on 1st try, but succeed otherwise:

    >>> x => 1 # Creates a DeferExpr object `x`
    >>> e = expose(x)
    >>> e.callable # 1st try fails
    AttributeError: 'DeferExprExposed' object has no attribute 'callable'
    >>> e.callable # 2nd try works
    <function <lambda> at 0x100df2a30>
=========== END Mirror of original post ===========

Expected behavior

Attribute access should alway success.

Reproducible Example

Commit 958f53e @ zhangyx1998/cpython can be used to reproduce this problem.

In commit 3e3b7d4, tp_getset was replaced by tp_setattr and tp_getattr, and the problem no longer exists. This indicates the problem is unlikely to be caused by internal logic of tp_getset hooks.

Possible Solution

In C API PyObject_GetAttr(), add a for-loop to traverse tp_getset and match listed attributes with given name. Traversal of tp_getset should be the last resort (i.e. only executed when tp_getattr and tp_getattro are both NULL).

This logic is not found anywhere in the current code.

P.S. I do not really understand why the second access to the same attribute "magically" works. According to the logic shown in linked code, it should always raise the same attribute error.

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

@zhangyx1998 zhangyx1998 added the type-bug An unexpected behavior, bug, or error label Nov 12, 2024
@zhangyx1998
Copy link
Contributor Author

zhangyx1998 commented Nov 12, 2024

Self triaged: PyType_Ready() must be called before instantiating an object of that type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

1 participant