[英]How to convert class variables into instance variables in Python?
我正在嘗試在運行時將類變量轉換為實例變量,這與Django ORM中的情況類似,在Django ORM中,各種類型的類變量在運行時都轉換為相同類型的實例變量。
我正在使用一個元類將某些類型的類變量收集到一個列表中,然后在實例化時將它們設置為實例變量。 我嘗試刪除原始的類變量並保留它們,但在兩種情況下我都得到了不同的結果,兩者都是不希望的。
刪除類變量后,設置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.