数据类字段的工厂函数
Factory function for dataclass fields
我正在创建一个库,我想在其中利用数据字段上的元数据class。
为了得到我想要的结果,我可以像下面这样写数据class:
@dataclass
class Foo:
a: int = field(
metadata={'my_metadata': {'my_required_key': "c"}}
)
b: dict[str, str] = field(
metadata={'my_metadata': {'my_required_key': "d"}}, default_factory=dict
)
这似乎有很多样板文件,特别是如果我想制作许多 classes 具有许多这样的字段。我在想我可以写一个工厂函数来包装 dataclass.field
并帮助减少重复量。
但是,我似乎无法获得调用 dataclass.field
的正确类型参数,并且响应类型的正确值对我来说是个谜。我目前拥有的:
from dataclasses import dataclass, field, MISSING, _MISSING_TYPE
from typing import TypeVar, Union, Callable
_T = TypeVar("_T")
def myfield(
my_required_key: str,
*,
default: Union[_MISSING_TYPE, _T] = MISSING,
default_factory: Union[_MISSING_TYPE, Callable[[], _T]] = MISSING
) -> _T:
return field( # type: ignore
metadata={'my_metadata': {'my_required_key': my_required_key}},
default=default,
default_factory=default_factory,
)
@dataclass
class Foo:
a: int = myfield("c")
b: dict[str, str] = myfield("d", default_factory=dict)
此代码将通过 mypy
验证,但 PyCharm 似乎不喜欢它,报告:
Mutable default 'myfield("d", default_factory=dict)' is not allowed. Use 'default_factory'`
我可以忽略 PyCharm 错误,因为 class 似乎可以正常运行,而且我在我的 CICD 中使用 mypy
,这似乎很酷.
至于return类型,我目前有myfield(...) -> _T
。我觉得签名应该看起来更像 myfield(...) -> Field[_T]
,但 mypy
拒绝了这个想法,并报告:
error: Incompatible types in assignment (expression has type "Field[<nothing>]", variable has type "int")
error: Incompatible types in assignment (expression has type "Field[Dict[_KT, _VT]]", variable has type "Dict[str, str]")
我也不确定如何输入 default
和 default_factory
参数。没有 # type: ignore
我会得到:
error: No overload variant of "field" matches argument types "Dict[str, Dict[str, str]]", "Union[_MISSING_TYPE, _T]", "Union[_MISSING_TYPE, Callable[[], _T]]"
note: Possible overload variants:
note: def [_T] field(*, default: _T, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> _T
note: def [_T] field(*, default_factory: Callable[[], _T], init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> _T
note: def field(*, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> Any
我看到其他库已经开始通过仅制作工厂方法来减少样板代码 return 元数据字典。即
@dataclass
class Fizz:
a: int = field(metadata=myfield("c"))
b: dict[str, str] = field(metadata=myfield("d"), default_factory=dict)
这对我来说还是有点难看,但也许这就是要走的路。
如能提供任何有关清理此问题的帮助或想法,我们将不胜感激!
您已经在包装器中“截断”了 fields
的完整签名;我会更进一步:
def myfield(required_key: str, **kwargs):
kwargs['metadata'] = dict(my_metadata=dict(my_required_key=required_key))
return field(**kwargs)
不过,这种间接级别似乎可以防止 mypy
检查传递给 myfield
的参数是否具有 field
所期望的正确类型。
或者,本着“优先组合胜过继承”的精神,只需编写一个函数来创建正确的元数据以用作 field
的参数。这使您的用户可以直接调用 field
,而不必重复其 intricate hinting。
def make_metadata(required_key: str):
return dict(my_metadata(dict(my_required_key=required_key)))
@dataclass
class Foo:
a: int = field(metadata=make_metadata("c"))
b: dict[str, str] = field(metadata=make_metadata("d"), default_factory=dict)
还有一些样板文件,但少了。
在某种程度上,存在一个不可避免的权衡,涉及您可以绑定到动态类型语言上的静态类型的数量,或者更确切地说,类型是如何绑定的。您会在 field
的类型提示中看到 overload
的使用,但 overload
不会 做 任何事情。它只是在源代码中放置注释以供 mypy
分析的地方;它无论如何都不会改变它的目标(事实上,它只是丢弃它,因为目的是稍后重新定义它)。这就是为什么仅使用 ...
来“实现”暗示的变体,因为主体并不重要:您永远不会使用正在定义的 function
对象,只会使用最终的未修饰函数。
我会建议直接在 Field
对象上设置元数据,除了元数据是唯一涉及 simple assignment in Field.__init__
以外的属性:
def __init__(self, default, default_factory, init, repr, hash, compare,
metadata):
[...]
self.metadata = (_EMPTY_METADATA
if metadata is None else
types.MappingProxyType(metadata))
[...]
从“更喜欢组合...”中撤退,如果 Field
直接公开就好了,这样你就可以像
那样子类化它
class MyField(Field):
def set_metadata(self, key: str):
self.metadata = types.MappingProxyType(dict(...))
return self
并使用
@dataclass
class Foo:
a: int = Field().set_metadata("c")
b: dict[str, str] = Field(default_factory=dict).set_metadata("d")
并不是说我提倡像这样直接使用Field
,但是....
此外,据我所知,MappingProxyType
仅用于将元数据设置为只读。如果您不介意放松 Field
对象的那部分...
@dataclass
class Foo:
a: int = field()
make_metadata(a, "c")
b: dict[str, str] = field(default_factory=dict)
make_metadata(b, "d")
Return myfield
类型
关于 return 类型应该是 _T
还是 Field[_T]
,值得注意的是 typeshed 库——所有主要类型检查器都使用的存根文件的存储库用于检查标准库——仅使用 _T
作为 return 类型。事实上,源代码中有一个非常有启发性的注释说明了为什么会这样:
# NOTE: Actual return type is 'Field[_T]', but we want to help type checkers
# to understand the magic that happens at runtime.
如果它对 typeshed 来说足够好,我想说它可能对你来说足够好!
PyCharm
在这个阶段,我对 PyCharm 错误的类型检查感到非常恼火,所以我建议您忽略有关可变默认字段的恼人消息。但是,这并不是特别有用,因为您正在编写一个库,而您的库的许多用户将使用 PyCharm。不幸的是,看起来并没有真正好的解决方案,因为这是 known bug 在 PyCharm 对数据类 field
s 的类型检查方面比这个特定问题要广泛得多.
类型:忽略
我认为在您的代码中使用单个 # type: ignore
没有什么不妥。没有类型检查器是完美的,无论如何它都是一个近似值,因为 Python 从根本上说是一种动态语言。对于 MyPy,您可以更具体地告诉它只忽略某些类型的错误(--show-error-codes 选项对于了解引发哪种 MyPy 异常非常有用)。在这种情况下,错误是 MyPy call-overload
错误,因此您可以将 # type: ignore
更改为 # type: ignore[call-overload]
。 (遗憾的是,即使在 评论 中使用这种语法也会对 PyCharm 的 linter 造成严重破坏。老实说,我不知道为什么。)
参考资料
这是截至 2021 年 8 月 13 日 dataclasses.field
函数的 full hint:
# NOTE: Actual return type is 'Field[_T]', but we want to help type checkers
# to understand the magic that happens at runtime.
if sys.version_info >= (3, 10):
@overload # `default` and `default_factory` are optional and mutually exclusive.
def field(
*,
default: _T,
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
kw_only: bool = ...,
) -> _T: ...
@overload
def field(
*,
default_factory: Callable[[], _T],
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
kw_only: bool = ...,
) -> _T: ...
@overload
def field(
*,
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
kw_only: bool = ...,
) -> Any: ...
else:
@overload # `default` and `default_factory` are optional and mutually exclusive.
def field(
*,
default: _T,
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
) -> _T: ...
@overload
def field(
*,
default_factory: Callable[[], _T],
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
) -> _T: ...
@overload
def field(
*,
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
) -> Any: ...
我正在创建一个库,我想在其中利用数据字段上的元数据class。
为了得到我想要的结果,我可以像下面这样写数据class:
@dataclass
class Foo:
a: int = field(
metadata={'my_metadata': {'my_required_key': "c"}}
)
b: dict[str, str] = field(
metadata={'my_metadata': {'my_required_key': "d"}}, default_factory=dict
)
这似乎有很多样板文件,特别是如果我想制作许多 classes 具有许多这样的字段。我在想我可以写一个工厂函数来包装 dataclass.field
并帮助减少重复量。
但是,我似乎无法获得调用 dataclass.field
的正确类型参数,并且响应类型的正确值对我来说是个谜。我目前拥有的:
from dataclasses import dataclass, field, MISSING, _MISSING_TYPE
from typing import TypeVar, Union, Callable
_T = TypeVar("_T")
def myfield(
my_required_key: str,
*,
default: Union[_MISSING_TYPE, _T] = MISSING,
default_factory: Union[_MISSING_TYPE, Callable[[], _T]] = MISSING
) -> _T:
return field( # type: ignore
metadata={'my_metadata': {'my_required_key': my_required_key}},
default=default,
default_factory=default_factory,
)
@dataclass
class Foo:
a: int = myfield("c")
b: dict[str, str] = myfield("d", default_factory=dict)
此代码将通过 mypy
验证,但 PyCharm 似乎不喜欢它,报告:
Mutable default 'myfield("d", default_factory=dict)' is not allowed. Use 'default_factory'`
我可以忽略 PyCharm 错误,因为 class 似乎可以正常运行,而且我在我的 CICD 中使用 mypy
,这似乎很酷.
至于return类型,我目前有myfield(...) -> _T
。我觉得签名应该看起来更像 myfield(...) -> Field[_T]
,但 mypy
拒绝了这个想法,并报告:
error: Incompatible types in assignment (expression has type "Field[<nothing>]", variable has type "int")
error: Incompatible types in assignment (expression has type "Field[Dict[_KT, _VT]]", variable has type "Dict[str, str]")
我也不确定如何输入 default
和 default_factory
参数。没有 # type: ignore
我会得到:
error: No overload variant of "field" matches argument types "Dict[str, Dict[str, str]]", "Union[_MISSING_TYPE, _T]", "Union[_MISSING_TYPE, Callable[[], _T]]"
note: Possible overload variants:
note: def [_T] field(*, default: _T, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> _T
note: def [_T] field(*, default_factory: Callable[[], _T], init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> _T
note: def field(*, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> Any
我看到其他库已经开始通过仅制作工厂方法来减少样板代码 return 元数据字典。即
@dataclass
class Fizz:
a: int = field(metadata=myfield("c"))
b: dict[str, str] = field(metadata=myfield("d"), default_factory=dict)
这对我来说还是有点难看,但也许这就是要走的路。
如能提供任何有关清理此问题的帮助或想法,我们将不胜感激!
您已经在包装器中“截断”了 fields
的完整签名;我会更进一步:
def myfield(required_key: str, **kwargs):
kwargs['metadata'] = dict(my_metadata=dict(my_required_key=required_key))
return field(**kwargs)
不过,这种间接级别似乎可以防止 mypy
检查传递给 myfield
的参数是否具有 field
所期望的正确类型。
或者,本着“优先组合胜过继承”的精神,只需编写一个函数来创建正确的元数据以用作 field
的参数。这使您的用户可以直接调用 field
,而不必重复其 intricate hinting。
def make_metadata(required_key: str):
return dict(my_metadata(dict(my_required_key=required_key)))
@dataclass
class Foo:
a: int = field(metadata=make_metadata("c"))
b: dict[str, str] = field(metadata=make_metadata("d"), default_factory=dict)
还有一些样板文件,但少了。
在某种程度上,存在一个不可避免的权衡,涉及您可以绑定到动态类型语言上的静态类型的数量,或者更确切地说,类型是如何绑定的。您会在 field
的类型提示中看到 overload
的使用,但 overload
不会 做 任何事情。它只是在源代码中放置注释以供 mypy
分析的地方;它无论如何都不会改变它的目标(事实上,它只是丢弃它,因为目的是稍后重新定义它)。这就是为什么仅使用 ...
来“实现”暗示的变体,因为主体并不重要:您永远不会使用正在定义的 function
对象,只会使用最终的未修饰函数。
我会建议直接在 Field
对象上设置元数据,除了元数据是唯一涉及 simple assignment in Field.__init__
以外的属性:
def __init__(self, default, default_factory, init, repr, hash, compare,
metadata):
[...]
self.metadata = (_EMPTY_METADATA
if metadata is None else
types.MappingProxyType(metadata))
[...]
从“更喜欢组合...”中撤退,如果 Field
直接公开就好了,这样你就可以像
class MyField(Field):
def set_metadata(self, key: str):
self.metadata = types.MappingProxyType(dict(...))
return self
并使用
@dataclass
class Foo:
a: int = Field().set_metadata("c")
b: dict[str, str] = Field(default_factory=dict).set_metadata("d")
并不是说我提倡像这样直接使用Field
,但是....
此外,据我所知,MappingProxyType
仅用于将元数据设置为只读。如果您不介意放松 Field
对象的那部分...
@dataclass
class Foo:
a: int = field()
make_metadata(a, "c")
b: dict[str, str] = field(default_factory=dict)
make_metadata(b, "d")
Return myfield
关于 return 类型应该是 _T
还是 Field[_T]
,值得注意的是 typeshed 库——所有主要类型检查器都使用的存根文件的存储库用于检查标准库——仅使用 _T
作为 return 类型。事实上,源代码中有一个非常有启发性的注释说明了为什么会这样:
# NOTE: Actual return type is 'Field[_T]', but we want to help type checkers
# to understand the magic that happens at runtime.
如果它对 typeshed 来说足够好,我想说它可能对你来说足够好!
PyCharm
在这个阶段,我对 PyCharm 错误的类型检查感到非常恼火,所以我建议您忽略有关可变默认字段的恼人消息。但是,这并不是特别有用,因为您正在编写一个库,而您的库的许多用户将使用 PyCharm。不幸的是,看起来并没有真正好的解决方案,因为这是 known bug 在 PyCharm 对数据类 field
s 的类型检查方面比这个特定问题要广泛得多.
类型:忽略
我认为在您的代码中使用单个 # type: ignore
没有什么不妥。没有类型检查器是完美的,无论如何它都是一个近似值,因为 Python 从根本上说是一种动态语言。对于 MyPy,您可以更具体地告诉它只忽略某些类型的错误(--show-error-codes 选项对于了解引发哪种 MyPy 异常非常有用)。在这种情况下,错误是 MyPy call-overload
错误,因此您可以将 # type: ignore
更改为 # type: ignore[call-overload]
。 (遗憾的是,即使在 评论 中使用这种语法也会对 PyCharm 的 linter 造成严重破坏。老实说,我不知道为什么。)
参考资料
这是截至 2021 年 8 月 13 日 dataclasses.field
函数的 full hint:
# NOTE: Actual return type is 'Field[_T]', but we want to help type checkers
# to understand the magic that happens at runtime.
if sys.version_info >= (3, 10):
@overload # `default` and `default_factory` are optional and mutually exclusive.
def field(
*,
default: _T,
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
kw_only: bool = ...,
) -> _T: ...
@overload
def field(
*,
default_factory: Callable[[], _T],
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
kw_only: bool = ...,
) -> _T: ...
@overload
def field(
*,
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
kw_only: bool = ...,
) -> Any: ...
else:
@overload # `default` and `default_factory` are optional and mutually exclusive.
def field(
*,
default: _T,
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
) -> _T: ...
@overload
def field(
*,
default_factory: Callable[[], _T],
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
) -> _T: ...
@overload
def field(
*,
init: bool = ...,
repr: bool = ...,
hash: bool | None = ...,
compare: bool = ...,
metadata: Mapping[Any, Any] | None = ...,
) -> Any: ...