【问题标题】:How to convert class variables into instance variables in Python?如何在 Python 中将类变量转换为实例变量?
【发布时间】:2013-06-09 17:35:26
【问题描述】:

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

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

  • 我创建了一个简单的 Field 对象,它使用 _get__set_ 描述符进行转换值转换为类型。 Field 的一个简单实现是 Textfield,它对 _get_ 上的值进行 unicode。
  • 在创建类时,元类将“字段”类型的所有属性收集到一个列表中
  • 然后将字段列表设置为类的 _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 

【问题讨论】:

  • 也许您可以详细说明您想要实现的目标?你似乎想模仿 Django 的行为,但你为什么不使用例如。它的Fieldbase 类继承自?除了像你一样对实例的_meta 进行猴子修补之外,可能会导致很多意想不到的行为......
  • 我正在尝试创建与 Django 模型类似的模型类型,但专门针对我们的应用程序并使用 MongoDb 作为后端。我知道 django-nonrel 端口和 mongodb 后端,但我对大多数功能不感兴趣 - 只需要一个模型,在我可以用于不同目的的字段上进行一些基本输入(扩展到专用模型等)。最终结果是迁移了一堆基于 django 的模型,并将它们从 Postgres 后端移到 Mongo。我可以使用具有相同方法的实例变量来做到这一点,但我希望先尝试这种方法。
  • 另外,除了实际的字段实现之外,我遇到的问题是将这些字段从最初的类变量转换为实例变量。

标签: python django metaclass


【解决方案1】:

只能在您尝试实现复杂的目标时为您提供一些提示:

  • 尝试创建自己的 Options 类 - _meta 实际上是 django.db.models.options.Options 的一个实例,_meta 中的某些东西看起来就像列表等,但你应该研究 Django 的类的子类化和覆盖你需要的东西。

  • 猜猜您使用 Django 的模型元类的方法是正确的,但您还应该看看字段类中内置了什么魔法,该字段的 contribute_to_class 非常重要...

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

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

【讨论】:

  • 我没有尝试使用 Django 的元分类,只是借用了他们使用的概念。我宁愿这能够作为围绕 mongodb 集合的自包含轻量级对象包装器运行。代码而不是数据库中的某种灵活模式。
【解决方案2】:

我想我解开了你的谜团 - 你分配了 class 而不是 Field 的 instance。所以这有帮助:

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 的结果迫使我的胆量向我发出信号,即解决方案仍然存在问题,以便进一步完善和有用。

【讨论】:

  • 是的。在这种情况下缺少 get__/__set 打印依赖于我的测试的另一个用例(因此删除类 var 开关)。删除类变量后,“字段”只是实例变量,但随后它们似乎“丢失”了它们的描述符。不过有那么一会儿,在我注意到这与我得到的结果相同之前,当我认为问题已经解决时,我确实在房间里做了一个小夹具。 :)
【解决方案3】:

我最终设法解决了这个问题,非常感谢#Python IRC 频道的 pjdelport。感谢所有花时间评论和回答的人——这一切都帮了大忙。 事实证明,我确实错过了一些重要部分 - _get__set_ 描述符正在运行在类级别上,通过使用 'self' 设置值,我将其设置在字段的 class 上,而不是对象的实例上。

适应的解决方案是使用传递给 _get__set_ 的实例参数和使用实例的 _dict_ 将值直接放入实例中,而不触发 _get_/_设置_

显然 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 

【讨论】:

    猜你喜欢
    • 2016-09-12
    • 2012-01-31
    • 2011-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-12
    • 2019-11-24
    相关资源
    最近更新 更多