[ 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()
내가 전화하지 않은 이유 defaultX
와 defaultY
객체의 __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] 그것은 코드를 어지럽히는 여분의 소음입니다.
또 다른 문제는에서 x
및 y
값 을 초기화해야한다는 것 입니다 __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
추가 했으며 이것이 실제로 "값 없음"이라고 선언하는 깨끗한 방법을 허용합니다. 다른 방법은 일종의 센티넬입니다.isSet
None
이것은 기본적으로 우리가 원하는 것을 할 수있는 객체를 제공하지만 실제로 어떻게 수업에 배치합니까? 속성은 데코레이터를 사용합니다. 왜 안돼? 그것이 어떻게 보일지 봅시다 (여기에서 나는 단지 하나의 '속성'을 사용할 것입니다 x
).
class Example(object):
@uberProperty
def x(self):
return defaultX()
물론 실제로는 작동하지 않습니다. uberProperty
Get과 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] 이것이 여전히 그런지에 대해서는 뒤처져있을 수 있습니다.