类作用域内的动态实例变量
我有一个类,它是SQLAlchemy的声明式基础的子类。我需要写一个桥接对象,用来在声明式基础和我正在运行的另一个系统之间进行转换,但我希望这个桥接对象不需要知道我的记录中具体有哪些列。为了实现这一点,我想要一种方法来列出记录中的所有列,这样就不用在两个关系不大的地方维护属性列表,也能减少重复工作。这意味着我可以创建列属性,然后维护一个单独的列名列表,但这样又会出现重复的问题——如果我以后想改变这个结构,就得在两个地方都改。
因此,我想到了把列定义放在一个字典里,然后通过遍历这个字典来用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是我唯一的选择吗?
补充:我在下面接受了一个解决方案,但没有完全按照它做(虽然它对我找到答案非常重要)。我最后做的是: (注意,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 个回答
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