如何在Python中将类变量转换为实例变量?

2024-09-29 19:06:06 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图在运行时将类变量转换为实例变量,类似于Django ORM中的情况,在运行时,不同类型的类变量被转换为相同类型的实例变量。在

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

  • 我创建了一个简单的Field对象,它使用getset描述符将值转换为类型。Field的一个简单实现是Textfield,它将get上的值进行unicode。在
  • 创建类时,元类将“Field”类型的所有属性收集到一个列表中
  • 然后将字段列表设置为类的as\u meta['fields']。在
  • 当类被实例化到一个对象中时,那些meta['fields']就被设置为对象。在一个用例中,原始类var被提前删除。在
  • 然后,我通过创建两个对象来进行测试,将其中一个的textfield属性设置为某个文本,期望调用getset,它们都具有不同的非冲突值。在

当类变量被删除时,设置字段值实际上并不会调用setget,字段只是将类型更改为str。当我不删除类变量时,实例化的两个对象共享它们之间的值。在

我已经将我的代码稀释成下面的代码,这些代码可以保存到一个文件中并使用python test.py运行,或者导入到pythonshell中。将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 

Tags: to实例self类型fieldfieldsgetvalue
3条回答

我最终成功地解决了这个问题,非常感谢来自Python IRC通道的pjdelport。感谢所有花时间发表评论和回答的人-这一切都帮助很大。 事实证明,我确实漏掉了一些重要的部分,getset描述符是在类级别上操作的,通过使用“self”来设置一个值,我将它设置在字段的上,而不是对象的实例上。在

合适的解决方案是使用传递给getset的实例参数,并使用实例的dict直接将值放入实例中,而不会触发get。在

显然,删除类变量部分是多余的,因为我们不想删除类变量。现在这些对象可以使用类型化的实例变量,还可以维护一个包含所有字段的列表。事实上,我们并不真的需要一个元类,但它很容易拥有对象的所有字段的集合,这些字段是在类创建时创建的,而不是在每次实例化时创建的。在

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 

我想我解开了你的谜团-你分配的类不是一个领域的实例。所以这有助于:

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的结果迫使我的勇气告诉我,这个解决方案仍然有问题,需要进一步完善和使用。在

在你试图完成一些复杂的事情时,只能给你一些提示:

  • 尝试创建您自己的Options类-_meta实际上是django.db.models.options.Options的一个实例,_meta中的某些东西看起来像列表等,但是您应该研究一下Django的类的子类,并重写您所需的内容。

  • 我猜你使用Django的模型元类是正确的,但是你也应该看看在字段类中内置了什么魔力,字段的^{}是非常重要的。。。

  • 还可以尝试使用Django的Field类作为基类,因为可能会有代码检查字段是否真的是这样的。。。

好吧,这不是真正的答案,只是想提供一些提示!在

相关问题 更多 >

    热门问题