2013-10-22 1 views
11

파이썬에서 값 객체를 정의하는 좋은 방법에 대해 궁금합니다. 위키 피 디아 : "value object은 동일성이 동일성을 기반으로하지 않는 간단한 엔티티를 나타내는 작은 객체입니다. 즉, 두 값 객체가 동일한 값을 가질 때 동일하며 동일한 객체가 아니어도됩니다. 파이썬에서는 본질적으로 변하지 않는 것은 물론 __eq____hash__ 메서드를 다시 정의하는 것을 의미합니다.Python에서 PyCharm 친화적 인 값 객체를 정의하는 방법은 무엇입니까?

표준 namedtuple은 PyCharm과 같은 최신 Python IDE에서 잘 돌아 가지 않는 거의 완벽한 솔루션처럼 보입니다. IDE가 namedtuple으로 정의 된 클래스에 대한 유용한 통찰력을 제공하지 않는다는 의미입니다. 그것은 가능하지만이 같은 트릭을 사용하여 같은 클래스에 문서화 문자열을 연결하려면 :

class Point2D(namedtuple("Point2D", "x y")): 
    """Class for immutable value objects""" 
    pass 

단순히 생성자 인자의 설명을 넣어 자신의 유형을 지정하는 곳이 없습니다. PyCharm은 Point2D "constructor"에 대한 인수를 추측 할 수있을만큼 똑똑하지만 타입 상 현명합니다. ,

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을 파악하지 않습니다

이 코드는 어떤 종류의 정보가 밀어 가지고 있지만, 그것은 매우 유용 아니다 그들의 오용을 감지하는 데 도움이되지는 않을 것입니다. 그리고 나는 또한 일상적으로 "마법"방법을 재정의한다는 생각을 싫어한다.

그래서 나는 것입니다 뭔가를 찾고 있어요 :

  • 일반 파이썬 클래스 또는 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__에서의 ctor 인수에 문서화 문자열과의 ctor 본문에. 지금까지 나는 그것을 덜 어색하게 만드는 방법을 모른다.

+1

'namedtuple'은 튜플 인터페이스 (색인화, 압축 해제)와 속성 액세스를 모두 제공한다는 주된 목적을 가지고 있습니다. 'os.stat' 나'time.gmtime'과 같은 튜플을 반환하는 함수의 하위 호환성을 위해 개발되었습니다. 단순한 값 유형에 대해서는 최적의 선택이 아닐 수도 있습니다. – user4815162342

+0

관련 유형 : * PyCharm은 Point2D "constructor"에 대한 인수를 추측 할만큼 똑똑하지만 형식이 잘못되었습니다. * 정적으로 입력 된 언어를 사용해야합니까? 파이썬에서는 IDE가 타입에 관해서 눈이 멀지는 않는다. – user4815162342

+0

글쎄,'namedtuple'은 나에게 맞는 직업이다. 그것은 분명히 단순한 가치 객체이지만, 나는 그것과 함께 살 수 있습니다. 정적 유형 언어를 사용하는 것에 관해서는 할 수 있었으면합니다. 그러나 나는 손에 파이썬 프로젝트를 가지고 있으며 개발을보다 편하게 할 수있는 방법을 찾고 있습니다. 그리고 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' 

이 당신이 읽기 전용 슬롯과 자동 생성 생성자, __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은 인자의 타입 선언을 객체 속성에 연결해야한다는 것을 모릅니다. 나는 당신의 코드를 사용하여 나를 위해 일할 무언가를 만들었지 만, 완벽하지는 않다. 나는 그것을 원래의 질문에 대한 업데이트로 붙일 것이다. –