I am using Django 3.2 and I am having to monkey patch a model, based on some conditional logic.
myapp/models.py
class Foo(models.Model):
# fields ...
# methods ...
pass
in console
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.
Can anyone explain the technical reason why this is happening - and is this documented (and therefore consistent) behaviour I can rely on in my code?
CodePudding user response:
This is expected and consistent behaviour. Doing this:
setattr(Foo, 'mysomething', something)
is functionally equivalent to doing this (just that you do it later, rather than when the class is defined):
class Foo:
def mysomething(self):
return something(self)
In Python, defining a method on a class without any decorator implies it is an instance method - i.e, that the first argument to the method will be the instance itself. Defining a method via setattr
does not change this fact - it's just a less obvious way of doing it.
Consider this alternative, which illustrates the point:
# Now we wrap `something` in `staticmethod`
setattr(Foo, 'mysomething', staticmethod(something))
is equivalent to:
class Foo:
@staticmethod
def mysomething():
return something()
Now, because it's been added as a static method, it will not be passed an instance of the class when you call it, and you'll get an error if you try calling Foo().mysomething()
:
TypeError: something() missing 1 required positional argument: 'obj'