我很好奇在Python中定义值对象的好方法。每个维基百科:“value object是一个小对象,它代表一个简单的实体,它的相等性不是基于同一性的:即当两个值对象具有相同的值时,它们是相等的,不一定是同一个对象”。在Python中,本质上意味着重新定义的__eq__
和__hash__
方法,以及不变性。
标准namedtuple
似乎是几乎完美的解决方案,但它们与PyCharm等现代Python IDE不兼容。我的意思是IDE不会真正提供有关定义为namedtuple
的类的任何有用的见解。虽然可以使用这样的技巧将docstring附加到这样的类:
class Point2D(namedtuple("Point2D", "x y")):
"""Class for immutable value objects"""
pass
根本没有地方可以对构造函数参数进行描述并指定它们的类型。 PyCharm非常聪明,可以猜测Point2D
“构造函数”的参数,但类型方面它是盲目的。
此代码已推入一些类型信息,但它不是很有用:
class Point2D(namedtuple("Point2D", "x y")):
"""Class for immutable value objects"""
def __new__(cls, x, y):
"""
:param x: X coordinate
:type x: float
:param y: Y coordinate
:type y: float
:rtype: Point2D
"""
return super(Point2D, cls).__new__(cls, x, y)
point = Point2D(1.0, 2.0)
PyCharm在构造新对象时会看到类型,但是不会抓住那个点.x和point.y是浮点数,所以不会有助于检测它们的误用。而且我也不喜欢在日常基础上重新定义“神奇”方法的想法。
所以我正在寻找一些可能的东西:
理想的解决方案可能如下所示:
class Point2D(ValueObject):
"""Class for immutable value objects"""
def __init__(self, x, y):
"""
:param x: X coordinate
:type x: float
:param y: Y coordinate
:type y: float
"""
super(Point2D, self).__init__(cls, x, y)
或那:
class Point2D(object):
"""Class for immutable value objects"""
__metaclass__ = ValueObject
def __init__(self, x, y):
"""
:param x: X coordinate
:type x: float
:param y: Y coordinate
:type y: float
"""
pass
我试图找到类似的东西,但没有成功。我认为在我自己实施之前寻求帮助是明智的。
更新: 在user4815162342的帮助下,我设法找到了有效的东西。这是代码:
class ValueObject(object):
__slots__ = ()
def __repr__(self):
attrs = ' '.join('%s=%r' % (slot, getattr(self, slot)) for slot in self.__slots__)
return '<%s %s>' % (type(self).__name__, attrs)
def _vals(self):
return tuple(getattr(self, slot) for slot in self.__slots__)
def __eq__(self, other):
if not isinstance(other, ValueObject):
return NotImplemented
return self.__slots__ == other.__slots__ and self._vals() == other._vals()
def __ne__(self, other):
return not self == other
def __hash__(self):
return hash(self._vals())
def __getstate__(self):
"""
Required to pickle classes with __slots__
Must be consistent with __setstate__
"""
return self._vals()
def __setstate__(self, state):
"""
Required to unpickle classes with __slots__
Must be consistent with __getstate__
"""
for slot, value in zip(self.__slots__, state):
setattr(self, slot, value)
离理想的解决方案还很远。类声明如下所示:
class X(ValueObject):
__slots__ = "a", "b", "c"
def __init__(self, a, b, c):
"""
:param a:
:type a: int
:param b:
:type b: str
:param c:
:type c: unicode
"""
self.a = a
self.b = b
self.c = c
列出所有属性共计四次:在__slots__
中,在ctor参数中,在docstring中和在ctor体中。到目前为止,我不知道如何减少它的尴尬。
答案 0 :(得分:4)
您的要求虽然经过深思熟虑,但对我来说并不十分清楚,部分原因是我不使用PyCharm GUI。但这是一次尝试:
class ValueObject(object):
__slots__ = ()
def __init__(self, *vals):
if len(vals) != len(self.__slots__):
raise TypeError, "%s.__init__ accepts %d arguments, got %d" \
% (type(self).__name__, len(self.__slots__), len(vals))
for slot, val in zip(self.__slots__, vals):
super(ValueObject, self).__setattr__(slot, val)
def __repr__(self):
return ('<%s[0x%x] %s>'
% (type(self).__name__, id(self),
' '.join('%s=%r' % (slot, getattr(self, slot))
for slot in self.__slots__)))
def _vals(self):
return tuple(getattr(self, slot) for slot in self.__slots__)
def __eq__(self, other):
if not isinstance(other, ValueObject):
return NotImplemented
return self.__slots__ == other.__slots__ and self._vals() == other._vals()
def __ne__(self, other):
return not self == other
def __hash__(self):
return hash(self._vals())
def __setattr__(self, attr, val):
if attr in self.__slots__:
raise AttributeError, "%s slot '%s' is read-only" % (type(self).__name__, attr)
super(ValueObject, self).__setattr__(attr, val)
用法是这样的:
class X(ValueObject):
__slots__ = 'a', 'b'
这将为您提供一个具体的值类,其中包含两个只读插槽和一个自动生成的构造函数__eq__
和__hash__
。例如:
>>> x = X(1.0, 2.0, 3.0)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 5, in __init__
TypeError: X.__init__ accepts 2 arguments, got 3
>>> x = X(1.0, 2.0)
>>> x
<X[0x4440a50] a=1.0 b=2.0>
>>> x.a
1.0
>>> x.b
2.0
>>> x.a = 10
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 32, in __setattr__
AttributeError: X slot 'a' is read-only
>>> x.c = 10
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 33, in __setattr__
AttributeError: 'X' object has no attribute 'c'
>>> dir(x)
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_vals', 'a', 'b']
>>> x == X(1.0, 2.0)
True
>>> x == X(1.0, 3.0)
False
>>> hash(x)
3713081631934410656
>>> hash(X(1.0, 2.0))
3713081631934410656
>>> hash(X(1.0, 3.0))
3713081631933328131
如果需要,您可以使用docstring定义自己的__init__
,该文档字符串(可能)为您的IDE提供类型注释提示。
答案 1 :(得分:2)
<Hosts>
<Host Name="Document" />
</Hosts>
<DefaultSettings>
<SourceLocation DefaultValue="https://<somedomain>.com//App/Home.html" />
</DefaultSettings>
模块和typing
在版本3.5中,添加了typing
模块,您将找到一个完全适合您需求的类。
NamedTuple
它的工作与您期望的一样:
注意:
截至今天,该API仍位于provisional stage中。这意味着在发布新版本时,不能保证它向后兼容。但是,不希望更改接口。我个人认为:鉴于设计的简单性,如果有更改,我相信这将是一个容易的重构;)
答案 2 :(得分:2)
更新解决方案:
从python 3.7开始,有一个名为dataclasses
的内置模块,其中包含dataclass
类。
Pycharm支持它,并且知道如何使用它。
它非常适合值对象,因为它已经定义了很多要为值对象定义的内容,并且语法非常简短:
Frozen=True
使类不可变示例:
from dataclasses import dataclass
@dataclass(frozen=True)
class Point2D:
x: float
y: float
NamedTuple
和Dataclasses
之间存在差异,最明显的是一个基于元组,另一个基于字典。
我认为dataclass
是创建值对象的最佳代码生成器ATM。
有关更多信息,请继续使用python的docs来了解如何使用dataclass
。