2013-06-13 165 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