@property와 getter 및 setter 사용


727

다음은 순수한 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):
        ...

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

답변:


613

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

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

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


90
이중 밑줄이있는 속성 이름은 Python에서 특별히 처리합니다. 단순한 컨벤션이 아닙니다. 참조 docs.python.org/py3k/tutorial/classes.html#private-variables
6502

63
그것들은 다르게 처리되지만 액세스하지 못하게합니다. 추신 : AD 30 C0
kindall

4
"@"문자는 파이썬 코드에서 못 생기고 역 참조 @decorators는 스파게티 코드와 같은 느낌을줍니다.
Berry Tsakala

18
동의하지 않습니다. 구조화 된 코드는 스파게티 코드와 어떻게 다릅니 까? 파이썬은 아름다운 언어입니다. 그러나 적절한 캡슐화 및 구조적 클래스와 같은 간단한 것들을 더 잘 지원하면 더 나아질 것입니다.

69
대부분의 경우 동의하지만 @property 데코레이터 뒤에 느린 메소드를 숨기는 데주의하십시오. API 사용자는 속성 액세스가 가변 액세스처럼 수행 될 것으로 예상하고 너무 멀어지면 API를 사용하기가 불편할 수 있습니다.
defrex

153

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

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

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


46
@ 6502, "예를 들어 간단한 튜플이하는 곳 어디에서나 무의미한 클래스"라고 말했을 때 : 튜플에 비해 클래스의 장점은 클래스 인스턴스가 해당 부분에 액세스 할 수있는 명확한 이름을 제공하지만 튜플은 그렇지 않다는 것입니다 . 튜플 첨자보다 이름이 가독성이 좋고 오류를 피하는 것이 더 좋습니다. 특히 현재 모듈 외부로 전달되는 경우입니다.
Hibou57

15
@ Hibou57 : 수업이 쓸모 없다는 말은 아닙니다. 그러나 때로는 튜플이 충분합니다. 그러나 문제는 Java 또는 C ++에서 온 사람들은 다른 가능성이 그 언어에 사용하기가 귀찮기 때문에 모든 클래스를 만드는 것 외에는 선택의 여지가 없다는 것입니다. 파이썬을 사용한 Java / C ++ 프로그래밍의 또 다른 전형적인 증상은 파이썬에서 오리 타이핑 덕분에 독립적 인 클래스를 사용할 수있는 이유없이 추상 클래스와 복잡한 클래스 계층을 만드는 것입니다.
6502

39
u를위한 @ Hibou57 또한 namedtuple을 사용할 수 있습니다 : doughellmann.com/PyMOTW/collections/namedtuple.html
hugo24

5
@JonathonReinhart : 그것은 IS 2.6 보낸 표준 라이브러리에서 볼 ... docs.python.org/2/library/collections.html
6502

1
"필요한 경우 속성으로 궁극적으로 마이그레이션"할 때 클래스를 사용하여 코드를 깨뜨릴 가능성이 높습니다. 속성에는 종종 제한 사항이 있습니다. 이러한 제한 사항을 예상하지 않은 코드는 소개하자마자 중단됩니다.
yaccob

118

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


3
@GregKrsak 그것은 이상해 보인다. "성인을 만나는 것"은 속성이 추가되기 전의 파이썬 밈이었습니다. 액세스 수정 자 부족에 대해 불만을 제기 한 사람들에 대한 재고 반응이었습니다. 속성이 추가되면 갑자기 캡슐화가 바람직합니다. 추상 기본 클래스에서도 마찬가지입니다. "파이썬은 항상 캡슐화를 깨고 전쟁을 벌였습니다. 자유는 노예입니다. 람다는 한 줄에만 맞아야합니다."
johncip

71

짧은 대답은 : 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)

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


1
@BasicWolf-울타리의 속성 측면에 있다는 것이 암묵적으로 명확하다고 생각했습니다! :) 그러나 나는 그것을 명확히하기 위해 내 대답에 파라를 추가합니다.

9
힌트 : "항상"이라는 단어는 저자가 주장이 아니라 주장을 설득하려고한다는 힌트입니다. 굵은 글꼴이 있습니다. (즉, 당신이 CAPS를 본다면-whoa-맞아야합니다.) "프로퍼티"기능은 Java (어떤 이유로 파이썬의 사실상 천적)와 다르기 때문에 파이썬의 커뮤니티 그룹 생각 더 나은 것으로 선언합니다. 실제로 속성은 "명시 적 암시 적보다 낫다"규칙을 위반하지만 아무도이를 인정하지 않습니다. 그것은 언어로 만들어 졌으므로 이제는 팽팽한 주장을 통해 "Pythonic"으로 선언되었습니다.
스튜어트 버그

3
감정이 상하지 않습니다. :-P이 경우 "Pythonic"규칙이 일치하지 않는다는 점을 지적하려고합니다 property. (이것은 간단한 할당처럼 보이지만 함수를 호출합니다.) 따라서 "Pythonic"은 팽팽한 정의를 제외하고는 본질적으로 의미가없는 용어입니다.
스튜어트 버그

1
이제 테마를 따르는 일련의 규칙을 갖는 아이디어훌륭 합니다. 그러한 규칙 집합이 존재한다면, 암기해야 할 트릭의 긴 점검 목록뿐만 아니라 생각을 안내하는 일련의 공리로 사용할 수 있으므로 크게 유용하지 않습니다. 공리를 외삽에 사용할 수 있으며 아직 아무도 보지 못한 문제에 접근 할 수 있습니다 . 이 property기능이 파이썬 공리의 아이디어를 거의 쓸모 없게 만들겠다고 위협 한다는 것은 부끄러운 일입니다 . 우리가 남긴 것은 점검표입니다.
스튜어트 버그

1
동의하지 않습니다. 나는 대부분의 상황에서 속성을 선호하지만 객체를 수정하는 것 이외의 부작용self 이 있음을 강조하고 싶을 때 명시 적 세터가 도움이 될 수 있습니다. 예를 들어, user.email = "..."속성을 설정하는 것처럼 보이기 때문에 예외가 발생할 수있는 것처럼 보이지 않지만 예외와 같은 user.set_email("...")부작용이있을 수 있음을 분명히합니다.
bluenote10

65

[ 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] 이것이 여전히 그런지에 대해서는 뒤처져있을 수 있습니다.


53
예, 이것은 'tldr'입니다. 여기서하려는 일을 요약 해 주시겠습니까?
poolie 2016

9
@ 아담 return self.x or self.defaultX()이것은 위험한 코드입니다. 언제 발생 self.x == 0합니까?
Kelly Thomas

참고로, getter를 매개 변수화 할 있도록 만들 수 있습니다. 변수를 사용자 정의 클래스로 만드는 것이 포함되며,이 클래스에서 __getitem__메소드를 재정의했습니다 . 그래도 표준 파이썬이 아닌 완전히 이상한 것처럼 보일 것입니다.
것이다

2
@KellyThomas 예제를 단순하게 유지하려고합니다. None 값조차도 구체적으로 설정 되었기 때문에 제대로하려면 x dict 항목 을 작성하고 삭제해야합니다 . 그러나 그렇습니다. 이것은 프로덕션 사용 사례에서 고려해야 할 사항입니다.
Adam Donahue

Java와 같은 게터를 사용하면 정확히 같은 계산을 수행 할 수 있습니까?
qed December

26

둘 다 자리가 있다고 생각합니다. 사용의 한 가지 문제점 @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 () 사용은 매우 어수선합니다.


20

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

비교

o.x = 5
ox = o.x

vs.

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

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


12

대부분의 경우 어느 쪽도 사용하지 않는 것이 좋습니다. 속성의 문제점은 클래스를 덜 투명하게 만든다는 것입니다. 특히 세터에서 예외를 발생시키는 경우 문제가됩니다. 예를 들어 Account.email 속성이있는 경우 :

class Account(object):
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, value):
        if '@' not in value:
            raise ValueError('Invalid email address.')
        self._email = value

클래스의 사용자는 속성에 값을 할당하면 예외가 발생할 수 있다고 기대하지 않습니다.

a = Account()
a.email = 'badaddress'
--> ValueError: Invalid email address.

결과적으로 예외가 처리되지 않고 호출 체인에서 너무 높게 전파되어 올바르게 처리되지 않거나 프로그램 사용자에게 매우 도움이되지 않는 추적이 표시됩니다 (파이썬과 자바 세계에서는 슬프게도 일반적입니다) ).

또한 getter와 setter를 사용하지 않는 것이 좋습니다.

  • 모든 속성에 대해 미리 정의하는 데 시간이 많이 걸리기 때문에
  • 코드의 양이 불필요하게 길어 지므로 코드를 이해하고 유지하는 것이 더 어려워집니다.
  • 필요한 경우에만 속성에 대해 정의하면 클래스의 인터페이스가 변경되어 클래스의 모든 사용자가 아프게됩니다.

속성 및 게터 / 세터 대신 유효성 검사 방법과 같이 잘 정의 된 장소에서 복잡한 논리를 수행하는 것을 선호합니다.

class Account(object):
    ...
    def validate(self):
        if '@' not in self.email:
            raise ValueError('Invalid email address.')

또는 유사한 Account.save 메소드.

속성이 유용한 경우가 없다고 말하려는 것이 아니라 클래스가 필요하지 않을 정도로 간단하고 투명하게 클래스를 만들 수 있다면 더 나아질 수 있습니다.


3
@ user2239734 속성의 개념을 잘못 이해했다고 생각합니다. 속성을 설정하는 동안 값의 유효성을 검사 할 수 있지만 반드시 그럴 필요는 없습니다. validate()클래스에서 속성과 메서드를 모두 가질 수 있습니다 . 속성은 간단한 obj.x = y할당 뒤에 복잡한 로직이있을 때 사용되며 로직의 특성에 달려 있습니다.
Zaur Nasibov

12

속성은 실제로 필요한 경우에만 게터와 세터를 작성하는 오버 헤드를 얻는 것과 관련이 있다고 생각합니다.

Java Programming culture는 속성에 대한 액세스 권한을 부여하지 말고 getter 및 setter를 통해 실제로 필요한 것만 사용하는 것이 좋습니다. 이러한 명백한 코드를 항상 작성하는 것은 약간 장황하며 시간의 70 %가 사소한 논리로 대체되지 않는다는 것을 알 수 있습니다.

파이썬에서는 사람들이 실제로 이런 종류의 오버 헤드를 관리하므로 다음과 같은 방법을 사용할 수 있습니다.

  • 필요하지 않은 경우 처음에는 게터와 세터를 사용하지 마십시오.
  • @property나머지 코드 구문을 변경하지 않고 구현하는 데 사용하십시오 .

1
"70 %의 시간이 결코 사소한 논리로 대체되지 않는다는 점에 주목하십시오." 그것은 다소 특정한 숫자이거나, 어느 곳에서 나왔거나, 또는 "대다수"종류의 손으로 만들려고합니까? 읽기에 정말로 관심이 있습니다)
Adam Parkin

1
아 죄송합니다. 이 숫자를 백업하기 위해 약간의 연구를 한 것처럼 들리지만 "대부분의 시간"으로 만 사용했습니다.
fulmicoton

7
사람들이 오버 헤드를 걱정하는 것은 아닙니다. 파이썬에서는 클라이언트 코드를 변경하지 않고 직접 액세스에서 접근 자 메서드로 변경할 수 있으므로 속성을 처음에 직접 노출하여 잃을 것이 없습니다.
Neil G

10

속성이 디스크립터 클래스의 바인딩 된 메소드라고 언급 한 사람이 아무도 없습니다. Adam DonohueNeilenMarais 는 게시물 에서이 아이디어를 정확하게 얻습니다.

  • 확인
  • 데이터 변경
  • 오리 형 (다른 종류의 강제 형)

이는 블록, 어설 션 또는 계산 된 값을 제외하고 정규식, 유형 캐스트, try ..와 같은 구현 세부 사항과 코드 균열을 숨기는 현명한 방법을 제시 합니다.

일반적으로 오브젝트에서 CRUD를 수행하는 것은 상당히 평범 할 수 있지만 관계형 데이터베이스에 지속될 데이터의 예를 고려하십시오. ORM은 속성 클래스에 정의 된 fget, fset, fdel에 바인딩 된 메소드에서 특정 SQL vernaculars의 구현 세부 사항을 숨길 수 있습니다. ORM을 사용 하는 self.variable = something개발자 위해 우아 하고 세부적인 사항을 제거하십시오 .

속박과 징계 언어 (예 : Java)의 음란 한 흔적으로 만 속성을 생각하면 설명 자의 요점이 없습니다.


6

복잡한 프로젝트에서는 명시적인 setter 함수와 함께 읽기 전용 속성 또는 getter를 사용하는 것이 좋습니다.

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

def set_my_attr(self, value):
    ...

오래 지속되는 프로젝트에서 디버깅 및 리팩토링은 코드 자체를 작성하는 것보다 더 많은 시간이 걸립니다. @property.setter디버깅을 더욱 어렵게 만드는 몇 가지 단점이 있습니다 .

1) 파이썬은 기존 객체에 대한 새로운 속성을 만들 수 있습니다. 이로 인해 다음 오판을 추적하기가 매우 어려워집니다.

my_object.my_atttr = 4.

객체가 복잡한 알고리즘이라면 왜 그것이 수렴하지 않는지 알아내는 데 꽤 많은 시간을 할애 할 것입니다 (위의 줄에 여분의 't'에 주목하십시오)

2) setter는 때때로 복잡하고 느린 방법으로 발전 할 수 있습니다 (예 : 데이터베이스 적중). 다른 개발자가 다음 기능이 왜 매우 느린 지 파악하기는 매우 어렵습니다. 그는 프로파일 링 do_something()방법 에 많은 시간을 할애 하지만 my_object.my_attr = 4.실제로 속도 저하의 원인은 다음과 같습니다.

def slow_function(my_object):
    my_object.my_attr = 4.
    my_object.do_something()

5

모두 @property전통 getter 및 setter는 장점이있다. 사용 사례에 따라 다릅니다.

장점 @property

  • 데이터 액세스 구현을 변경하는 동안 인터페이스를 변경할 필요가 없습니다. 프로젝트가 작을 경우 직접 속성 액세스를 사용하여 클래스 멤버에 액세스하려고 할 수 있습니다. 예를 들어, 멤버가있는 foo유형 의 객체가 있다고 가정 해 봅시다 . 그러면이 멤버를로 간단히 얻을 수 있습니다 . 프로젝트가 성장함에 따라 간단한 속성 액세스에 대한 점검 또는 디버그가 필요하다고 느낄 수 있습니다. 그런 다음 수업 에서 할 수 있습니다 . 데이터 액세스 인터페이스는 동일하게 유지되므로 클라이언트 코드를 수정할 필요가 없습니다.Foonumnum = foo.num@property

    PEP-8 에서 인용 :

    단순한 공개 데이터 속성의 경우 복잡한 접근 자 / 돌연변이 방법없이 속성 이름 만 노출하는 것이 가장 좋습니다. 간단한 데이터 속성이 기능적 행동을 키워야한다는 사실을 알게되면 Python은 향후 개선을위한 쉬운 길을 제공한다는 점을 명심하십시오. 이 경우 속성을 사용하여 간단한 데이터 속성 액세스 구문 뒤에 기능 구현을 숨길 수 있습니다.

  • @propertyPython에서 데이터 액세스에 사용 하는 것은 Pythonic 으로 간주됩니다 .

    • 파이썬 (자바가 아닌) 프로그래머로서 당신의 자기 식별을 강화시킬 수 있습니다.

    • 면접관이 Java 스타일 게터와 세터가 반 패턴 이라고 생각하면 면접에 도움이 될 수 있습니다 .

전통적인 게터와 세터의 장점

  • 전통적인 게터와 세터는 단순한 속성 액세스보다 더 복잡한 데이터 액세스를 허용합니다. 예를 들어, 클래스 멤버를 설정할 때 무언가가 완벽하게 보이지 않더라도이 작업을 강제 할 위치를 나타내는 플래그가 필요할 때가 있습니다. 과 같은 직접 멤버 액세스를 늘리는 방법은 분명하지 않지만 다음 foo.num = num과 같은 추가 force매개 변수를 사용하여 기존 세터를 쉽게 보강 할 수 있습니다 .

    def Foo:
        def set_num(self, num, force=False):
            ...
  • 전통적인 게터와 세터 는 클래스 멤버 액세스가 메소드를 통해 이루어진다는 것을 명시 적으로 만듭니다 . 이것은 다음을 의미합니다.

    • 결과로 얻는 것과 해당 클래스에 정확히 저장된 것과는 다를 수 있습니다.

    • 액세스가 단순한 속성 액세스처럼 보이지만 성능과 크게 다를 수 있습니다.

    클래스 사용자가 @property모든 속성 액세스 문 뒤에 숨어 있기를 기대하지 않는 한 , 그러한 것을 명시 적으로 만들면 클래스 사용자의 놀라움을 최소화하는 데 도움이 될 수 있습니다.

  • @NeilenMarais 와이 게시물 에서 언급했듯이 하위 클래스에서 기존 게터와 세터를 확장하는 것이 속성을 확장하는 것보다 쉽습니다.

  • 전통적인 게터와 세터는 다른 언어로 오랫동안 널리 사용되었습니다. 팀에 서로 다른 배경을 가진 사람들이 있다면 사람들보다 친숙하게 보입니다 @property. 또한 프로젝트가 커짐에 따라 파이썬에서없는 언어로 마이그레이션해야 할 경우 @property기존 게터와 세터를 사용하면 마이그레이션이 더 순조로워집니다.

경고

  • @property이름 앞에 이중 밑줄을 사용하더라도 전통적인 게터 나 세터도 클래스 멤버를 비공개로 만들지 않습니다.

    class Foo:
        def __init__(self):
            self.__num = 0
    
        @property
        def num(self):
            return self.__num
    
        @num.setter
        def num(self, num):
            self.__num = num
    
        def get_num(self):
            return self.__num
    
        def set_num(self, num):
            self.__num = num
    
    foo = Foo()
    print(foo.num)          # output: 0
    print(foo.get_num())    # output: 0
    print(foo._Foo__num)    # output: 0

2

다음은 "유효한 파이썬 :보다 나은 파이썬을 작성하는 90 가지 구체적인 방법" ( 발췌 )입니다.

기억해야 할 것

✦ 간단한 공용 속성을 사용하여 새 클래스 인터페이스를 정의하고 setter 및 getter 메소드를 정의하지 마십시오.

✦ 필요한 경우 객체에서 속성에 액세스 할 때 @property를 사용하여 특수한 동작을 정의하십시오.

✦ 최소한의 놀람 규칙을 따르고 @property 방법에서 이상한 부작용을 피하십시오.

✦ @property 메소드가 빠른지 확인하십시오. 느리거나 복잡한 작업 (특히 I / O 관련 또는 부작용 발생)을 위해서는 일반적인 방법을 사용하십시오.

@property의 고급이지만 일반적으로 사용되는 방법 중 하나는 단순한 수치 속성을 즉시 계산으로 전환하는 것입니다. 이는 호출 사이트를 다시 작성할 필요없이 클래스의 기존 사용을 모두 마이그레이션하여 새로운 동작을 수행 할 수있게하므로 매우 유용합니다 (제어하지 않는 호출 코드가있는 경우 특히 중요 함). @property는 또한 시간이 지남에 따라 인터페이스를 개선하기위한 중요한 중지 간격을 제공합니다.

시간이 지남에 따라 더 나은 데이터 모델을 향해 점진적으로 진보 할 수 있기 때문에 @property가 특히 좋습니다.
@property는 실제 코드에서 발생할 수있는 문제를 해결하는 데 도움이되는 도구입니다. 그것을 과도하게 사용하지 마십시오. @property 메소드를 반복적으로 확장하면 코드의 열악한 디자인을 추가로 포장하는 대신 클래스를 리팩터링해야 할 때입니다.

✦ @property를 사용하여 기존 인스턴스 속성에 새로운 기능을 부여하십시오.

✦ @property를 사용하여 더 나은 데이터 모델로 점차 발전하십시오.

✦ @property를 너무 많이 사용하면 클래스와 모든 콜 사이트를 리팩토링하는 것이 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.