我正在尝试将类变量转换为运行时的实例变量,类似于Django ORM中的内容,其中各种类型的类变量在运行时被转换为相同类型的实例变量。
我正在使用元类将某些类型的类变量收集到列表中,然后在实例化时将它们设置为实例变量。我已经尝试删除原始类变量并保留它们,并且在两种情况下我都得到不同的结果,这两者都是不可取的。
删除类变量后,设置字段值实际上不会调用 _ 设置 或 _ 获取 _ 和字段只是将类型更改为str。当我不删除类变量时,实例化的两个对象共享它们之间的值。
我已将代码稀释到下面的代码中,该代码可以保存到文件中并使用python test.py
运行或导入到python shell中。将DELETE_CLASS_VAR设置为True将删除类变量(以考虑两个测试用例)。
显然我在这里遗漏了一些东西。如果没有让它工作,我会使用常规的实例变量,但我非常喜欢Django模型(我已经完成了Django代码而没有成功),其中在模型上设置的类变量然后成为其实例变量,具有某种程度的类型安全和具体方法。
谢谢!
# 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
答案 0 :(得分:1)
我最终设法解决了这个问题,非常感谢来自#Python IRC频道的pjdelport。感谢所有花时间发表评论和回答的人 - 这一切都有很大帮助。 事实证明我确实错过了一些重要的部分 - _ 获取 _ 和 _ 设置 _ 描述符正在运行在类级别上,通过使用“self”设置值,我将其设置在Field的类而不是对象的实例上。
改编后的解决方案是使用传递给 _ 获取 _ 和 _ 设置 _ 的实例参数使用实例的 _ dict _ 将值直接放在实例中而不触发 _ get _ / _ 设置 _ 强>
显然 DELETE_CLASS_VAR 部分是多余的,因为我们不想删除类变量。现在,对象可以使用类型化实例可变量,并且还可以维护_meta ['fields']中所有字段的列表。事实上,我们并不真的需要一个元类,但它很容易拥有对象的_meta属性中的所有字段的集合,这些字段是在类创建时创建的,而不是在每个实例化时创建的。
class Field(object):
def __init__(self, *args, **kwargs):
self.value = None
self.name = None
self._args = args
self._kwargs = kwargs
def __get__(self, instance, owner):
print "__get__ called: %s for %s" %(self.name, instance)
if instance:
return self.to_python(instance)
else: # called directly from class - return itself
return self
def __set__(self, instance, value):
print "__set__ called: %s for %s with value %s" %(self.name, instance, value)
self.value = self.from_python(instance, value)
def to_python(self, instance):
return instance.__dict__.get(self.name)
def from_python(self, instance, value):
instance.__dict__[self.name] = value
class TextField(Field):
def to_python(self, instance):
return unicode(instance.__dict__.get(self.name))
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)
_field.name = key
_fields.append( {'name':key, 'value': _field } )
setattr(obj, '_meta', {'fields': _fields} )
return obj
class Model(object):
__metaclass__ = ModelMetaClass
def __init__(self):
super(Model, self).__init__()
_metafields = self._meta.get('fields',[])
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"
print "\tNew value is: ", Ad1.textfield
print "* Getting Ad2.textfield, expecting __get__ to be called and no value."
print "\tAd2.textfield=", Ad2.textfield
print "* Setting Ad2 text field "
Ad2.textfield = "A different text"
print "\tAd2.textfield=",Ad2.textfield
print "\tAd1.textfield=", Ad1.textfield
答案 1 :(得分:0)
当您尝试实现复杂的事情时,只能为您提供一些提示:
尝试创建自己的Options
课程 - _meta
实际上是django.db.models.options.Options
的实例,_meta
中的某些内容似乎表现得像列表等,但是你应该研究一下Django类的子类,并覆盖你需要的东西。
猜猜你使用Django的模型元类是正确的方法,但是你也应该看看在字段类中构建了什么魔法,字段的contribute_to_class
是非常必要的...... < / p>
还尝试使用Django的Field
类作为基类,因为可能存在代码检查字段是否真的类似......
嗯,这不是真正的答案,只是想提供一些提示!
答案 2 :(得分:0)
我认为我解开了你的谜团 - 你分配了类而不是Field的实例。这有助于:
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.__new__(_f.__class__, *_f._args, **_f._kwargs))
仅在启用DELETE_CLASS_VAR
时才有效。
然而print
方法中__set__()
的结果迫使我的胆量向我发出信号,表明该解决方案仍有问题,可以进一步完善和有用。