2013-06-13 228 views
1

我想在运行时将类变量转换为实例变量,类似于Django ORM中的各种类型的类变量在运行时转换为相同类型的实例变量。如何将类变量转换为Python中的实例变量?

我使用元类来将某些类型的类变量收集到列表中,然后在实例化时将它们设置为实例变量。我试过删除原来的类变量,并保留它们,我在两种情况下都得到了不同的结果,都是不可取的。

  • 我创建了使用_ 得到 __ 设置 _描述符值转换为类型简单的Field对象。字段的一个简单的实现是文本字段,它对在_得到的值进行解码,得到_
  • 创建类时,元类将'字段'类型的所有属性收集到列表中
  • 字段列表然后设置为类的_meta ['fields']。
  • 当类被实例化为一个对象时,那些_meta ['fields']被设置到对象中。在一个用例中,原始类var将被事先删除。
  • 我再创建两个对象,一个文本字段属性设置为一些文字,希望测试_ 得到 __ 设置 _被称为两者具有不同的非冲突的值。

当类变量被删除,设置字段值实际上并不叫_ 设置_ 得到 _和场只是改变类型的海峡。当我不删除类变量时,实例化的两个对象共享它们之间的值。

我已将代码稀释到下面的代码中,可以将代码保存到文件中并使用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

也许你可以详细阐述一下你试图实现的目标?你似乎想要模仿Django的行为,但你为什么不使用例如。它的'Field'类继承自?除了那个猴子修补实例的'_meta'就像你可能会导致很多意想不到的行为... –

+0

我想创建一个类似的模型类型到Django的模型,但专门为我们的应用程序和使用MongoDb作为后端。我知道django-nonrel端口和mongodb后端,但我对大部分功能不感兴趣 - 只需要一个模型,其中有一些基本的字段输入,可以用于不同的目的(扩展到特定的模型等)。最终的结果是迁移一堆基于django的模型,并将它们从Postgres后端迁移到Mongo中。我可以用同样的方法使用实例变量来做到这一点,但希望首先给出这种方法。 – Harel

+0

另外,除了实际的字段实现外,我遇到的问题是将这些字段从最初的类变量转换为实例变量。 – Harel

回答

0

只能提供你一些提示,你正在努力实现复杂的东西:

  • 尝试创建自己的Options类 - _meta实际上是django.db.models.options.Options一个实例,在_meta有些东西似乎只是行为像列表等,但你应该考虑子类化Django的类,并覆盖你需要的东西。

  • 猜你是在用Django的模型元类工作的正确方法,但你也应该是什么神奇的是内置到现场班,现场的contribute_to_class的东西相当必要...

  • 而且尝试使用Django的Field类作为基类,因为有可能是代码检查,如果现场真的是类似的东西...

嗯,这是没有真正的答案,只是想提供一些线索!

+0

我不想使用Django的元类,只是借用他们使用的概念。我宁愿它能够作为一个包含mongodb集合的自包含轻量级对象包装器运行。代码中的某种灵活模式而不是数据库。 – Harel

0

我觉得我解决了你的谜团 - 你分配了不是实例的字段。这有助于:

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__()方法中的结果迫使我的胆量向我发出信号,表明解决方案仍然存在某些问题,使其更加精细和有用。

+0

是的。在这种情况下缺少__get __/__ set__打印会回退到我的测试的其他用例(因此删除类var switch)。一旦类变量被删除,“Fields”只是实例变量,但它们似乎“丢失”了它们的描述符。 有一段时间,在我注意到这与我所得到的结果相同之前,当我认为问题已解决时,我确实在房间里做了一个小夹具。 :) – Harel

1

我终于设法解决了这个问题,非常感谢#Python IRC频道的pjdelport。感谢大家花时间评论和回答 - 这一切都有很大帮助。 事实证明我没有错过一些重要的部分 - _ 得到 __ 设置 _描述符上的一流水平运行,并通过“自我”设定的值,我将它设置在字段的而不是对象的实例。

的适应解决方案是使用传递给实例参数_ 得到 __ 设置 _和使用实例的_ 字典 _直接放置在实例中的值,而不会触发_ 得到_/_ 集合_

特别是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