[python] @property와 getter 및 setter 사용

다음은 순수한 Python 전용 디자인 질문입니다.

class MyClass(object):
    ...
    def get_my_attr(self):
        ...

    def set_my_attr(self, value):
        ...

class MyClass(object):
    ...        
    @property
    def my_attr(self):
        ...

    @my_attr.setter
    def my_attr(self, value):
        ...

파이썬으로 우리는 어느 쪽이든 할 수 있습니다. 파이썬 프로그램을 디자인한다면, 어떤 접근법을 사용해야하며 왜 그런가?



답변

속성을 선호하십시오 . 그들이 거기있는 것입니다.

그 이유는 모든 속성이 파이썬에서 공개되기 때문입니다. 밑줄 또는 두 개로 이름을 시작하면 주어진 속성이 향후 버전의 코드에서 동일하게 유지되지 않을 수있는 구현 세부 사항이라는 경고 일뿐입니다. 실제로 해당 속성을 가져 오거나 설정하는 것을 방해하지는 않습니다. 따라서 표준 속성 액세스는 속성에 액세스하는 일반적인 파이썬 방식입니다.

속성의 장점은 속성 액세스와 구문 상 동일하므로 클라이언트 코드를 변경하지 않고도 서로 변경할 수 있다는 것입니다. 속성을 사용하는 (예 : 계약 별 또는 디버깅 용) 하나의 클래스 버전과 사용하지 않는 클래스를 사용할 수도 있습니다 (예 : 코드 별 또는 디버깅). 동시에 나중에 액세스를 더 잘 제어해야하는 경우를 대비하여 모든 것에 대해 getter 및 setter를 작성할 필요가 없습니다.


답변

파이썬에서는 재미를 위해서 getter 또는 setter 또는 속성을 사용하지 않습니다. 먼저 속성을 사용한 다음 나중에 필요한 경우에만 클래스를 사용하여 코드를 변경하지 않고도 속성으로 마이그레이션합니다.

getter와 setter, 상속 및 무의미한 클래스를 사용하는 확장명이 .py 인 많은 코드가 있습니다. 예를 들어 간단한 튜플이 수행하는 곳은 파이썬을 사용하여 C ++ 또는 Java로 작성하는 사람들의 코드입니다.

그것은 파이썬 코드가 아닙니다.


답변

속성을 사용하면 일반 속성 액세스로 시작한 다음 필요에 따라 나중에 getter 및 setter백업 할 수 있습니다.


답변

짧은 대답은 : properties wins down . 항상.

때때로 게터와 세터가 필요하지만 그때까지는 외부 세계에 “숨길”것입니다. 이 파이썬에서이 작업을 수행하는 방법 (충분히있다 getattr, setattr, __getattribute__, 등 …하지만, 매우 간결하고 깨끗한 하나입니다 :

def set_email(self, value):
    if '@' not in value:
        raise Exception("This doesn't look like an email address.")
    self._email = value

def get_email(self):
    return self._email

email = property(get_email, set_email)

다음 은 파이썬에서 게터와 세터에 대한 주제를 소개 하는 간단한 기사 입니다.


답변

[ TL; DR? 코드 예제의 끝으로 건너 뛸 수 있습니다 .]

나는 실제로 다른 관용구를 사용하는 것을 선호합니다.이 관용구는 일회용으로 사용하는 데 약간 관여하지만 더 복잡한 사용 사례가 있으면 좋습니다.

먼저 약간의 배경.

속성은 프로그램 방식으로 설정 및 가져 오기 값을 처리 할 수 ​​있지만 속성을 속성으로 액세스 할 수 있다는 점에서 유용합니다. ‘gets’를 ‘computations'(본질적으로)로 바꾸고 ‘sets’를 ‘events’로 바꿀 수 있습니다. Java와 유사한 게터와 세터로 코딩 한 다음 클래스가 있다고 가정하겠습니다.

class Example(object):
    def __init__(self, x=None, y=None):
        self.x = x
        self.y = y

    def getX(self):
        return self.x or self.defaultX()

    def getY(self):
        return self.y or self.defaultY()

    def setX(self, x):
        self.x = x

    def setY(self, y):
        self.y = y

    def defaultX(self):
        return someDefaultComputationForX()

    def defaultY(self):
        return someDefaultComputationForY()

내가 전화하지 않은 이유 defaultXdefaultY객체의 __init__방법을 궁금해 할 것 입니다. 그 이유는 someDefaultComputation시간이 지남에 따라 시간이 지남에 따라 값이 변하는 값을 반환하고 x(또는 y)가 설정되지 않은 경우 (이 예제의 목적 상 “not set”은 “set”을 의미 한다고 가정하고 싶습니다. 없음 “)로 I는 다음의 값과 원하는 x의 (또는 y의) 기본 계산을.

따라서 이것은 위에서 설명한 여러 가지 이유로 불충분합니다. 속성을 사용하여 다시 작성하겠습니다.

class Example(object):
    def __init__(self, x=None, y=None):
        self._x = x
        self._y = y

    @property
    def x(self):
        return self.x or self.defaultX()

    @x.setter
    def x(self, value):
        self._x = value

    @property
    def y(self):
        return self.y or self.defaultY()

    @y.setter
    def y(self, value):
        self._y = value

    # default{XY} as before.

우리는 무엇을 얻었습니까? 우리는 이러한 속성을 속성으로 참조 할 수있는 능력을 얻었습니다.

물론 속성의 진정한 힘은 일반적으로 이러한 메소드가 값을 가져오고 설정하는 것 외에도 무언가를 수행하기를 원한다는 것입니다 (그렇지 않으면 속성 사용에 아무런 의미가 없습니다). 나는 getter 예제에서 이것을했다. 우리는 기본적으로 값이 설정되지 않을 때마다 기본값을 선택하기 위해 함수 본문을 실행하고 있습니다. 이것은 매우 일반적인 패턴입니다.

그러나 우리는 무엇을 잃고 있으며, 무엇을 할 수 없습니까?

필자의 견해로는, 우리가 getter를 정의하면 (여기서와 같이) setter도 정의해야한다. [1] 그것은 코드를 어지럽히는 여분의 소음입니다.

또 다른 문제는에서 xy값 을 초기화해야한다는 것 입니다 __init__. (물론 우리는 그것을 사용하여 추가 할 수 setattr()있지만 더 많은 코드입니다.)

셋째, Java와 유사한 예와 달리 getter는 다른 매개 변수를 승인 할 수 없습니다. 이제 매개 변수를 취하고 있다면 게터가 아닙니다. 공식적인 의미에서 그것은 사실입니다. 그러나 실제로는 명명 된 속성을 매개 변수화하거나 x특정 매개 변수에 대한 값을 설정할 수없는 이유가 없습니다 .

다음과 같은 일을 할 수 있다면 좋을 것입니다.

e.x[a,b,c] = 10
e.x[d,e,f] = 20

예를 들어. 우리가 얻을 수있는 가장 가까운 것은 할당을 재정 의하여 특별한 의미를 내포하는 것입니다.

e.x = [a,b,c,10]
e.x = [d,e,f,30]

물론 세터가 처음 세 값을 사전의 키로 추출하고 그 값을 숫자 또는 다른 것으로 설정하는 방법을 알고 있는지 확인하십시오.

그러나 우리가 그렇게하더라도 매개 변수를 getter에 전혀 전달할 수 없으므로 값을 얻을 수있는 방법이 없기 때문에 여전히 속성으로 지원할 수 없었습니다. 그래서 우리는 비대칭을 도입하여 모든 것을 반환해야했습니다.

Java 스타일의 getter / setter를 사용하면이를 처리 할 수 ​​있지만 getter / setter가 필요합니다.

우리가 정말로 원하는 것은 다음 요구 사항을 충족시키는 것입니다.

  • 사용자는 주어진 속성에 대해 하나의 방법 만 정의하며 속성이 읽기 전용인지 읽기 쓰기인지를 표시 할 수 있습니다. 속성이 쓰기 가능한 경우 속성이이 테스트에 실패합니다.

  • 사용자가 함수의 기본 변수를 추가로 정의 할 필요가 없으므로 __init__또는 setattr코드 가 필요하지 않습니다 . 변수는 우리가이 새로운 스타일의 속성을 만들었 기 때문에 존재합니다.

  • 속성의 모든 기본 코드는 메소드 본문 자체에서 실행됩니다.

  • 속성을 속성으로 설정하고 속성으로 참조 할 수 있습니다.

  • 속성을 매개 변수화 할 수 있습니다.

코드 측면에서 다음과 같이 작성하는 방법이 필요합니다.

def x(self, *args):
    return defaultX()

그런 다음 할 수 있습니다 :

print e.x     -> The default at time T0
e.x = 1
print e.x     -> 1
e.x = None
print e.x     -> The default at time T1

기타 등등.

또한 매개 변수화 가능 속성의 특수한 경우에이를 수행 할 수있는 방법을 원하지만 기본 대소 문자를 계속 사용할 수 있습니다. 아래에서이 문제를 어떻게 해결했는지 확인할 수 있습니다.

이제 요점까지 (예! 요점!). 내가 찾은 해결책은 다음과 같습니다.

속성 개념을 대체 할 새 객체를 만듭니다. 이 객체는 변수 세트의 값을 저장하기위한 것이지만 기본값을 계산하는 방법을 알고있는 코드에 대한 핸들도 유지합니다. 작업은 세트를 저장 value하거나 method해당 값이 설정되지 않은 경우 실행하는 것입니다.

이라고 부릅시다 UberProperty.

class UberProperty(object):

    def __init__(self, method):
        self.method = method
        self.value = None
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def clearValue(self):
        self.value = None
        self.isSet = False

나는 method여기에 클래스 메소드 라고 가정 value하고의 값이며 실제 값 일 수 있기 때문에 UberProperty추가 했으며 이것이 실제로 “값 없음”이라고 선언하는 깨끗한 방법을 허용합니다. 다른 방법은 일종의 센티넬입니다.isSetNone

이것은 기본적으로 우리가 원하는 것을 할 수있는 객체를 제공하지만 실제로 어떻게 수업에 배치합니까? 속성은 데코레이터를 사용합니다. 왜 안돼? 그것이 어떻게 보일지 봅시다 (여기에서 나는 단지 하나의 ‘속성’을 사용할 것입니다 x).

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

물론 실제로는 작동하지 않습니다. uberPropertyGet과 Set을 모두 처리하고 구현 해야합니다.

gets부터 시작하겠습니다.

첫 번째 시도는 단순히 새로운 UberProperty 객체를 만들고 반환하는 것입니다.

def uberProperty(f):
    return UberProperty(f)

물론 이것이 작동하지 않는다는 것을 빨리 발견했습니다. 파이썬은 호출 가능한 객체를 객체에 바인딩하지 않으며 함수를 호출하기 위해 객체가 필요합니다. 클래스에 데코레이터를 생성해도 작동하지 않습니다. 이제 클래스가 있지만 여전히 작업 할 오브젝트가 없습니다.

우리는 여기서 더 많은 일을 할 수 있어야합니다. 우리는 메소드가 한 번만 표현 될 필요가 있다는 것을 알고 있으므로 계속해서 데코레이터를 유지하고 참조 UberProperty만 저장하도록 수정 하자 method.

class UberProperty(object):

    def __init__(self, method):
        self.method = method

또한 호출 할 수 없으므로 현재 아무것도 작동하지 않습니다.

우리는 어떻게 그림을 완성합니까? 새 데코레이터를 사용하여 예제 클래스를 만들 때 어떤 결과가 발생합니까?

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

print Example.x     <__main__.UberProperty object at 0x10e1fb8d0>
print Example().x   <__main__.UberProperty object at 0x10e1fb8d0>

두 경우 모두 UberProperty물론 우리는 어느 것이 호출 가능하지 않은지 다시 얻습니다. 그래서 이것은 많이 사용되지 않습니다.

우리가 필요로하는 UberProperty것은 클래스가 객체를 생성 한 후 데코레이터가 생성 한 인스턴스를 클래스의 객체에 동적으로 바인딩하는 방법 입니다. 음, 그게 __init__전화 야, 친구

우리가 찾은 결과가 무엇을 원하는지 적어 봅시다. 우리는 UberProperty인스턴스에 바인딩하고 있으므로 반환해야 할 것은 BoundUberProperty입니다. 여기에서 실제로 x속성의 상태를 유지 합니다.

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

이제 우리는 표현입니다. 이것들을 어떻게 객체에 넣습니까? 몇 가지 접근법이 있지만 설명하는 가장 쉬운 __init__방법은 해당 매핑을 수행하는 방법을 사용하는 것입니다. 시간 __init__이 지나면 데코레이터가 실행되었으므로 객체를 살펴보고 __dict__속성 값이 type 인 속성을 업데이트하면 UberProperty됩니다.

이제 uber-properties는 멋지고 아마도 많이 사용하고 싶을 것이므로 모든 서브 클래스에 대해 이것을 수행하는 기본 클래스를 만드는 것이 좋습니다. 기본 클래스가 무엇인지 알 것 같습니다.

class UberObject(object):
    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)

우리는 이것을 추가하고에서 상속하도록 예제를 변경 UberObject하고 …

e = Example()
print e.x               -> <__main__.BoundUberProperty object at 0x104604c90>

다음과 같이 수정 한 후 x:

@uberProperty
def x(self):
    return *datetime.datetime.now()*

간단한 테스트를 실행할 수 있습니다.

print e.x.getValue()
print e.x.getValue()
e.x.setValue(datetime.date(2013, 5, 31))
print e.x.getValue()
e.x.clearValue()
print e.x.getValue()

그리고 원하는 결과를 얻습니다.

2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310

(Gee, 나는 늦게 일하고있다.)

내가 사용하는 것을 참고 getValue, setValue그리고 clearValue여기. 이것은 자동으로 반환하는 수단에 아직 연결되어 있지 않기 때문입니다.

그러나 나는 피곤해지기 때문에 이것이 지금 막을 좋은 곳이라고 생각합니다. 또한 원하는 핵심 기능이 제대로 갖추어져 있음을 알 수 있습니다. 나머지는 창문 드레싱입니다. 중요한 유용성 창 드레싱이지만 게시물을 업데이트하기 위해 변경 될 때까지 기다릴 수 있습니다.

다음 게시물에서 이러한 사항을 해결하여 예제를 마무리하겠습니다.

  • 우리는 UberObject __init__가 항상 서브 클래스에 의해 호출 되도록해야 합니다.

    • 따라서 우리는 그것을 어딘가에 강제로 호출하거나 구현하지 못하게합니다.
    • 메타 클래스로이 작업을 수행하는 방법을 살펴 보겠습니다.
  • 우리는 누군가가 다음과 같은 다른 함수를 ‘별명 화’하는 일반적인 경우를 처리해야합니다.

      class Example(object):
          @uberProperty
          def x(self):
              ...
    
          y = x
    
  • 우리는 필요로 e.x돌아 e.x.getValue()기본적으로.

    • 우리가 실제로 보게 될 것은 이것이 모델이 실패하는 영역입니다.
    • 항상 값을 얻으려면 함수 호출을 사용해야합니다.
    • 그러나 우리는 그것을 일반 함수 호출처럼 보이게 만들고 사용할 필요가 없습니다 e.x.getValue(). (아직 해결하지 않은 경우이 작업을 수행하는 것이 분명합니다.)
  • e.x directly에서와 같이 설정을 지원해야합니다 e.x = <newvalue>. 부모 클래스 에서도이 작업을 수행 할 수 있지만 __init__처리하려면 코드를 업데이트해야 합니다.

  • 마지막으로 매개 변수화 된 속성을 추가합니다. 우리가 이것을 어떻게 할 것인지는 분명합니다.

지금까지의 코드는 다음과 같습니다.

import datetime

class UberObject(object):
    def uberSetter(self, value):
        print 'setting'

    def uberGetter(self):
        return self

    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)


class UberProperty(object):
    def __init__(self, method):
        self.method = method

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

    def uberProperty(f):
        return UberProperty(f)

class Example(UberObject):

    @uberProperty
    def x(self):
        return datetime.datetime.now()

[1] 이것이 여전히 그런지에 대해서는 뒤처져있을 수 있습니다.


답변

둘 다 자리가 있다고 생각합니다. 사용의 한 가지 문제점 @property은 표준 클래스 메커니즘을 사용하여 서브 클래스에서 게터 또는 세터의 동작을 확장하기 어렵다는 것입니다. 문제는 실제 getter / setter 함수가 속성에 숨겨져 있다는 것입니다.

실제로 함수를 잡을 수 있습니다.

class C(object):
    _p = 1
    @property
    def p(self):
        return self._p
    @p.setter
    def p(self, val):
        self._p = val

getter 및 setter 함수에 C.p.fgetand 로 액세스 할 수 C.p.fset있지만 일반적인 메소드 상속 (예 : 수퍼) 기능을 사용하여 쉽게 확장 할 수는 없습니다. super의 복잡한 부분을 파고 들고 나면 실제로 다음과 같이 super를 사용할 수 있습니다 .

# Using super():
class D(C):
    # Cannot use super(D,D) here to define the property
    # since D is not yet defined in this scope.
    @property
    def p(self):
        return super(D,D).p.fget(self)

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for D'
        super(D,D).p.fset(self, val)

# Using a direct reference to C
class E(C):
    p = C.p

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for E'
        C.p.fset(self, val)

그러나 속성을 재정의해야하고 p의 바인딩되지 않은 복사본을 얻으려면 약간 반 직관적 인 super (cls, cls) 메커니즘을 사용해야하므로 super () 사용은 매우 어수선합니다.


답변

속성을 사용하는 것이 더 직관적이며 대부분의 코드에 더 적합합니다.

비교

o.x = 5
ox = o.x

vs.

o.setX(5)
ox = o.getX()

나에게 읽기 쉬운 것이 분명합니다. 또한 속성을 사용하면 개인 변수를 훨씬 쉽게 사용할 수 있습니다.