TL;DR:
- any reachable variable from
MyModel
or any higher scope's variable up from the class named Meta
, that's already defined
- 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:
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:
# the original ModelBase from class Model(metaclass=ModelBase)
class Meta(type):
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):
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 are locals()
+ anything defined within the class scope as a variable + anything in a higher scope be it from a nested class, module or others:
Edit:
So for the class variable it was a screw up, this is the correct explanation. For your case it'll be either a function or metaclass patching because we're dealing here with finding the beginning of a "chicken & egg" problem. And that we can do by either looking from above - introducing a function scope and setting values prior the defining of the upper class is finished -, or looking from inside of the creation process (metaclass).
We can't use a class variable, because we're still in the process of defining a class. We also can't use the __init__
to reference the class (or Meta
nested class) to inject a value there, because we're already in a situation where the metaclass' __new__()
method has already been executed, thus anything you set there may be used in your case, but won't be present while creating the MyModel
class.
class MyModel(Model):
def __init__(self, name):
MyModel.name = name # MyModel not defined yet
# but we can use self.__class__ in the post-poned execution
# once MyModel is defined (case when calling __init__())
# we haven't even started to define the Meta nested class
# starting to define Meta nested class
class Meta:
# MyModel not defined yet because the code block
# of MyModel class isn't finished yet
table = MyModel.name
# can't reference because it doesn't exist yet,
# same applies for Meta, the only thing you have now is
# the __qualname__ string
But!
from pprint import pprint
class RealMetaClass(type):
def __new__(cls, *_, **__):
# create a new class - "Main" because it uses "metaclass" kwarg
new_class = super().__new__(cls, *_, **__)
# once we have the "Main" class, we can reference it the same way
# like *after* we define it the normal way
nested_meta = getattr(new_class, "NestedClassNamedMeta")
# ~= pprint(getattr(Main, "NestedClassNamedMeta"))
pprint(("in metaclass", nested_meta))
pprint(("in metaclass", dir(nested_meta)))
pprint(("in metaclass", vars(nested_meta)))
return new_class
class CustomMetaClass(RealMetaClass):
def __new__(cls, *_, **__):
new_class = super().__new__(cls, *_, **__)
nested_meta = getattr(new_class, "NestedClassNamedMeta")
# set a new class variable without affecting previous __new__() call
new_class.custom_thing = getattr(nested_meta, "custom_thing", None)
# do my stuff with custom attribute, that's not handled by Django
return new_class
class Main(metaclass=RealMetaClass):
pprint("defining Main class")
def __init__(self, name):
pprint(("in instance", self.__class__.NestedClassNamedMeta.name))
# works, because it's called after the Main class block
# is finished and executed i.e. defined
self.__class__.NestedClassNamedMeta.new_value = "hello"
class NestedClassNamedMeta:
name = "John"
custom_thing = "custom"
class CustomMain(Main, metaclass=CustomMetaClass):
class NestedClassNamedMeta:
name = "John"
custom_thing = "custom"
instance = Main("Fred")
# custom_thing is ignored, not added to instance.__class__
pprint(("after init", vars(instance.__class__), vars(instance.NestedClassNamedMeta)))
instance = CustomMain("Fred")
# custom_thing is processed, added to instance.__class__
pprint(("after init", vars(instance.__class__), vars(instance.NestedClassNamedMeta)))