2013-10-22 2 views
11

私はPythonで値オブジェクトを定義する良い方法について興味があります。 Wikipedia:value objectは、同一性がアイデンティティに基づいていない単純なエンティティを表す小さなオブジェクトです。つまり、2つの値オブジェクトが同じ値を持ち、必ずしも同じオブジェクトである必要はありません。 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 "コンストラクタ"の引数を推測するのに十分なほどスマートですが、それは盲目的です。

このコードでプッシュされ、いくつかの種類の情報を持っているが、それは非常に便利ではありません:PyCharmは、新しいオブジェクトを構築する際に種類が表示されますが、それpoint.xとpoint.yがフロートしている把握しています

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) 

彼らの誤用を検出するのに役立ちません。また、私は、「魔法」の方法を定期的に再定義するという考えを嫌っています。に簡単に

  • 同じように通常の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) 

これは理想的な解決策とはほど遠いものです。 __slots__で、CTOR引数で、ドキュメンテーション文字列内とCTOR本体内:

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 

それはすべての属性を一覧表示するには、4回の合計です:クラスの宣言は次のようになります。これまでのところ私はそれをあまり厄介なものにする方法を知らない。

+1

'namedtuple'はタプルインターフェース(索引付け、アンパック)と属性アクセスの両方を提供することを主な目的としています。これは、 'os.stat'や' time.gmtime'のようなタプルを返す関数の後方互換性のために考案されました。単純な値の型に最適な選択ではない可能性があります。 – user4815162342

+0

タイプについて:* PyCharmは、Point2Dの "コンストラクタ"の引数を推測するのに十分なほどスマートですが、タイプワイズです。*静的に型指定された言語を使用する必要がありますか? Pythonでは、IDEがタイプについて盲目的であるほど大したものであってはなりません。 – user4815162342

+0

さて、 'namedtuple'は私にとって正しい仕事です。それは間違いなく単なる価値オブジェクトですが、私はそれで生きることができます。 静的型付けされた言語を使用することについては、できればと思います。しかし、私はPythonプロジェクトを手にして、開発をより快適にする方法を模索しています。 PyCharmはすでにdocstringを使って変数の型を推論するのにとても良い仕事をしています。 –

答えて

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' 

これは、次の2つの読み取り専用スロットと自動生成されたコンストラクタ、__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

私はしばらくこのソリューションを試していました。値オブジェクトのセマンティクスを正確に把握しますが、名前付きタプルと同じ型推論でも同じ問題があります。 '__init__'を追加することは本当に助けになりません:' self.a = a'のような "魔法のような"文字列なしでPyCharmは引数の型宣言をオブジェクト属性にリンクするべきではないと考えていません。私はあなたのコードを使って私のために役立つものを作りましたが、それは完璧ではありません。私は元の質問への更新として今それを付けています。 –

関連する問題