Recently I faced a problem in a C-based python extension while trying to instantiate objects without calling its constructor -- which is a requirement of the extension.
The class to be used to create instances is obtained dynamically: at some point, I have an instance x whose class I wish to use to create other instances, so I store x.__class__ for later use -- let this value be klass.
At a later point, I invoke PyInstance_NewRaw(klass, PyDict_New()) and then, the problem arises. It seems that if klass is an old-style class, the result of that call is the desired new instance. However, if it is a new-style class, the result is NULL and the exception raised is:
SystemError: ../Objects/classobject.c:521: bad argument to internal function
For the record, I'm using Python version 2.7.5. Googling around, I observed no more than one other person looking for a solution (and it seemed to me he was doing a workaround, but didn't detailed it).
For the record #2: the instances the extension is creating are proxies for these same x instances -- the x.__class__ and x.__dict__'s are known, so the extension is spawning new instances based on __class__ (using the aforementioned C function) and setting the respective __dict__ to the new instance (those __dict__'s have inter-process shared-memory data). Not only is conceptually problematic to call an instance's __init__ a second time (first: it's state is already know, second: the expected behavior for ctors is that they should be called exactly once for each instance), it is also impractical, since the extension cannot figure out the arguments and their order to call the __init__() for each instance in the system. Also, changing the __init__ of each class in the system whose instances may be proxies and making them aware there is a proxy mechanism they will be subjected to is conceptually problematic (they shouldn't know about it) and impractical.
So, my question is: how to perform the same behavior of PyInstance_NewRaw regardless of the instance's class style?
The type of new-style classes isn't
instance, it's the class itself. So, thePyInstance_*methods aren't even meaningful for new-style classes.In fact, the documentation explicitly explains this:
So, you will have to write code that checks whether
klassis an old-style or new-style class and does the appropriate thing for each case. An old-style class's type isPyClass_Type, while a new-style class's type is eitherPyType_Type, or a custom metaclass.Meanwhile, there is no direct equivalent of
PyInstance_NewRawfor new-style classes. Or, rather, the direct equivalent—calling itstp_allocslot and then adding a dict—will give you a non-functional class. You could try to duplicate all the other appropriate work, but that's going to be tricky. Alternatively, you could usetp_new, but that will do the wrong thing if there's a custom__new__function in the class (or any of its bases). See the rejected patches from #5180 for some ideas.But really, what you're trying to do is probably not a good idea in the first place. Maybe if you explained why this is a requirement, and what you're trying to do, there would be a better way to do it.
If the goal is to build objects by creating a new uninitialized instance of the class, then copying over its
_dict__from an initialized prototype, there's a much easier solution that I think will work for you:__class__is a writeable attribute. So (showing it in Python; the C API is basically the same, just a lot more verbose, and I'd probably screw up the refcounting somewhere):The new object will be an instance of
cls—in particular, it will have the same class dictionary, including the MRO, metaclass, etc.This won't work if
clshas a metaclass that's required for its construction, or a custom__new__method, or__slots__… but then your design of copying over the__dict__doesn't make any sense in those cases anyway. I believe that in any case where anything could possibly work, this simple solution will work.Calling
cls.__new__seems like a good solution at first, but it actually isn't. Let me explain the background.When you do this:
(where
Foois a new-style class), it gets converted into something like this pseudocode:The problem is that, if
Fooor one of its bases has defined a__new__method, it will expect to get the arguments from the constructor call, just like an__init__method will.As you explained in your question, you don't know the constructor call arguments—in fact, that's the main reason you can't call the normal
__init__method in the first place. So, you can't call__new__either.The base implementation of
__new__accepts and ignores any arguments it's given. So, if none of your classes has a__new__override or a__metaclass__, you will happen to get away with this, because of a quirk inobject.__new__(a quirk which works differently in Python 3.x, by the way). But those are the exact same cases the previous solution can handle, and that solution works for much more obvious reason.Put another way: The previous solution depends on nobody defining
__new__because it never calls__new__. This solution depends on nobody defining__new__because it calls__new__with the wrong arguments.