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
Assigning and deleting __new__ attr on the class does not allow to create instances of this class #69917
Comments
When moving from python 2.7 to 3.5 I have found a problem with patching __new__ method on the class. It was done this way: I have created minimal scenario that ilustrates this bug cause: class X:
def __init__(self, a):
pass
def new(cls, a):
pass
X(1)
X.__new__ = new
X(1)
del X.__new__
X(1)
'''
Setting __new__ attribute and then deleting it has some side effect. |
For "del X.__new__", type_setattro in Objects/typeobject.c indirectly calls update_one_slot. This finds object.__new__ fom the base object class when it looks up __new__ on the type. Since __new__ for built-in types is special-cased to be a built-in method instead of a slot wrapper, update_one_slot takes the branch for "Py_TYPE(descr) == &PyCFunction_Type", which assigns the existing tp_new to "specific". In this case that's slot_tp_new instead of object_new due to the previous assignment of the "new" function to X.__new__. slot_tp_new looks up and calls __new__, which in this case, as noted above, is object.__new__. This built-in method calls tp_new_wrapper, which calls the wrapped tp_new function. In this case that's object_new. If the type's tp_init is object_init (i.e. not overridden) or tp_new is not object_new (i.e. overridden), then object_new raises a TypeError when called with arguments. The problem in this case is that __new__ isn't overridden anymore. It seems to me that update_one_slot needs to install the tp_new that tp_new_wrapper would call, e.g. specific = (void *)(
(PyTypeObject *)PyCFunction_GET_SELF(descr))->tp_new; In this case that's object_new. Thus after "del X.__new__", X would be restored as if __new__ was never overridden. |
Hi, I'm maintainer of flexmock [1] and some users of my library have started hitting this bug [2] - when one tries to mock __new__ and then revert this mock at the end of testcase to the original one (either by deleting the mock __new__ or replacing mock __new__ by the old one), then one gets "TypeError: object() takes no parameters". I think that solution proposed by Eryk is correct, but I don't have that much insight into Python internals to confirm that 100 %. I would appreciate this getting fixed. Thanks! [1] https://github.com/bkabrda/flexmock |
New changeset 3c9512d8ac0d by Benjamin Peterson in branch '3.5': New changeset e7062dd9085e by Benjamin Peterson in branch '2.7': New changeset a7953ee29f1c by Benjamin Peterson in branch 'default': |
This change in 2.7 seems to break things: $ cat foo.pxd
cdef class B:
cdef object b
$ cat foo.pyx
cdef class A:
pass cdef class B: class C(A, B):
def __init__(self):
B.__init__(self, 1)
C()
$ cython foo.pyx && gcc -I/usr/include/python2.7 -Wall -shared -fPIC -o foo.so foo.c
$ python -c 'import bar'
Segmentation fault C's tp_new is set to A's tp_new function, thus the b slot is never initialized to Py_None, and C's __init__ calls DECREF on a NULL pointer. Reverting changeset e7062dd9085e makes things work again, with C's tp_new being B's tp_new. |
I believe the correct behavior is actually Traceback (most recent call last):
File "<string>", line 1, in <module>
File "bar.py", line 7, in <module>
C()
TypeError: foo.A.__new__(C) is not safe, use foo.B.__new__() This is because A comes before B in the mro, and, indeed, constructing C with A.__new__ is unsafe. In fact, reordering A and B in the definition of C fixes everything. |
Well yes, but as far as I can tell that's why python used B.__new__ for C before your change, as that has a compatible layout. |
I'll have to think about how to fix this while maintaining compatiblity with obscure cases like above. |
New changeset 3ff84a3eeb6b by Benjamin Peterson in branch '2.7': |
Hi all, it seems to me that this change has been reverted not only in 2.7, but also in 3.5 (changeset: 101549:c8df1877d1bc). Benjamin, was this intentional? If so, perhaps this issue should be reopened and not marked as resolved. Thanks a lot! |
TypeError: object() takes no parameters
.Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: