-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
Description
Bug report
Bug description:
The problem is initially presented in this post on python discussion forum (under topic "python help")
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:
-
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). -
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>
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