Django模型实例外键在状态变更时失去一致性列表

3 投票
4 回答
742 浏览
提问于 2025-04-15 23:49

我有一个模型,叫做 Match,里面有两个外键:

class Match(model.Model):
   winner = models.ForeignKey(Player)
   loser = models.ForeignKey(Player)

当我遍历 Match 时,我发现每个模型实例使用了一个独特的对象作为外键。这导致了不一致的问题,下面是一个例子:

>>> def print_elo(match_list):
...     for match in match_list:
...         print match.winner.id, match.winner.elo
...         print match.loser.id, match.loser.elo
... 
>>> print_elo(teacher_match_list)
4 1192.0000000000
2 1192.0000000000
5 1208.0000000000
2 1192.0000000000
5 1208.0000000000
4 1192.0000000000
>>> teacher_match_list[0].winner.elo = 3000
>>> print_elo(teacher_match_list) 
4 3000            # Object 4
2 1192.0000000000
5 1208.0000000000
2 1192.0000000000
5 1208.0000000000
4 1192.0000000000 # Object 4
>>>

我这样解决了这个问题:

def unify_refrences(match_list):
    """Makes each unique refrence to a model instance non-unique.

    In cases where multiple model instances are being used django creates a new
    object for each model instance, even if it that means creating the same
    instance twice. If one of these objects has its state changed any other
    object refrencing the same model instance will not be updated. This method
    ensure that state changes are seen. It makes sure that variables which hold
    objects pointing to the same model all hold the same object.

    Visually this means that a list of [var1, var2] whose internals look like so:

        var1 --> object1 --> model1
        var2 --> object2 --> model1

    Will result in the internals being changed so that:

        var1 --> object1 --> model1
        var2 ------^
    """
    match_dict = {}
    for match in match_list:
        try:
            match.winner = match_dict[match.winner.id]
        except KeyError:
            match_dict[match.winner.id] = match.winner
        try:
            match.loser = match_dict[match.loser.id]
        except KeyError:
            match_dict[match.loser.id] = match.loser

我想问的是:有没有更优雅的方法,通过使用查询集(QuerySets)来解决这个问题,而不需要在任何时候调用保存(save)?如果没有的话,我想让这个解决方案更通用:怎样才能获取模型实例上的外键列表,或者你有没有更好的通用解决方案来解决我的问题?

如果你觉得我对这个问题的理解有误,请纠正我。

4 个回答

0

呃,你是不是在用 get_or_create() 来处理玩家记录?如果没有的话,你可能在每场比赛中都在创建一模一样(或者差不多一样)的玩家记录。这可能会让你感到很烦恼,甚至崩溃。

1

你可以看看 django-idmapper 这个项目。它定义了一个叫做 SharedMemoryModel 的东西,这样在解释器里每个实例就只有一份拷贝。

2

这是因为,按照我的理解,没有一个全局的模型实例缓存,所以每次查询都会创建新的实例,而你相关对象的列表是通过单独的查询懒加载出来的。

你可能会发现,select_related() 在这种情况下足够聪明,可以解决这个问题。不要用这样的代码:

match = Match.objects.filter(...).get()

而是用:

match = Match.objects.select_related().filter(...).get()

这样可以一次性创建所有属性实例,并且可能会聪明地重用这些实例。否则,你就需要某种明确的缓存(这正是你解决方案的做法)。

警告:我自己对这种行为也感到惊讶,并不是这方面的专家。我是在寻找自己代码中类似问题的信息时发现了这个帖子。我只是分享我认为发生了什么,以帮助我理解……

撰写回答