类作用域内的动态实例变量

1 投票
1 回答
669 浏览
提问于 2025-04-16 16:07

我有一个类,它是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

撰写回答