![](/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.