Динамические переменные экземпляра в области видимости класса

#python #sqlalchemy #metaclass #setattr

#python #sqlalchemy #метакласс #setattr

Вопрос:

У меня есть класс, который является потомком декларативной базы SQLAlchemy. Мне нужно написать объект-мост, который будет транслировать между декларативной базой и другой системой, которую я запускаю, но я хочу, чтобы объекту-мосту не нужно было точно знать, какие столбцы находятся в моей записи. В таком случае мне нужен способ перечислить все столбцы в записи, чтобы избежать необходимости поддерживать список атрибутов в двух лишь смутно связанных местах и одновременно минимизировать репликацию. Это означает, что я мог бы создать атрибуты столбца, а затем поддерживать отдельный список имен столбцов, но опять же, репликация является фактором — если бы я захотел изменить структуру этой вещи позже, мне пришлось бы изменить ее в обоих местах.

В таком случае я подумал о том, чтобы вставить определения столбцов в dict, а затем выполнить итерацию по dict для создания определений каждого столбца с помощью setattr, вот так:

     for k,v in self.__column_dict.items():
        setattr(self,k,
                sqlalchemy.Column(v['column_type'],**v.get('opts',{})),
                )
  

Теперь вот в чем загвоздка: если я вставлю этот фрагмент кода на уровне класса, self еще не определен, и я получу сообщение об ошибке (что имеет смысл). Если я вставлю это __init__ , self будет определен, но декларативная база SQLAlchemy выдает ошибку, потому что к моменту завершения определения класса в таблице нет первичного ключа (что имеет смысл, поскольку эти атрибуты не будут установлены до времени выполнения).

Итак, с учетом всего сказанного, как мне получить то, что я хочу, не используя eval, или использование eval здесь мой единственный вариант?


РЕДАКТИРОВАТЬ: я принял решение ниже, но не использовал его на 100% (хотя это было абсолютно полезно для получения мной ответа). Вот что я в итоге сделал: (обратите внимание, что DeclarativeMeta является частью sqlalchemy.ext.declarative , того же пакета, что и declarative_base)

 class MyMeta(DeclarativeMeta):
    def __new__(meta, classname, bases, dict_):
        klass = type.__new__(meta, classname, bases, dict_)
        if dict_.has_key('__classinit__'):
            klass.__classinit__ = staticmethod(klass.__classinit__.im_func)
        klass.__classinit__(klass, dict_)
        return klass
  

Затем в моем производном классе,

 class DerivedClass(declarative_base()):
    __tablename__ = 'yaddayadda'
    __metaclass__ = MyMeta

    def __classinit__(klass, dict_):
        klass.__column_dict = <column dict here>

        for k,v in klass.__column_dict.items():
            setattr(klass,k,
                <fancy column stuff>
                )
   ...<rest of class definition>...
  

Ответ №1:

Это можно сделать, предоставив метакласс declarative_base и в этом метаклассе

 from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta

class MyMeta(DeclarativeMeta): 

    def __init__(klass, classname, bases, dict_): 

        for k, v in dict_.items():

            if k.endswith('__column_dict'):

                for name, datatype, is_pk in v:

                    setattr(klass, name, Column(name, datatype, primary_key=is_pk))

        return DeclarativeMeta.__init__(klass, classname, bases, dict_)

Base = declarative_base(metaclass=MyMeta)

class Bob(Base):
    __tablename__ = 'bob'
    __column_dict = [('a', Integer, True), ('b', Integer, False)]

bob = Bob()
print bob.a
print bob.b
  

Комментарии:

1. Большое вам спасибо. Чтобы быть уверенным, должен ли я также вызывать super в конце для проверки на будущее? Например, вернуть super(RecordMeta, klass).__init__(classname, bases, dict_)?

2. Возвращаясь, когда я реализую это, я получаю «объект типа ‘MyDerivedObject’ не имеет атрибута ‘_MyMeta__column_dict». Похоже, что поиск column_dict привязывается к RecordMeta, как мне указать ему, где на самом деле искать?

3. Извините, я привел неаккуратный пример. Добавлена новая и полная.

4. Спасибо, довольно изящно, хотя кажется пугающим быть двусмысленным, используя «endswith» в имени атрибута.