2013-06-13 4 views
1

Я пытаюсь преобразовать переменные класса в переменные экземпляра во время выполнения, аналогично тому, что происходит в ORM Django, где переменные класса различных типов преобразуются в переменные экземпляра одних и тех же типов во время выполнения.Как преобразовать переменные класса в переменные экземпляра в Python?

Я использую метакласс, чтобы собирать переменные класса определенных типов в список и затем устанавливать их как переменные экземпляра при создании экземпляра. Я попытался удалить исходные переменные класса, а также сохранить их, и я получаю разные результаты в обоих случаях, как нежелательные.

  • Я создал простой объект Field, который использует _ получить _ и _ набора _ дескрипторов для преобразования значений в типы. Простая реализация поля - это текстовое поле, которое уникодирует значение на _ получает _.
  • При создании класса метакласс собирает все атрибуты типа «Поле» в список
  • Затем список полей устанавливается как класс _meta ['fields'].
  • Когда класс создается в объект, эти _meta ['fields'] затем устанавливаются в объект. В одном случае исходный класс var удаляется перед началом работы.
  • я тогда тест пути создания двух объектов, установив атрибут TextField один к какому-то тексту, ожидая _ получить _ и _ набор _ называться и как имеющие различные удобствами конфликтующих значений.

Когда переменная класса удаляется, устанавливая значение поля фактически не называют _ установить или _ получить _ и поле просто меняет тип на ул. Когда я не удаляю переменную класса, оба экземпляра экземпляра разделяют значения между ними.

Я разбавил свой код кодом ниже, который может быть сохранен в файл и запущен с python test.py или импортирован в оболочку python. Установка 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, но почему бы вам тогда не использовать, например. его класс «Свойство» для наследования? Кроме того, что обезвреживание обезьяны «_мета» экземпляра, как и вы, может привести к неожиданному поведению ... –

+0

Я пытаюсь создать подобный тип модели для моделей Django, но специализируется на нашем приложении и использует MongoDb в качестве бэкэнд. Я знаю порт django-nonrel и брандмау mongodb, но мне не интересны большинство функций - просто нужна модель с некоторой базовой типизацией на полях, которые я могу использовать для разных целей (распространяются на специализированные модели и т. Д.). Конечным результатом является перенос кучи моделей на основе django и их перемещение от бэкэнда Postgres и в Mongo. Я могу сделать это, используя переменные экземпляра с тем же подходом, но надеялся, что этот подход будет первым. – Harel

+0

Кроме того, фактические реализации полей в стороне, проблема, с которой я сталкиваюсь, заключается в преобразовании этих полей из исходных переменных класса в переменные экземпляра. – Harel

ответ

0

Может только предоставить вам несколько советов, как вы пытаетесь достичь чего-то сложного:

  • Попробуйте создать свой собственный Options класс - _meta на самом деле является экземпляром django.db.models.options.Options, некоторые вещи в _meta только кажется вести себя как списки и т. д., но вы должны изучить класс подкласса Django и переопределить необходимый вам материал.

  • Думаю, вы на правильном пути с работы с моделью мета класса Джанго, но вы также должны посмотреть, что магия строится на классы полей, поле-х contribute_to_class что-то весьма существенно ...

  • Также попробуйте использовать 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__ prints в этом случае относится к другому варианту использования моего теста (следовательно, переключатель var класса delete). После удаления классов классов «Поля» являются только переменными экземпляра, но затем они, похоже, «теряют» свои дескрипторы. На какое-то мгновение, прежде чем я заметил, что это тот же результат, что и у меня, я сделал маленькое джиг в комнате, когда решил, что проблема решена. :) – Harel

1

Мне удалось решить проблему в конечном итоге с большой благодарностью pjdelport от #Python IRC-канала. Спасибо всем, кто нашел время, чтобы прокомментировать и ответить - все это очень помогло. Как оказалось я пропустить некоторые жизненно важную часть - _ получить _ и _ набор _ дескрипторы работают на уровне класса, а также с помощью «я», чтобы установить значение я устанавливало его на поле класса поля вместо экземпляра объекта.

адаптированное решением является использование экземпляра аргумента, передаваемый _ получить _ и _ набора _ и использовать экземпляр _ ДИКТ _ поместить значение в случае непосредственно, не вызывая _ get _/_ _.

Obvisouly DELETE_CLASS_VAR часть избыточна, так как мы не хотим удалять классы vars. Теперь объекты могут использовать типизированные экземпляры vairables, а также вести список всех полей в _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 
Смежные вопросы