数据类描述符字段的类型注释应是什么?

0 投票
1 回答
27 浏览
提问于 2025-04-14 17:41

我正在开发一个类,用户应该能够以最方便的方式设置它的字段,这包括将字符串赋值给任何字段。用户赋值后,这些值应该自动转换成实际的数据类型(比如说,用户把 "2022-01-02" 赋给一个 date 字段时,它应该被转换成 datetime.date 对象)。

为此,我选择了 Python 数据类模块中的 描述符类型字段 方法。为了避免不必要的或不支持的转换,我会检查 __annotations__,以确定是否可以直接赋值而不进行转换。

from typing import Optional

import datetime
from dataclasses import dataclass
from datetime import date
from decimal import Decimal


class Conversion:
    def __init__(self, *, conv, default=None):
        self._conv = conv
        self._default = default
        self._name = None
        self._prop = None

    def __set_name__(self, owner, name):
        self._prop = name
        self._name = "_" + name

    def __get__(self, obj, tp):
        # dataclasses determines default value by calling
        # descriptor.__get__(obj=None, tp=cls)
        if obj is None:
            return self._default

        return getattr(obj, self._name, self._default)

    def __set__(self, obj, value):
        tp = obj.__annotations__.get(self._prop)

        # Don't convert values which already match desired type
        if tp and isinstance(value, tp):
            setattr(obj, self._name, value)
        else:
            try:
                val = self._conv(value)
            except:
                raise ValueError(
                    f"Conversion error for '{self._name.lstrip('_')}': {value}"
                )

            setattr(obj, self._name, val)


@dataclass
class Entry:
    date: datetime.date = Conversion(conv=date.fromisoformat, default=date.today())
    amount: Optional[Decimal] = Conversion(conv=Decimal, default=None)


e = Entry()
print(e)
e.date = "2022-02-05"
e.amount = "11.02"
print(e)

输出结果如预期那样:

Entry(date=datetime.date(2024, 3, 7), amount=None)
Entry(date=datetime.date(2022, 2, 5), amount=Decimal('11.02'))

这个方法效果很好,我觉得这是一个非常干净和优雅的解决方案,但我注意到 文档 总是用描述符的类型来注解描述符类型字段,而不是底层的数据类型。对我来说,这可能是 date: Conversion = Conversion(...)。请问,数据类的作者为什么选择这样做?我这样用数据类型来注解字段是否有问题?

1 个回答

1

从编程的角度来看,在你的例子中,date的默认类型是Conversion,也就是说,使用Conversion这个注解是正确的。不过,像mypy这样的代码检查工具会对你的类型注解提出警告。

不过,类型注解其实是可选的,并不是强制要求的,所以你可以随意选择。在我个人看来,我会避免使用一个非常模糊的名字“Conversion”作为描述符,因为它对不同的数据字段做的事情不一样。尤其是当底层数据类型不同时,这样会让人更困惑。

撰写回答