2013-10-22 20 views
11

我很好奇在Python中定义值对象的好方法。每个维基百科:“value object是一个小对象,表示一个简单的实体,它的相等性不基于身份:即当两个值对象具有相同的值时,它们是相等的,不一定是相同的对象。在Python中,本质上意味着重新定义__eq____hash__方法,以及不变性。如何在Python中定义PyCharm友好的值对象?

标准namedtuple似乎是几乎完美的解决方案,除了它们不像现在的PyCharm之类的Python IDE那么好用。我的意思是,IDE不会真正提供关于定义为namedtuple的类的任何有用的见解。虽然可以使用招这样附加文档字符串这样的类:

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会看到构建新对象时的类型,但不会把握point.x和point.y是花车,所以不会有助于发现他们的滥用。我也不喜欢重新定义“魔法”方法的想法。

所以我在寻找的东西,这将是:

  • 一样简单定义为正常Python类或namedtuple
  • 提供值语义(平等,哈希,不变性)
  • 容易在某种程度上文件将与IDE发挥很好

的理想解决方案看起来是这样的:

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__,在构造函数的参数,在文档字符串并在构造函数体。到目前为止,我不知道如何让它变得不那么笨拙。

+1

请注意,'namedtuple'的主要目的是提供元组接口(索引,解包)和属性访问。它是为了向后兼容用于返回元组的函数而发明的,例如'os.stat'或'time.gmtime'。对于简单的值类型,它可能不是最佳选择。 – user4815162342

+0

关于类型:* PyCharm足够聪明地猜测Point2D“构造函数”的参数,但是类型上它是盲目的*也许你应该使用静态类型语言?在Python中,IDE不应该是一个大问题,因为IDE对类型是盲目的。 – user4815162342

+0

好吧,''ttple''几乎为我做了正确的工作。这绝对是更简单的价值对象,但我可以忍受它。 至于使用静态类型语言,我希望我可以。但我手上有一个Python项目,并寻找让开发更加舒适的方法。而PyCharm已经在使用docstrings推断变量类型方面做得非常出色。 –

回答

3

您的需求尽管经过了仔细的表述,但对我而言并不是很清楚,部分原因是我没有使用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 

如果你愿意,你可以用(大概)提供使用的IDE类型注释提示文档字符串定义自己__init__

+0

我用这个解决方案玩了一段时间。它精确地掌握了价值对象的语义,但与“namedtuple”类型推断有同样的问题。添加'__init__'并没有真正的帮助:没有像'self.a = a'这样的“魔法”字符串,PyCharm不知道它应该将参数的类型声明链接到对象属性。我用你的代码构建了一些适用于我的东西,但它远非完美。我现在将它附加为原始问题的更新。 –

相关问题