簡體   English   中英

如何在Python中將類變量轉換為實例變量?

[英]How to convert class variables into instance variables in Python?

我正在嘗試在運行時將類變量轉換為實例變量,這與Django ORM中的情況類似,在Django ORM中,各種類型的類變量在運行時都轉換為相同類型的實例變量。

我正在使用一個元類將某些類型的類變量收集到一個列表中,然后在實例化時將它們設置為實例變量。 我嘗試刪除原始的類變量並保留它們,但在兩種情況下我都得到了不同的結果,兩者都是不希望的。

  • 我創建了一個簡單的Field對象,該對象使用_ get __ set _描述符將值轉換為類型。 字段的一個簡單實現是Textfield,它對_ get _上的值進行統一編碼。
  • 在創建類時,元類將類型為“字段”的所有屬性收集到列表中
  • 然后將字段列表設置為類的_meta ['fields']。
  • 當將該類實例化為一個對象時,然后將_meta ['fields']設置為該對象。 在一個用例中,原始類var會被事先刪除。
  • 然后,我通過創建兩個對象進行測試,將一個對象的textfield屬性設置為某些文本,期望_ get __ set _被調用並且都具有不同的非沖突值。

刪除類變量后,設置Field值實際上不會調用_ set_ get _,並且該字段僅將類型更改為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 

最終,我非常感謝#Python IRC頻道的pjdelport解決了這個問題。 感謝所有花時間評論和回答的人-這一切都非常有幫助。 事實證明,我確實錯過了一些重要的部分-_ get __ set _描述符在類級別上運行,並且通過使用“ self”設置值,我將其設置為Field的 ,而不是對象的實例。

調整后的解決方案是使用傳遞給_ get __ set _的實例參數,並使用實例的_ dict _將值直接放入實例中,而無需觸發_ get _ / _ set _

顯然, DELETE_CLASS_VAR部分是多余的,因為我們不想刪除類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 

在嘗試實現復雜目標時只能提供一些提示:

  • 嘗試創建自己的Options_meta實際上是django.db.models.options.Options的實例, _meta某些行為看起來似乎像列表等,但是您應該研究子類化Django的類並覆蓋對於您。

  • 猜猜您使用Django的模型元類是正確的方法,但是您還應該查看字段類內置了什么魔術,字段的contribute_to_class是非常重要的……

  • 還要嘗試使用Django的Field類作為基類,因為可能會進行代碼檢查某個字段是否真的像這樣……

好吧,這不是真正的答案,只是試圖提供一些提示!

我想我解決了您的奧秘-您分配的不是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情況下有效。

但是__set__()方法中的print結果迫使我的膽量向我發出信號,告訴我該解決方案仍然存在某些問題,它確實可以進一步完善和有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM