如何处理从 Web API 构建的相互引用的 python 对象?

How to handle python objects built from a web API that have references to each other?

我正在为 Web API 构建一个客户端库,它公开了一些像这样的对象:

# objs/foo/obj_id_1
{id: "foo_id_1"
 name: "your momma"
 bar: "bar_id_2"}

# objs/bar/obj_id_1
{id: "bar_id_2"
 name: "puh-lease"
 foo: "foo_id_1"}

因此,foo 类型的对象引用了 bar 类型的对象,反之亦然。

在我的 python 客户端库中,我根据 API 数据构建 python 对象。 Foo 实例将具有包含 Bar 实例的 bar 属性,而不仅仅是 bar 的 id,而 Bar 实例具有对 Foo foo 属性中的实例。

我的 python 类 有 saverefresh 方法 POST 或从网络获取 API,他们这样做对于子对象。例如,如果我调用 a_foo.refresh(),这也会自动调用 a_foo.bar.refresh()

以上是一个非常简化的示例,可能有许多不同的 类 都指的是 barfoo 或许多其他类型中的任何一个的相同实例我们得到的对象。

我想我的问题实际上是两个问题:

  1. 什么是好的设计或策略来确保当我从 api 数据构建一个对象时,它对其他对象的所有引用都指向 相同的 对象如果我已经根据之前的 api 请求构建了该对象?
  2. 当我调用 saverefresh 时,当两个或多个对象相互引用时,防止无限循环的好的设计或策略是什么?

这是一个相当宽泛的问题。

1) 一种方法是使用工厂方法模式。示例:

def get_foo(_id):
    get_foo.foos = dict()
    def real_get_foo(_id):
        # get foo from the API
        get_foo.foos[_id] = foo
        return foo
    if _id in get_foo.foos:
        return get_foo.foos[_id]
    return real_get_foo(_id)

2) 我不认为进行嵌套保存是个好主意。如果我写 foo.bar.x = 5 后跟 foo.save() 我不希望 bar 被保存。为什么?因为我在 foo 上调用了 save(),所以我不必担心相关对象的不必要保存。

对于#1,你可以使用一个二级字典来保存获取的对象

objectCache
    |- foo 
    |   |- foo_id_1 : <obj>
    |   |- foo_id_2 : <obj>
    |   |- ...
    |
    |- bar
    |   |- bar_id_1: <obj>
    |   |- bar_id_2: <obj>
    |   |- ...
    |   
    |- baz
    |   |- baz_id_1: <obj>
    |   |- baz_id_2: <obj>
    |   |- ...
    |
    |- ...

你会像这样使用它:

def get_or_make_fetched_object(cls, id):
    return object_cache.setdefault(cls, {}).setdefault(id, cls())

my_foo = get_or_make_fetched_object(Foo, 'foo_id_1')

这里的技巧部分是如何摆脱不再引用的对象(例如,如果 foo_id_1 从 bar_id_1 切换到 bar_id_35),以避免内存泄漏,因为 object_cache 字典将无限期保留对对象的引用,除非从缓存中删除。

解决内存使用问题的一种可能方法是使用 gc.get_referrers() 为每个缓存对象获取 refcount 的清理函数。基本上缓存中 refcount 等于 2 的对象可以从缓存中删除(1 个计数来自 gc,另一个来自缓存)。这不适用于循环引用...

至于#2,您可以为一个对象加上保存日期的时间戳,与当前保存时间戳匹配的对象将被跳过,以避免无限递归。但是,只保存从属对象是有意义的。