在继承情况下分配类属性的问题

时间:2017-12-12 17:14:26

标签: python python-3.x

这是我目前面临的问题的简化示例。

我有一个类,其成员可能会也可能不会运行需要从存储在数据库中的表中访问值的函数。我显然不想进行一百次不必要的数据库调用,因此我目前正在努力想出一个能够执行以下操作的解决方案:

  1. 对于特定数据集,不会多次访问数据库。
  2. 仅在实际需要数据时才访问数据库。
  3. 现在我的第一个想法是将我需要的数据分配给一个类变量。这样,类的每个实例都不必与数据库建立新的连接来获取表,而是只进行一次连接。

    def get_data_from_db():
        # Placeholder function for database access
        return 4
    
    class Foo1:
        dbtable = get_data_from_db()
    

    这很好地完成了目标1,但由于在声明类时访问数据库,在实际运行任何内容之前,我们可能会下载一些从未使用过的东西。

    我的下一次尝试是使用类属性,这使我能够完成目标1和2。

    class classproperty(object):
        def __init__(self, fget):
            self.fget = fget
        def __get__(self, owner_self, owner_cls):
            return self.fget(owner_cls)
    
    class Foo1:
        _dbtable = None
        @classproperty
        def dbtable(cls):
            if cls._dbtable is None:
                print('new assignment from class {}'.format(cls.__name__))
                cls._dbtable = get_data_from_db()
            return cls._dbtable
    

    现在,第一次访问Foo1.dbtable时,它会运行get_data_from_db(),但每次访问它时都会使用以前存储的值。

    然而,我遇到了一个问题。我有多个继承自我的基类的类,而我目前所拥有的类并不像我想要的那样工作。

    让我们添加:

    class Foo2(Foo1):
        pass
    
    class Foo3(Foo1):
        pass
    

    如果我们运行以下代码,我们会得到所需的结果:

    print(Foo1.dbtable)
    print(Foo2.dbtable)
    print(Foo3.dbtable)
    
    > new assignment from class Foo1
    > 4
    > 4
    > 4
    

    数据库调用发生在Foo1的命名空间中,稍后当它需要在Foo2和Foo3中访问时,它已经可用。但是Foo1是一个基类,可能永远不会实际实例化。相反,我们可能会运行这样的事情:

    print(Foo2.dbtable)
    print(Foo3.dbtable)
    
    > new assignment from class Foo2
    > 4
    > new assignment from class Foo3
    > 4
    

    现在我遇到了一个问题。当在Foo2中第一次访问数据库时,结果存储在Foo2的命名空间中,Foo3无权访问。然后,当Foo3需要相同的数据时,它会进行第二次数据库调用,这不是理想的结果。

    我不知道解决这个问题的最佳方法是什么。我最初的想法是看看以下是否可行。我目前的代码是:

    if cls._dbtable is None:
        cls._dbtable = get_data_from_db()
    

    而不是那样,如果cls._dbtable为None,我会以相反的顺序遍历类的mro(从对象前面的类开始),寻找cls._dbtable所在的第一个类定义。据推测,该命名空间将是一切从cls._dbtable继承的命名空间。然后,我将数据库中下载的数据放在该命名空间中,而不是将其分配给当前类的命名空间。

    然而,这个想法似乎是一个超级混乱的黑客,所以我真的希望有一个更容易的解决方案。

1 个答案:

答案 0 :(得分:0)

这似乎是一个难题,但在评论中,Norrius提出了一个非常明显的解决方案,即将dbtable放入自己的类中。

class DbTable:
    def __init__(self):
        self._db_table = None

    @property
    def dbtable(self):
        if self._db_table is None:
            print('new assignment')
            self._db_table = self.get_data_from_db()
        return self._db_table

    def get_data_from_db(self):
        # Placeholder function for database access
        return 4


class Foo1:
    db = DbTable()
    dbtable = db.dbtable

class Foo2(Foo1):
    pass

class Foo3(Foo1):
    pass

它似乎具有我想要的功能。

print(Foo1.dbtable)
print(Foo2.dbtable)
print(Foo3.dbtable)

> new assignment
> 4
> 4
> 4

print(Foo2.dbtable)
print(Foo3.dbtable)

> new assignment
> 4
> 4

再次感谢Norrius提出的一个简单而明显的建议,即我在某种程度上忽略了这一点。对于其他所有人,很抱歉,我遇到了一个问题,这个问题的答案非常简单。