Использование __new__ на классах, полученных из моделей Джанго, не работает - PullRequest
6 голосов
/ 01 февраля 2012

Это то, что озадачивает меня, но я не могу получить окончательный ответ.Использование метода __new__ (или, точнее, статического метода) в классах, полученных из модели DJango.

Вот как __new__ должно идеально использоваться (так как мы используем Django, мы можем предположить, что версия 2.x из python используется):

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

  def __init__(self):
    print ("This is A's init function")

Создание объекта из вышеприведенного класса работает как положено.Теперь, когда кто-то пытается что-то подобное на классах, производных от моделей Django, происходит нечто неожиданное:

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

Создание объекта из вышеуказанного класса приводит к этой ошибке: TypeError: unbound method __new__() must be called with Test instance as first argument (got ModelBase instance instead).

Я не могу понять, почему это происходит (хотя я знаю, что некоторая классовая магия происходит за кулисами из-за структуры Django).

Любые ответы будут оценены.

1 Ответ

11 голосов
/ 02 февраля 2012

__new__ не получает экземпляр в качестве первого параметра.Как это могло случиться, когда (а) это статический метод, как вы заметили, и (б) его задача - создать экземпляр и вернуть его!Первый параметр __new__ условно называется cls, так как это класс.

, что делает сообщение об ошибке, которое вы цитируете, очень странным;обычно это сообщение об ошибке, которое вы получаете, когда вызываете несвязанный метод (то есть то, что вы получаете, получая доступ к ClassName.methodName) с чем-то отличным от экземпляра этого класса в качестве параметра self.Однако статические методы (включая __new__) не становятся несвязанными методами, это просто простые функции, которые оказываются атрибутами класса:

>>> 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>

Из этого видно, что __new__ долженникогда не будьте свободным методом.Кроме того (в отличие от обычного метода) это не заботится о том, что вы последовательны в том, что вы передаете;Мне действительно удалось создать экземпляр Bar, вызвав Foo.__new__, потому что и он, и Bar.__new__ в конечном итоге реализованы одинаково (откладывая всю фактическую работу до object.__new__).

Однако этовкратце заставил меня взглянуть на исходный код самого Django.У класса Model Джанго есть метакласс ModelBase.Это довольно сложно, и я не понял, что он делает полностью, но я заметил кое-что очень интересное.

ModelBase.__new__ (метакласс __new__, который являетсяфункция, которая создает class в конце вашего блока классов), вызывает его супер __new__ , не передавая ему ваш словарь классов .Вместо этого он передает словарь только с установленным атрибутом __module__.Затем, выполнив целую кучу обработки, он выполняет следующие действия:

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

(attrs - это словарь, содержащий все ваши определения в вашем блоке классов, включая вашу функцию __new__; add_to_classэто метод метакласса, который в основном просто setattr).

Теперь я на 99% уверен, что проблема здесь, потому что __new__ - странный неявно статический метод.Таким образом, в отличие от любого другого статического метода, вы не применили к нему декоратор staticmethod.Python (на некотором уровне) просто распознает метод __new__ и обрабатывает его как статический метод, а не как обычный метод [1].Но держу пари, что это происходит только тогда, когда вы определяете __new__ в блоке класса, а не когда вы устанавливаете его с помощью setattr.

Так что ваш __new__, который должен быть статическим методом, но не имеетt обработан декоратором staticmethod, преобразуется в обычный метод экземпляра.Затем, когда Python вызывает его, передавая класс Test, в соответствии с обычным протоколом создания экземпляра, он жалуется, что не получает экземпляр Test.

Если все правильно,затем:

  1. Это терпит неудачу, потому что Django немного сломан, но только потому, что ему не удается учесть несогласованность Python по поводу статичности __new__.
  2. Возможно, вы могли бы сделать этоработать, применяя @staticmethod к вашему __new__ методу, даже если вам не нужно это делать.

[1] Я считаю, что это историческая особенность Python, поскольку __new__ был представлен до staticmethod декоратора, но __new__ не может взять экземпляр, так как нет экземпляра для его вызова.

...