Given a metaclass, or simpler, type()
, the last argument stands as the class dict, the one which is in charge with class variables. I was wondering, is there a way to set instance variables from within a metaclass?
The reason I am using a metaclass is that I need to collect some variables defined on class creation and process them. But then, the result of this processing needs to be attached to each instance of the class, not to the class itself, since the result will be a list, which will differ from one instance of the class to another.
I will provide an example so it's easier to follow me.
I have a Model
class, defined as follows:
class Model(object):
__metaclass__ = ModelType
has_many = None
def __init__(self, **kwargs):
self._fields = kwargs
The has_many
class variable will be filled in at class definition by whatever model someone feels the need. In the __init__()
, I simply assign the keywords provided at model instantiation to instance variable _fields
.
I then have the ModelType
metaclass:
class ModelType(type):
def __new__(cls, name, bases, dct):
if 'has_many' in dct:
dct['_restricted_fields'].append([has-many-model-name])
What I need to do here is pretty straight-forward. I check if someone defined the has_many
class variable on his custom model class, then add the variable's content to a list of restricted fields.
And now comes the important part. When instantiating a model, I need _fields
to be composed of both the keyword arguments used for instantiating the model and the restricted fields, as processed in the metaclass. This would be pretty easy if _fields
were a class variable in the Model
class, but since it is an instance variable, I don't know how can I also add the restricted fields to it.
These being said, is there any way of achieving this? Or is there a better way of handling this? (I thought of using only a metaclass and set a _restricted_fields
class variable on the model, then use this variable within the class __init__()
to add the restricted fields to the normal fields, but very soon the model class will be cluttered of code which, in my opinion, should rest in another part of the code).
Using a metaclass for this is not the correct approach here. A metaclass modifies the class-creation behavior not the instance creation behavior. You should use the __init__
or the __new__
function to modify instance creation behavior. Wanting to use a metaclass for such things is using a hammer instead of a screwdriver to put a screw in a wall. ;-)
I'd suggest you use __new__
to achieve what you want. From the Python docs:
__new__()
is intended mainly to allow subclasses of immutable types (likeint
,str
, ortuple
) to customize instance creation. It is also commonly overridden in custom metaclasses in order to customize class creation.
class MetaModel(type):
def __new__(cls, name, bases, attrs):
attrs['_restricted_fields'] = [attrs.get('has_many')]
return type.__new__(cls, name, bases, attrs)
class Model(object):
__metaclass__ = MetaModel
has_many = None
def __new__(cls, *args, **kwargs):
instance = object.__new__(cls, *args, **kwargs)
instance.instance_var = ['spam']
return instance
class SubModel(Model):
has_many = True
def __init__(self):
# No super call here.
self.attr = 'eggs'
s = SubModel()
assert s._restricted_fields == [True] # Added by MetaModel
assert s.instance_var == ['spam'] # Added by Model.__new__
assert s.attr == 'eggs' # Added by __init__
# instance_var is added per instance.
assert SubModel().instance_var is not SubModel().instance_var
The MetaModel
is responsible for creating Model
classes. It adds a _restricted_fields
class variable to any Model
class created by it (the value is a list containing the has_many
class variable).
The Model
class defines a default has_many
class variable. It also modifies the instance creation behavior, it adds an instance_var
attribute to each created instance.
The SubModel
is created by a user of your code. It defines an __init__
function to modify instance creation. Note that it does not call any super-class function, this is not a necessity. The __init__
adds an attr
attribute to each SubClass
instance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With