【问题标题】:Quickly locate an item in a dataclass containing a list of dataclasses by field value按字段值快速定位包含数据类列表的数据类中的项目
【发布时间】:2021-09-26 12:09:49
【问题描述】:

我有一个具有这种结构的数据类:

from dataclasses import dataclass
from typing import List

@dataclass
class PartData:
    id: int = 0
    name: str = None
    value: int = 0

@dataclass
class StockData:
    stock_1: List[PartData] = None
    stock_2: List[PartData] = None
    def __getitem__(self, key):
        return super().__getattribute__(key)

现在我创建数据类并用项目填充它们:

PARTS = [{"id": 1, "name": "screw"}, {"id": 3, "name": "bolt"}, {"id": 42, "name": "glue"}, {"id": 11, "name": "nail"}, {"id": 31, "name": "hammer"}, {"id": 142, "name": "paper"}]

dc_stock = StockData()

for p in PARTS:
    dc_part = PartData()
    dc_part.id = p["id"]
    if dc_part.id % 2 == 0:
        dc_stock_list = "stock_1"
    else:
        dc_stock_list = "stock_2"
    if getattr(dc_stock, dc_stock_list) == None:
        setattr(dc_stock, dc_stock_list, [dc_part])
    else:
        dc_stock[dc_stock_list].append(dc_part)

print(dc_stock)
# StockData(stock_1=[PartData(id=42, name=None, value=0), PartData(id=142, name=None, value=0)], 
#           stock_2=[PartData(id=1, name=None, value=0), PartData(id=3, name=None, value=0), PartData(id=11, name=None, value=0), PartData(id=31, name=None, value=0)]) 

我知道我可以遍历所有项目并比较它们,但是我可以定义一个将part_id 作为参数的方法,并且可以使用新的part_id 更新dc_stock 中的任何项目value?这可以实现为StockData 的方法吗?假设我不知道零件是在stock_1 还是stock_2

编辑

为了更好地理解我想分享我的方法,这对我来说看起来非常循环和昂贵:

@dataclass
class StockData:
    stock_1: List[PartData] = None
    stock_2: List[PartData] = None

    def __getitem__(self, key):
        return super().__getattribute__(key)

    def update_part(self, id, value):
        for stock_list in [f for f in fields(self) if f.name.startswith("stock")]:
            stock = getattr(self, stock_list.name)
            if len(stock) > 0:
                for part in stock:
                    if part.id == id:
                        part.value = value
                        return None


print(dc_stock)
dc_stock.update_part(1, 10)
print(dc_stock)

【问题讨论】:

  • 看起来stock_1 中只有偶数项,stock_2 中只有奇数项 - 想知道这是故意的,还是仅用于演示目的。
  • 只是表示该项目暂时不能在两者中。所以是的,只是演示。
  • 我还用自己的方法更新了我的问题,以阐明我想要实现的目标。
  • 你可能不想在这里使用__getattribute__。我认为__getattr__ 就足够了。
  • 是的,每个id 都是独一无二的。

标签: python python-dataclasses


【解决方案1】:

这是一种设置方法。如果您总是知道需要通过id 查找,则可以使用dict 将id 映射到零件,因为dict 查找比从列表中查找零件要快得多。我还缓存了与股票相关的数据类字段列表,这也是一个好主意。

from dataclasses import dataclass, fields, field
from functools import cached_property
from typing import List, Dict, Union, Tuple


@dataclass
class PartData:
    id: int = 0
    name: str = None
    value: int = 0


@dataclass
class StockData:
    stock_1: Dict[int, PartData] = field(default_factory=dict)
    stock_2: Dict[int, PartData] = field(default_factory=dict)

    @cached_property
    def stock_fields(self) -> Tuple[str, ...]:
        return tuple(f.name for f in fields(self)
                     if f.name.startswith("stock"))

    @classmethod
    def from_parts(cls, parts: List[Dict[str, Union[str, int]]]):
        """Create a new `StockData` object from list of parts."""
        stock = cls()

        for p in parts:
            part = PartData(**p)
            if part.id % 2 == 0:
                stock_list = 'stock_1'
            else:
                stock_list = 'stock_2'

            getattr(stock, stock_list)[part.id] = part

        return stock

    def update_part(self, id, value):
        """Update value for a part, given the part id."""

        for stock_field in self.stock_fields:
            stock = getattr(self, stock_field)
            if id in stock:
                stock[id].value = value
                return None

用法与您的使用方式非常相似。我还添加了一个from_parts 辅助方法,因为从部件列表构造StockData 实例似乎是一种常见模式。请注意,由于股票字段现在是字典,因此您可以访问 .values() 以迭代每个股票中的 PartData 项目。

def main():
    PARTS = [{"id": 1, "name": "screw"}, {"id": 3, "name": "bolt"},
             {"id": 42, "name": "glue"}, {"id": 11, "name": "nail"},
             {"id": 31, "name": "hammer"}, {"id": 142, "name": "paper"}]

    dc_stock = StockData.from_parts(PARTS)
    assert dc_stock.stock_2[1].value == 0

    print(dc_stock)

    dc_stock.update_part(1, 10)
    assert dc_stock.stock_2[1].value == 10

    print(dc_stock)

    print('Stock 1:')
    print(list(dc_stock.stock_1.values()))


if __name__ == '__main__':
    main()

【讨论】:

    【解决方案2】:

    您要求的是索引。

    基本上,您有一个 dict 伴随您的 {<field value>: <items with this value>} 数据结构,它会在您更新数据时相应更新。
    如果该字段是唯一的(作为项目 ID 应该是),则更容易:您只需从键链接到 1 个项目,而不是链接到项目列表。

    如您所见,使索引保持最新是一项额外的工作,因此它只会使您超过一定的数据大小;数据写入与读取的频率也很重要(索引会花费时间进行更新,但一旦索引查找开销变得比遍历整个表更快,则可以节省选择超过特定数据大小的时间)以及查询的百分比将从索引中受益。


    首先,考虑不要重新发明轮子,而是使用像 SQLAlchemy 这样的 Pythonic ORM 而不是支持透明索引的数据类。您无需运行数据库服务器即可从中受益,因为它也可以使用像 SQLite 这样的无服务器数据库作为后端。此外,编译后的后端可能会比纯 Python 快得多(数量级)。


    dict-base 索引集成到您的数据结构中的方法是将其保存在表类 (StockData) 中,并在写入任何索引字段时命令表实例更新索引 (包括它们第一次初始化的时间)。

    • 可能最简单的方法是:
      • 在每个记录实例中保留对表实例的引用(仅保留对更新方法的引用就足够了)
      • 每当写入索引字段时(包括首次初始化时),使用它来命令表实例更新索引
    • 如果您不想修改字段类,您可以选择:
      • 不要直接写字段,只能通过表类提供的一些接口。这样,表类的逻辑将有机会更新索引,因为它将在写入值之后但在返回给您之前获得控制
      • 在任何写入后手动更新索引。这很容易出错(=最终灾难的秘诀),尤其是在具有许多相互依赖的步骤的更复杂的操作中,因为您可能会忘记,甚至语法不允许(例如,如果您使用生成器表达式)调用更新合适的时机。

    以下是“最简单方法”选项的示例:

    class PartData:
      <...>
      _table: StockData
    
      def __setitem__(self, key, new_value):
        if key == 'id':
          self._table.update_id_index(self, new_value, self.__getitem__(key))
        super(self,PartData).__setitem__(self, key, new_value)
    
    class StockData:
      <...>
      # assuming id is unique
      id_index: {object: PartData} = {}
    
      def update_id_index(self, record, new_value, old_value = None):
        try: del self.id_index[old_value]
        except KeyError: pass
        self.id_index[new_value] = record
    

    【讨论】:

      猜你喜欢
      • 2019-10-22
      • 1970-01-01
      • 1970-01-01
      • 2013-10-13
      • 2015-05-02
      • 1970-01-01
      • 1970-01-01
      • 2011-05-06
      • 2011-12-27
      相关资源
      最近更新 更多