Using __new__ in classes derived from Django models does not work

This is what puzzles me, but I can’t get a final answer. Using the __new__ method (or rather, the static method) in classes derived from the DJango model.

Here's how to use __new__ (since we use Django, we can assume that python version 2.x is used):

 class A(object): def __new__(self, *args, **kwargs): print ("This is A new function") return super(A, self).__new__(self, *args, **kwargs) def __init__(self): print ("This is A init function") 

Activating an object from the specified class works as expected. Now, when you try something similar on classes derived from Django models, something unexpected happens:

 class Test(models.Model): def __new__(self, *args, **kwargs): return super(Test, self).__new__(self, *args, **kwargs) 

Initiating an object from the specified class results in an error: TypeError: unbound method __new__() must be called with Test instance as first argument (got ModelBase instance instead) .

I can't understand why this is happening (although I know that class magic happens behind the scenes from outside of Django).

Any answers would be appreciated.

+6
source share
1 answer

__new__ does not receive an instance as its first parameter. How can this happen when (a) it is a static method, as you noticed, and (b) its task is to create an instance and return it! The first parameter __new__ conditionally called cls , since it is a class.

Because of what, the error message you specify is very strange; this is usually an error message that you will get when you call an unrelated method (i.e. what you get by referring to ClassName.methodName ) with something other than an instance of this class, as a self parameter. However, staticmethods (including __new__ ) do not become unrelated methods, they are just simple functions that are class attributes:

 >>> class Foo(object): def __new__(cls, *args, **kwargs): return object.__new__(cls) def method(self): pass >>> class Bar(object): pass >>> Foo.method <unbound method Foo.method> >>> Foo.__new__ <function __new__ at 0x0000000002DB1C88> >>> Foo.method(Bar()) Traceback (most recent call last): File "<pyshell#36>", line 1, in <module> Foo.method(Bar()) TypeError: unbound method method() must be called with Foo instance as first argument (got Bar instance instead) >>> Foo.__new__(Bar) <__main__.Bar object at 0x0000000002DB4F28> 

From this, you can verify that __new__ should never be an unrelated method. In addition (as opposed to the usual method), you do not care that you agree on what you transmit; I managed to create an instance of Bar by calling Foo.__new__ , because both he and Bar.__new__ were ultimately implemented the same way (putting aside all the actual work to object.__new__ ).

However, this led me to a brief summary of the source code for Django itself. The Django Model class has a metaclass, ModelBase . This is quite complicated, and I did not understand what he was doing completely, but I noticed something very interesting.

ModelBase.__new__ (metaclass __new__ , which is the function that creates the class at the end of your class block) calls its super __new__ without passing its class dictionary . Instead, it passes the dictionary only with the __module__ attribute __module__ . Then, after doing a whole bunch of processing, he does the following:

  # Add all attributes to the class. for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) 

( attrs is a dictionary containing all of your definitions in your class block, including your __new__ function; add_to_class is a metaclass method that is basically just setattr ).

Now I'm 99% sure that the problem is here because __new__ is a strange implicitly static method. Therefore, unlike any other static method, you did not apply the staticmethod decorator to it. Python (at some level) simply recognizes the __new__ method and treats it as a static method, not a regular method [1]. But I bet that this only happens when __new__ defined in a class block, and not when it is set using setattr .

Thus, your __new__ , which should be a static method, but not processed by the staticmethod staticmethod , is converted to a regular instance method. Then, when Python calls it passing the class Test , according to the protocol for creating a regular instance, it complains that it does not receive an instance of Test .

If all this is correct, then:

  • This is not so because Django is a bit broken, but only in that it does not take into account Python inconsistency about __new__ , static in mind.
  • Perhaps you could do this work by applying @staticmethod to your __new__ method, although you don't need to.

[1] I think this is Python's historical quirk, since __new__ was introduced before the staticmethod decorator, but __new__ could not take an instance, since there wasn’t to call it.

+11
source

Source: https://habr.com/ru/post/907408/


All Articles