![](/img/trans.png)
[英]What is the proper way to automate use of ElementTree in Python?
[英]What is the proper way to use descriptors as fields in Python dataclasses?
我一直在玩弄 python 数据类并且想知道:制作一个或某些字段描述符的最优雅或最 pythonic 的方法是什么?
在下面的示例中,我定义了一个 Vector2D class,它应该根据其长度进行比较。
from dataclasses import dataclass, field
from math import sqrt
@dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
length: int = field(init=False)
def __post_init__(self):
type(self).length = property(lambda s: sqrt(s.x**2+s.y**2))
Vector2D(3,4) > Vector2D(4,1) # True
虽然这段代码有效,但每次创建实例时它都会触及 class ,是否有更易读/更少 hacky/更有意的方式来一起使用数据类和描述符?
仅将长度作为属性而不是字段就可以了,但这意味着我必须编写__lt__
等。 我自己。
我发现的另一个解决方案同样没有吸引力:
@dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
length: int = field(init=False)
@property
def length(self):
return sqrt(self.x**2+self.y**2)
@length.setter
def length(self, value):
pass
引入一个无操作设置器是必要的,因为显然数据类创建的 init 方法试图分配给length即使没有默认值并且它显式设置init=False
...
当然必须有更好的方法吗?
可能不会回答您的确切问题,但您提到您不想将长度作为属性和非字段的原因是因为您必须
自己写
__lt__
等
虽然你必须自己实现__lt__
,但你实际上可以摆脱实现
from functools import total_ordering
from dataclasses import dataclass, field
from math import sqrt
@total_ordering
@dataclass
class Vector2D:
x: int
y: int
@property
def length(self):
return sqrt(self.x ** 2 + self.y ** 2)
def __lt__(self, other):
if not isinstance(other, Vector2D):
return NotImplemented
return self.length < other.length
def __eq__(self, other):
if not isinstance(other, Vector2D):
return NotImplemented
return self.length == other.length
print(Vector2D(3, 4) > Vector2D(4, 1))
这样做的原因是因为total_ordering
只是添加了基于__eq__
和__lt__
所有其他相等方法
我不认为您提供的示例是您尝试做的事情的好用例。 尽管如此,为了完整起见,以下是您问题的可能解决方案:
@dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
length: int = field(default=property(lambda s: sqrt(s.x**2+s.y**2)), init=False)
这是有效的,因为除非值是列表、字典或集合,否则dataclass
将默认值设置为类属性的值。
尽管您可以手动实现@property
和其他方法,但这会使您失去其他理想的功能,例如在这种情况下使用hash=False
如果您想在dict
中使用Vector2D
。 此外,让它为您实现双下划线方法可以让您的代码更不容易出错,例如您不能忘记return NotImplemented
,这是一个常见的错误。
缺点是实现正确的类型提示并不容易,并且可能会有一些小的注意事项,但是一旦实现了类型提示,它就可以在任何地方轻松使用。
属性(描述符)类型提示:
import sys
from typing import Any, Optional, Protocol, TypeVar, overload
if sys.version_info < (3, 9):
from typing import Type
else:
from builtins import type as Type
IT = TypeVar("IT", contravariant=True)
CT = TypeVar("CT", covariant=True)
GT = TypeVar("GT", covariant=True)
ST = TypeVar("ST", contravariant=True)
class Property(Protocol[CT, GT, ST]):
# Get default attribute from a class.
@overload
def __get__(self, instance: None, owner: Type[Any]) -> CT:
...
# Get attribute from an instance.
def __get__(self, instance: IT, owner: Optional[Type[IT]] = ...) -> GT:
...
def __get__(self, instance, owner=None):
...
def __set__(self, instance: Any, value: ST) -> None:
...
从这里开始,我们现在可以在使用数据类时对property
dataclass
进行类型提示。 如果您需要使用field(...)
中的其他选项,请使用field(default=property(...))
) 。
import sys
import typing
from dataclasses import dataclass, field
from math import hypot
# Use for read-only property.
if sys.version_info < (3, 11):
from typing import NoReturn as Never
else:
from typing import Never
@dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
# Properties return themselves as their default class variable.
# Read-only properties never allow setting a value.
# If init=True, then it would assign self.length = Vector2D.length for the
# default factory.
# Setting repr=False for consistency with init=False.
length: Property[property, float, Never] = field(
default=property(lambda v: hypot(v.x, v.y)),
init=False,
repr=False,
)
v1 = Vector2D(3, 4)
v2 = Vector2D(6, 8)
if typing.TYPE_CHECKING:
reveal_type(Vector2D.length) # builtins.property
reveal_type(v1.length) # builtins.float
assert v1.length == 5.0
assert v2.length == 10.0
assert v1 < v2
在mypy Playground上试试。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.