Python 单个SQLAlchemy映射对象在内存中产生许多不同的实例

Python 单个SQLAlchemy映射对象在内存中产生许多不同的实例,python,sqlalchemy,Python,Sqlalchemy,我知道这个标题很复杂,所以我将试着给出一个简单的例子来说明我要做的事情。请容忍我一会儿 我在一个期权价值计算和投资组合风险分析服务器上工作,遇到了一个问题,我不太确定在加载持久化对象图,然后访问动态添加数据库加载的一些缓存属性时如何解决 假设我有以下映射类: Base = declarative_base() class Portfolio(Base): __tablename__ = "portfolios" id = Column(Integer, primary_key=

我知道这个标题很复杂,所以我将试着给出一个简单的例子来说明我要做的事情。请容忍我一会儿

我在一个期权价值计算和投资组合风险分析服务器上工作,遇到了一个问题,我不太确定在加载持久化对象图,然后访问动态添加数据库加载的一些缓存属性时如何解决

假设我有以下映射类:

Base = declarative_base()

class Portfolio(Base):
    __tablename__ = "portfolios"
    id = Column(Integer, primary_key=True)
    asset_classes = relationship("AssetClass", backref="portfolios")

class AssetClass(Base):
    __tablename__ = "asset_classes"
    id = Column(Integer, primary_key=True)
    portolio_id = Column(Integer, ForeignKey("portfolios.id"))
    assets = relationship("Asset", backref="asset_classes")

class Asset(Base):
    id = Column(Integer, primary_key=True)
    asset_class_id = Column(Integer, ForeignKey("asset_classes.id"))
然后,我加载一个公文包并按如下方式进行初始化:

session = Session()
# pretend there's only one Portfolio
portfolio = session.query(Portfolio).one()
init_portfolio(portfolio)
while(keep_server_alive):
# session stays open for server life
    gevent.sleep(0)
session.close()


def init_portfolio(p):
    # note that this is a dynamically added property
    portfolio.values_store = ValuesStore()
    for asset_class in p.asset_classes:
        init_asset_class(asset_class)


def init_asset_class(ac):
    for asset in ac.assets:
        init_asset(asset)
def on_underlying_update(asset, underlying_price):
    # the next line non-deterministically fails with an AttributeError
    values_store = asset.asset_class.portfolio.values_store
    values_store.get_asset_price(asset, underlying_price)
def keep_portfolio_alive(p):
    portfolio = p
    while True:
        gevent.sleep(0)
初始化所有内容后,我使用长时间运行的gevent线程处理价格更新并计算某些衍生值,如下所示:

session = Session()
# pretend there's only one Portfolio
portfolio = session.query(Portfolio).one()
init_portfolio(portfolio)
while(keep_server_alive):
# session stays open for server life
    gevent.sleep(0)
session.close()


def init_portfolio(p):
    # note that this is a dynamically added property
    portfolio.values_store = ValuesStore()
    for asset_class in p.asset_classes:
        init_asset_class(asset_class)


def init_asset_class(ac):
    for asset in ac.assets:
        init_asset(asset)
def on_underlying_update(asset, underlying_price):
    # the next line non-deterministically fails with an AttributeError
    values_store = asset.asset_class.portfolio.values_store
    values_store.get_asset_price(asset, underlying_price)
def keep_portfolio_alive(p):
    portfolio = p
    while True:
        gevent.sleep(0)
这些计算可能非常昂贵,因此ValuesStore对象有一个缓存策略来存储结果。在随机通过On_-underground_-update函数时,我将得到一个AttributeError,表示Portfolio对象没有values_-store属性

起初这让我很困惑,但当我调试这个问题时,我似乎发现引用同一个公文包的子AssetClass对象实际上可能引用了内存中的不同对象。在每次访问期间使用python的id()函数检查Portfolio.id和内存id之后,我开始相信这一点。这样做,我会经常发现,虽然我可能只访问过一个Portfolio.id,但我引用了几个不同的内存地址,这取决于哪个孩子在访问

我知道这是长篇大论,我很感激任何和我在一起这么久的人。我的问题是,是否有一种方法可以将整个对象图加载到内存中一次且仅加载一次?从资源的角度来看,这个ValuesStore对象可能很昂贵,我更愿意尽可能少地实例化它

作为参考,我使用的是Python2.7.3、SQLAlchemy 0.8、gevent 1.0rc2,在Windows7和OSX10.8上都遇到了同样的问题

再次感谢

**编辑**

我花了很多时间浏览我的代码,寻找那些会导致公文包对象从当前会话分离并附加到新会话的语句。我找不到任何类似的语句。我尝试聆听各种各样的实例和会话事件,每次我都能看到“新的”“正在加载公文包,但它是由原始会话加载的

公文包对象是否可能正在被垃圾收集?我尝试通过生成一个新的Gevent线程来验证这个假设,以保持对投资组合的强引用,如下所示:

session = Session()
# pretend there's only one Portfolio
portfolio = session.query(Portfolio).one()
init_portfolio(portfolio)
while(keep_server_alive):
# session stays open for server life
    gevent.sleep(0)
session.close()


def init_portfolio(p):
    # note that this is a dynamically added property
    portfolio.values_store = ValuesStore()
    for asset_class in p.asset_classes:
        init_asset_class(asset_class)


def init_asset_class(ac):
    for asset in ac.assets:
        init_asset(asset)
def on_underlying_update(asset, underlying_price):
    # the next line non-deterministically fails with an AttributeError
    values_store = asset.asset_class.portfolio.values_store
    values_store.get_asset_price(asset, underlying_price)
def keep_portfolio_alive(p):
    portfolio = p
    while True:
        gevent.sleep(0)
这似乎解决了问题。我从未看到会话再次加载公文包

**编辑2**

所以我不确定,但我相信我已经发现了我的问题。在定义映射类之间的关系时,我实际上使用了属性映射集合而不是普通列表。初始化类时,我会像这样迭代子对象:

for asset_class in portfolio.asset_classes.itervalues():
    # do something
由于其中一些类是多重嵌套的,所以在层次结构中有一个类使用这个复制的迭代器被引用,然后它使用另一个复制的迭代器引用自己的子类。我最好的猜测是,Portfolio类在它和它的父对象之间,或者它和它的任何子对象之间都没有强引用。GC然后在某个点将其标记为收集


希望这对将来的人有所帮助。

代码方面似乎缺少主要细节,因为我只看到上面的一个会话。对于给定的数据库标识(即表X+主键Y),会话将只实例化一个对象。因此,如果给定标识有多个副本,这意味着您有多个会话,或者正在分离对象,或者其他。您能提供一个简单的、完全自我复制的测试用例吗?很抱歉,我花了一点时间才回复您。这个项目的范围比这个例子要大得多,所以我花了一些时间试图用一段更小但更完整的代码来重现这个错误。