Django monkey патч модели с методом, кажется, работает правильно как метод экземпляра - я что-то упускаю?
Я использую Django 3.2, и мне приходится по-обезьяньи исправлять модель, основываясь на некоторой условной логике.
myapp/models.py
class Foo(models.Model):
# fields ...
# methods ...
pass
в консоли
from myapp.models import Foo
def something(obj):
return obj.id
# Monkey patching the class (model)
setattr(Foo, 'mysomething', something)
# Fetch an instance form db
f = Foo.objects.all().first()
f.mysomething()
# return 1 (correct)
My surprise is the fact that the something method is not accepting an argument, and instead, seems to be using the argument specified in the definition (obj) as a synonym for self - and therefore, the method is acting like an instance method.
Может ли кто-нибудь объяснить техническую причину, почему это происходит - и является ли это документированным (и, следовательно, последовательным) поведением, на которое я могу положиться в своем коде?
Это ожидаемое и последовательное поведение. Поступая так:
setattr(Foo, 'mysomething', something)
функционально эквивалентно этому (только вы делаете это позже, а не в момент определения класса):
class Foo:
def mysomething(self):
return something(self)
В Python определение метода на классе без декоратора подразумевает, что это метод экземпляра - то есть, что первым аргументом метода будет сам экземпляр. Определение метода через setattr не меняет этого факта - это просто менее очевидный способ сделать это.
Рассмотрим эту альтернативу, которая иллюстрирует суть:
# Now we wrap `something` in `staticmethod`
setattr(Foo, 'mysomething', staticmethod(something))
эквивалентно:
class Foo:
@staticmethod
def mysomething():
return something()
Теперь, поскольку он был добавлен как статический метод, ему не будет передан экземпляр класса, когда вы его вызовете, и вы получите ошибку, если попытаетесь вызвать Foo().mysomething():
TypeError: something() missing 1 required positional argument: 'obj'