Home > Software design >  how to pass arguments into a nested class in python
how to pass arguments into a nested class in python

Time:09-25

I have the following model:

def get_my_model(name1, name2):
    class MyModel(Model):
       class Meta: 
             table1 = name1
             table2 = name2
    return MyModel
  
     

I am trying to write this without the function and want to pass the arguments name1 and name2 directly to the Meta class but I'm not sure how. I tried using constructors but it somehow doesn't work.

class MyModel(Model):
       def __init__(self, name1, name2):
             self.name1 = name1
             self.name2 = name2

       class Meta: 
             table1 = self.name1
             table2 = self.name2 

I also tried setting the constructor on the meta class but didn't work either. Anyone an idea?

CodePudding user response:

TL;DR:

  • class (MyModel) or any higher scope's variable up from the class named Meta
  • modify ModelBase's metaclass (patch the file or inherit from)

It's not a metaclass! It's just a class in other class's scope named Meta. What's happening is that Python has separate contexts/scopes once the execution environment starts building:

  • interpreter
  • global (the original namespace you're given once you start the interpreter, globals())

then the nested ones within the global namespace:

  • module/file
  • class
  • function
  • perhaps some other(s)

You can't pass a parameter to the Meta class because it's just declared in there. It's not called. Parallel to this would be passing a parameter to a class declaration from the module scope:

# module.py
class MyClass:
    value = <how?>

Once you find the place where it's called, then you can inject the parameters by modifying the caller function.

class Main:
    class Meta:
        def __init__(self, *args, **kwargs):
            print("Meta", args, kwargs)

    def __init__(self, name):
        Main.Meta(name)

print(Main(123))

If I don't explicitly call Main.Meta(), the Meta class in this example won't be instantiated.

In case of Django, the Meta class is pulled via getattr() for the model class here, therefore you need to target ModelBase.__new__() with super()/copy-paste to modify the function so it accepts custom arguments, or, pass them as just class variables (how it's mostly done in Django / DRF).

class Main:
    class Meta:
        class_var = 123

Judging from the implementation of Model class in Django you might be able to swap the metaclass value, but I'm not sure about the inheritance as the __new__() of a metaclass is executed when you declare the class that uses it, thus inheriting like this breaks:

# the original ModelBase from class Model(metaclass=ModelBase)
class Meta:
    def __new__(cls, *_, **__):
        print("Hello")
        return cls

class MyMeta(Meta):
    def __new__(cls, *_, **__):
        print("Hi")
        # implement your stuff here or copy-paste   patch
        return cls

class Model(metaclass=Meta):
    pass

class CustomModel(metaclass=MyMeta):
    pass

class CustomModelWithInheritance(Model, metaclass=MyMeta):  # boom
    pass

For metaclasses check:

Regarding self: The naming itself doesn't matter, nor will work where you use it, because the self is just an implicitly passed instance of a class into a method (a function with a reference to a class instance):

class MyClass:
    def func(self_or_other_name):
        print(self_or_other_name)
MyClass().func()

The same way behaves a cls argument in a __new__() method when creating a class within a metaclass i.e. it's a reference to the metaclass instance (a class declaration in a namespace), for which the "description" is the metaclass that creates it.

cls = type("MyClass", (), {})  # create an instance of "type"
cls  # <class '__main__.MyClass'>
# "cls" is class and an instance at the same time

The only "special" variable you can use to refer to the class scope is the class' name anything defined within the class scope as a variable anything in a higher scope be it from a nested class, module or others:

class MyClass:
    print(MyClass)
    name = 123
    print(name)
MyClass()

so for this case it'll be:

class MyModel(Model):
       def __init__(self, name1, name2):
             MyModel.name1 = name1  # class variable
             MyModel.name2 = name2

       class Meta: 
             table1 = MyModel.name1  # class variable
             table2 = MyModel.name2 
  • Related