cor*_*vus 7 python properties python-dataclasses
似乎已经对此进行了相当多的讨论。我发现这篇文章特别有帮助,它似乎提供了最好的解决方案之一。
但推荐的解决方案存在问题。
嗯,一开始看起来效果很好。考虑一个没有属性的简单测试用例:
@dataclass
class Foo:
x: int
Run Code Online (Sandbox Code Playgroud)
>>> # Instantiate the class
>>> f = Foo(2)
>>> # Nice, it works!
>>> f.x
2
Run Code Online (Sandbox Code Playgroud)
x现在尝试使用推荐的解决方案作为属性来实现:
@dataclass
class Foo:
x: int
_x: int = field(init=False, repr=False)
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
Run Code Online (Sandbox Code Playgroud)
>>> # Instantiate while explicitly passing `x`
>>> f = Foo(2)
>>> # Still appears to work
>>> f.x
2
Run Code Online (Sandbox Code Playgroud)
可是等等...
>>> # Instantiate without any arguments
>>> f = Foo()
>>> # Oops...! Property `x` has never been initialized. Now we have a bug :(
>>> f.x
<property object at 0x10d2a8130>
Run Code Online (Sandbox Code Playgroud)
实际上,这里的预期行为是:
>>> # Instantiate without any arguments
>>> f = Foo()
TypeError: __init__() missing 1 required positional argument: 'x'
Run Code Online (Sandbox Code Playgroud)
似乎数据类字段已被属性覆盖...有没有想过如何解决这个问题?
有关的:
在数据类中使用共享方法参数名称的属性__init__会产生有趣的副作用。当类在不带参数的情况下实例化时,该property对象将作为默认值传递。
作为解决方法,您可以使用检查xin的类型__post_init__。
@dataclass
class Foo:
x: int
_x: int = field(init=False, repr=False)
def __post_init__(self):
if isinstance(self.x, property):
raise TypeError("__init__() missing 1 required positional argument: 'x'")
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
Run Code Online (Sandbox Code Playgroud)
现在,在实例化 时Foo,不传递任何参数会引发预期的异常。
f = Foo()
# raises TypeError
f = Foo(1)
f
# returns
Foo(x=1)
Run Code Online (Sandbox Code Playgroud)
这是使用多个属性时的更通用的解决方案。这用于InitVar将参数传递给__post_init__方法。它确实要求首先列出属性,并且它们各自的存储属性具有相同的名称并带有前导下划线。
这非常 hacky,并且属性不再显示在repr.
@dataclass
class Foo:
x: InitVar[int]
y: InitVar[int]
_x: int = field(init=False, repr=False, default=None)
_y: int = field(init=False, repr=False, default=None)
def __post_init__(self, *args):
if m := sum(isinstance(arg, property) for arg in args):
s = 's' if m>1 else ''
raise TypeError(f'__init__() missing {m} required positional argument{s}.')
arg_names = inspect.getfullargspec(self.__class__).args[1:]
for arg_name, val in zip(arg_names, args):
self.__setattr__('_' + arg_name, val)
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@property
def y(self):
return self._y
@y.setter
def y(self, value):
self._y = value
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
10016 次 |
| 最近记录: |