Home > Mobile >  Django monkey patching model with method seems to work correctly as instance method - am I missing s
Django monkey patching model with method seems to work correctly as instance method - am I missing s

Time:04-25

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'
  • Related