I'm trying to convert class variables into instance variables on runtime, similar to what goes on in the Django ORM where the class variables of various types are converted into instance variables of the same types on runtime.
I'm using a metaclass to collect the class variables of certain types into a list and then setting them as instance variables on instantiation. I've tried deleting the original class variables and also keeping them and I'm getting different results in both cases, both undesirable.
- I created a simple Field object which uses the _get_ and _set_ descriptors to convert values into types. A simple implementation of Field is the Textfield which unicodes the value on _get_.
- On class creation the metaclass collects all attributes of type 'Field' into a list
- The field list is then set to the class's as _meta['fields'].
- When the class is instantiated into an object those _meta['fields'] are then setattr into the object. In one use case the original class var is deleted before hand.
- I then test by creating two objects, setting the textfield attribute of one to some text, expecting the _get_ and _set_ to be called and both having different non conflicting values.
When the class variable is deleted, setting the Field value does not actually call _set or _get_ and the field simply changes type to a str. When I don't delete the class variable, the two objects instantiated share the values between them.
I've diluted my code into the code below which can be saved into a file and run with python test.py
or imported into a python shell. Setting DELETE_CLASS_VAR to True will delete the class variables (to account for both test cases).
Obviously I'm missing something here. Failing to get this to work I'd use regular instance variable throughout but I quite like the Django model (and I have gone through the Django code without success) where the class variables set on a model then become its instance variables with some degree of type safety and specific methods set into them.
Thanks!
# Set to True to delete class variables
DELETE_CLASS_VAR = False
class Field(object):
def __init__(self, *args, **kwargs):
self.value = None
self._args = args
self._kwargs = kwargs
def __get__(self, instance, owner):
print "__get__ called:", instance
return self.to_python()
def __set__(self, instance, value):
print "__set__ called:", instance, value
self.value = self.from_python(value)
def to_python(self):
return self.value
def from_python(self, value):
return value
class TextField(Field):
def to_python(self):
return unicode(self.value)
class ModelMetaClass(type):
def __new__(cls, name, bases, attrs):
print "Creating new class: %s" % name
obj = super(ModelMetaClass, cls).__new__(cls, name, bases, attrs)
print "class=", cls
_keys = attrs.keys()
_fields = []
for key in _keys:
if isinstance(attrs[key], Field):
_field = attrs.pop(key)
_fields.append( {'name':key, 'value': _field } )
setattr(obj, '_meta', {'fields': _fields} )
print "-"*80
return obj
class Model(object):
__metaclass__ = ModelMetaClass
def __init__(self):
print "ROOT MODEL INIT", self._meta
print "-"*80
super(Model, self).__init__()
_metafields = self._meta.get('fields',[])
for _field in _metafields:
_f = _field['value']
if DELETE_CLASS_VAR and hasattr(self, _field['name']):
delattr(self.__class__, _field['name'])
setattr(self, _field['name'], _f.__class__(*_f._args, **_f._kwargs))
class BasicAdModel(Model):
def __init__(self):
super(BasicAdModel, self).__init__()
self._id = None
self.created_at = None
self.last_modified = None
class SpecialAdModel(BasicAdModel):
textfield = TextField()
def __init__(self):
super(SpecialAdModel, self).__init__()
print "INIT SPECIALAD", self._meta
print "-"*80
print "* creating two models, Ad1 and Ad2"
Ad1 = SpecialAdModel()
Ad2 = SpecialAdModel()
print "* Ad1 textfield attribute is", Ad1.textfield
print "* Setting Ad1 TextField instance to 'Text', expecting __set__ on Textfield to be called"
Ad1.textfield = "Text"
if DELETE_CLASS_VAR:
print "* If the class var was deleted on instantiation __get__ is not called here, and value is now str"
print "\tNew value is: ", Ad1.textfield
print "* Getting Ad2.textfield, expecting __get__ to be called and no value."
if DELETE_CLASS_VAR:
print "* If the class var was deleted - again __get__ is not called, attribute repalced with str"
print "\tAd2.textfield=", Ad2.textfield
print "* Setting Ad2 text field "
Ad2.textfield = "A different text"
if not DELETE_CLASS_VAR:
print "* When class var is not deleted, the two separate instances share the value later set on Ad2 "
print "\tAd2.textfield=",Ad2.textfield
print "\tAd1.textfield=", Ad1.textfield
Field
base class to inherit from? Besides that monkey-patching the instance's_meta
like you do might lead to a lot of unexpected behaviour... – Bernhard Vallant Jun 13 '13 at 11:09