[英]Correct backwards-compatible way to migrate to modern attrs/cattrs style?
我有一組項目,我從 2020 年初開始使用 attrs v19.3.0 和 cattrs 進行序列化/反序列化。 將這些項目中的類從舊的@attr.s
/ attr.ib
樣式遷移到帶有@define
和@frozen
的現代樣式的正確、完全向后兼容的方法是什么?
我的假設是我可以混合和匹配新舊樣式類,並且總有一種方法可以使用新注釋獲得等效功能。 我一直在一次轉換一個類,在每次轉換后檢查單元測試失敗和 MyPy 警告等。
我現在已經轉移到這段代碼中最復雜的對象層次結構,它也使用 cattrs 進行序列化/反序列化。 我找不到向后兼容的解決方案。 只要我轉換了模塊中的一個類,我的測試套件就會立即失敗,並出現與 cattr 相關的錯誤。
我最初在這里展示了一些實際的代碼,但這太混亂了,沒有用。 我現在已將問題縮小到一個小測試用例。
這是帶有@attr.s
和attr.ib
的舊式類。 使用此代碼,測試用例通過。
from __future__ import annotations
from typing import Dict
import attr
import cattrs
converter = cattrs.Converter()
@attr.s
class Game:
players = attr.ib(type=Dict[str, str])
def copy(self) -> Game:
return converter.structure(converter.unstructure(self), Game)
class TestGame:
def test_copy(self):
game = Game(players={"key" : "value"})
copy = game.copy()
assert copy == game and copy is not game
這是具有等效測試用例的新型類:
from __future__ import annotations
from typing import Dict
import attrs
import cattrs
converter = cattrs.Converter()
@attrs.define
class Game:
players: Dict[str, str]
def copy(self) -> Game:
return converter.structure(converter.unstructure(self), Game)
class TestGame:
def test_copy(self):
game = Game(players={"key" : "value"})
copy = game.copy()
assert copy == game and copy is not game
這失敗並出現以下錯誤:
test_sample.py:15 (TestGame.test_copy)
self = <tests.test_sample.TestGame object at 0x108ab2490>
def test_copy(self):
game = Game(players={"key" : "value"})
> copy = game.copy()
test_sample.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
test_sample.py:13: in copy
return converter.structure(converter.unstructure(self), Game)
../.venv/lib/python3.9/site-packages/cattrs/converters.py:281: in structure
return self._structure_func.dispatch(cl)(obj, cl)
../.venv/lib/python3.9/site-packages/cattrs/converters.py:446: in structure_attrs_fromdict
conv_obj[name] = self._structure_attribute(a, val)
../.venv/lib/python3.9/site-packages/cattrs/converters.py:422: in _structure_attribute
return self._structure_func.dispatch(type_)(value, type_)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <cattrs.converters.Converter object at 0x108a9d6d0>, _ = {'key': 'value'}
cl = 'Dict[str, str]'
def _structure_error(self, _, cl):
"""At the bottom of the condition stack, we explode if we can't handle it."""
msg = "Unsupported type: {0!r}. Register a structure hook for " "it.".format(cl)
> raise StructureHandlerNotFoundError(msg, type_=cl)
E cattrs.errors.StructureHandlerNotFoundError: Unsupported type: 'Dict[str, str]'. Register a structure hook for it.
../.venv/lib/python3.9/site-packages/cattrs/converters.py:344: StructureHandlerNotFoundError
似乎正在發生的事情是 cattrs 不明白players
字段是新樣式類的映射類型字段。
看起來你遇到了這個問題。 有兩種解決方法:
不要使用from __future__ import annotations
。 您的代碼變為:
import attrs import cattrs converter = cattrs.Converter() @attrs.define class Game: players: dict[str, str] def copy(self) -> "Game": return converter.structure(converter.unstructure(self), Game) class TestGame: def test_copy(self): game = Game(players={"key": "value"}) copy = game.copy() assert copy == game and copy is not game
這很丑陋,無論如何都可能在未來打破。
使用GenConverter
而不是Converter
:
from __future__ import annotations import attrs import cattrs converter = cattrs.GenConverter() @attrs.define class Game: players: dict[str, str] def copy(self) -> Game: return converter.structure(converter.unstructure(self), Game) class TestGame: def test_copy(self): game = Game(players={"key": "value"}) copy = game.copy() assert copy == game and copy is not game
這可能是正確的解決方案。
您的示例代碼使用此答案中提供的兩個版本的代碼都通過了測試(在 Python 3.10.4 下測試時)。
(您會注意到我在這里使用dict
而不是typing.Dict
;這只是個人喜好,代碼可以使用任何一種方式。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.