首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何获取dataclass字段的索引

如何获取dataclass字段的索引
EN

Stack Overflow用户
提问于 2022-10-12 14:39:52
回答 1查看 100关注 0票数 0

假设我有一个简单的dataclass实例

代码语言:javascript
复制
import dataclasses as dc

@dc.dataclass
class DCItem:
    name: str
    unit_price: float

item = DCItem('test', 11)

现在我要确定实例属性item.unit_price.的位置(索引)我如何使它简单的使用和表演?我想过使用dc.asdict的get方法

代码语言:javascript
复制
@dc.dataclass
class DCItem:
    name: str
    unit_price: float

    def get_index(self, name):
        return list(dc.asdict(self)).index(name)

item.get_index('unit_price')  # 1

但这有两个缺点:

它不是很好的性能,至少在许多实例属性item.unit_price

  • 中,它失去了的良好的自动完成功能。

是否有将数据块的特性与IntEnum和enum.auto()特性相结合的解决方案,而没有上述缺点?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-10-12 22:58:16

如果类在运行时不被更改,则可以将类属性中的索引缓存为字典。

代码语言:javascript
复制
import dataclasses as dc

@dc.dataclass
class DCItem:
    name: str
    unit_price: float
    
    @classmethod
    def get_index(cls, name):
        if '_idx_mapping' not in cls.__dict__:
            flds = dc.fields(cls)
            cls._idx_mapping = {flds[idx].name: idx for idx in range(len(flds))}
        return cls._idx_mapping[name]


>>> item = DCItem('test', 11)
>>> item.get_index('unit_price')
1

在最坏的情况下,访问字典应该是快速的O(n)。

代码语言:javascript
复制
>>> from timeit import timeit
>>> timeit("item.get_index('unit_price')", "from __main__ import item")
0.21372696105390787

作为比较,您的解决方案非常慢,正如您提到的:

代码语言:javascript
复制
>>> timeit("item.get_index('unit_price')", "from __main__ import item")
4.260601775022224

注意到:我还没有用继承测试这个类。

编辑:解决第二点会使解决方案更加复杂。我使用Python描述符提出了以下内容。

代码语言:javascript
复制
import dataclasses as dc
from typing import Any
from collections import defaultdict


class IndexedField:
    def __init__(self, a_type: type, value: Any, index: int):
        self._validate_type(a_type, value)  # This line can be removed when type checking is not required.
        self._a_type = a_type
        self._value = value
        self._index = index

    @staticmethod
    def _validate_type(a_type: type, value: Any):
        if not isinstance(value, a_type):
            raise TypeError(f"value is of type {type(value)} but {a_type} is expected")

    @property
    def a_type(self):  # read-only
        return self._a_type

    @property
    def index(self):  # read-only
        return self._index

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        self._validate_type(self._a_type, new_value)  # This line can be removed when type checking is not required.
        self._value = new_value

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(a_type={self._a_type!r}, index={self._index!r}, value={self._value!r})')


class IndexedFieldDescriptor:
    _class_last_index = defaultdict(int)
    _class_indexes = defaultdict(dict)

    def __init__(self, a_type) -> None:
        self._name = None
        self._type = a_type

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self._name]

    def __set_name__(self, owner, name):
        self._name = name
        self._class_indexes[owner.__name__][name] = self._class_last_index[owner.__name__]
        self._class_last_index[owner.__name__] += 1

    def __set__(self, instance, value):
        index = self._class_indexes[instance.__class__.__name__][self._name]
        instance.__dict__[self._name] = IndexedField(self._type, value, index)


@dc.dataclass
class DCItem:
    name: IndexedField = IndexedFieldDescriptor(str)
    unit_price: IndexedField = IndexedFieldDescriptor(float)


item = DCItem('test', 11.0)
print(item)
print(f"* name field value: {item.name.value!r}, name field index: {item.name.index!r}, name field type: {item.name.a_type!r}")
print(f"* unit_price field value: {item.unit_price.value!r}, unit_price field index: {item.unit_price.index!r}, unit_price field type: {item.unit_price.a_type!r}")

from timeit import timeit
print(f'* Index access time: {timeit("item.name.index", "from __main__ import item")}')
print(f'* Value access time: {timeit("item.name.value", "from __main__ import item")}')

输出:

代码语言:javascript
复制
DCItem(name=IndexedField(a_type=<class 'str'>, index=0, value='test'), unit_price=IndexedField(a_type=<class 'float'>, index=1, value=11.0))
* name field value: 'test', name field index: 0, name field type: <class 'str'>
* unit_price field value: 11.0, unit_price field index: 1, unit_price field type: <class 'float'>
* Index access time: 0.2253845389932394
* Value access time: 0.2729280750500038
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74043760

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档