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