簡體   English   中英

Python attrs/cattrs 使用凍結的屬性類作為字典鍵序列化字典

[英]Python attrs/cattrs serializing a dict using frozen attrs classes as the dict keys

我想構造和attrs對象,其中包括使用簡單凍結屬性作為 dict 鍵的dict字段。 這對於在運行時創建的對象非常有效,但frozen 屬性無法使使用 cattrs 輕松取消/結構化。

這是問題的一個簡單示例:

import attr, cattr

# Simple attr that contains only a single primitive data type.
@attr.s(frozen=True)
class AbstractID:
    _id: Optional[int] = attr.ib()

    def __str__(self) -> str:
        if self._id is not None:
            return f"A{self._id}"
        else:
            return "—"


@attr.s(auto_attribs=True)
class Database:
    storage: dict[AbstractID, str] = {}

# Attempt to unstructure using cattrs
db = Database()
db.storage[AbstractID(1)] = "some data"

cattr.unstructure(db)

>>> TypeError: unhashable type: 'dict'

在導入/導出過程之外,是否有某種方法可以在不使用 int 或 str 作為 dict 鍵的情況下序列化數據? 我看到 cattrs 提供了用於自定義序列化過程的鈎子,但是我不知道如何在解構時將 AbstractID 減少為 int,或者如何將其重新構造為 AbstractID。

這能做到嗎?

好吧,你總是可以用棉花糖來做這樣的事情。 它允許您通過模式完全自定義流程。 無論如何,將序列化/反序列化與業務邏輯分開通常是個好主意。 因此,對於您的示例,它可能如下所示:

from typing import Any
from marshmallow import Schema, fields, post_dump, pre_load, post_load

class AbstractIdSchema(Schema):
    _id = fields.Integer()

    @pre_load
    def pre_load(self, obj: int, **_: Any) -> dict:
        return {'_id': obj}

    @post_load
    def post_load(self, data: dict, **_: Any) -> AbstractID:
        return AbstractID(id=data['_id'])

    @post_dump
    def post_dump(self, data: dict, **_) -> int:
        return data['_id']

class DatabaseSchema(Schema):
    storage = fields.Dict(
        keys=fields.Nested(AbstractIdSchema()),
        values=fields.String(),
    )

    @post_load
    def post_load(self, data: dict, **_: Any) -> Database:
        return Database(**data)

print(db)
db_schema = DatabaseSchema()
serialized_db = db_schema.dump(db)
print(serialized_db)
deserialized_db = db_schema.load(serialized_db)
print(deserialized_db)

# Prints:
# Database(storage={AbstractID(_id=1): 'some data'})
# {'storage': {1: 'some data'}}
# Database(storage={AbstractID(_id=1): 'some data'})

如果_id只是簡單的id (即 init arg 與屬性相同),它看起來會更簡單一些 - 那么你可以在post_load執行AbstractID(**data)

再說一次,如果你的模型真的那么簡單,這可能是一種矯枉過正。 但如果現實更復雜,那么它可能是要走的路。

默認方法失敗,因為它試圖生成:

{"storage": {{"_id": 1}: "some_data"}

並且 Python dicts 不支持其他 dicts 作為鍵。

由於我們將自定義行為,因此我們將使用轉換器的單獨實例。 我還將使用新的 attrs API,因為它們更干凈。 這是您想要執行的操作:

from typing import Optional

from attr import define, frozen, Factory

from cattr import GenConverter


# Simple attr that contains only a single primitive data type.
@frozen
class AbstractID:
    _id: Optional[int]

    def __str__(self) -> str:
        if self._id is not None:
            return f"A{self._id}"
        else:
            return "—"


@define
class Database:
    storage: dict[AbstractID, str] = Factory(dict)


# Attempt to unstructure using cattrs
db = Database()
db.storage[AbstractID(1)] = "some data"

c = GenConverter()
c.register_unstructure_hook(AbstractID, lambda aid: aid._id)
c.register_structure_hook(AbstractID, lambda v, _: AbstractID(v))

print(c.unstructure(db))  # {'storage': {1: 'some data'}}
print(c.structure(c.unstructure(db), Database))  # Database(storage={AbstractID(_id=1): 'some data'})

cattrs使這些東西的工作變得容易。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM