이것은 무료로만 제공 될 수있는 긴 바람의 대답이 될 것입니다 ...
궁극적으로이 답변이 실제 문제에 도움이되지 않을 수 있습니다. 사실, 내 결론은-나는 이것을 전혀하지 않을 것입니다. 말했듯이,이 결론의 배경은 자세한 내용을 찾고 있기 때문에 약간 재미있을 수 있습니다.
약간의 오해를 해결
대부분의 경우 첫 번째 대답은 정확하지만 항상 그런 것은 아닙니다 . 예를 들어 다음 클래스를 고려하십시오.
class Foo:
def __init__(self):
self.name = 'Foo!'
@property
def inst_prop():
return f'Retrieving {self.name}'
self.inst_prop = inst_prop
inst_prop
은 (는 property
)이지만 돌이킬 수없는 인스턴스 속성입니다.
>>> Foo.inst_prop
Traceback (most recent call last):
File "<pyshell#60>", line 1, in <module>
Foo.inst_prop
AttributeError: type object 'Foo' has no attribute 'inst_prop'
>>> Foo().inst_prop
<property object at 0x032B93F0>
>>> Foo().inst_prop.fget()
'Retrieving Foo!'
그것은 모두 당신 의 처음 정의 위치에 달려 있습니다 property
. @property
클래스가 "scope"클래스 (또는 실제로 namespace
) 내에 정의되어 있으면 클래스 속성이됩니다. 내 예에서 클래스 자체는 inst_prop
인스턴스화 될 때까지 아무것도 알지 못합니다 . 물론 여기에서는 속성으로 그리 유용하지 않습니다.
그러나 먼저 상속 해결에 대한 귀하의 의견을 다루겠습니다 ...
그렇다면 상속이 어떻게 정확하게이 문제에 영향을 미칩니 까? 이 기사에서는이 주제에 대해 조금 살펴 보았고, Method Resolution Order 는 다소 깊이가 아니라 상속의 폭을 주로 다루지 만 다소 관련이 있습니다.
아래 설정에서 우리의 발견과 결합 :
@property
def some_prop(self):
return "Family property"
class Grandparent:
culture = some_prop
world_view = some_prop
class Parent(Grandparent):
world_view = "Parent's new world_view"
class Child(Parent):
def __init__(self):
try:
self.world_view = "Child's new world_view"
self.culture = "Child's new culture"
except AttributeError as exc:
print(exc)
self.__dict__['culture'] = "Child's desired new culture"
이 줄이 실행될 때 어떤 일이 발생하는지 상상해보십시오.
print("Instantiating Child class...")
c = Child()
print(f'c.__dict__ is: {c.__dict__}')
print(f'Child.__dict__ is: {Child.__dict__}')
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')
결과는 다음과 같습니다.
Instantiating Child class...
can't set attribute
c.__dict__ is: {'world_view': "Child's new world_view", 'culture': "Child's desired new culture"}
Child.__dict__ is: {'__module__': '__main__', '__init__': <function Child.__init__ at 0x0068ECD8>, '__doc__': None}
c.world_view is: Child's new world_view
Child.world_view is: Parent's new world_view
c.culture is: Family property
Child.culture is: <property object at 0x00694C00>
방법에 주목하십시오 :
self.world_view
self.culture
실패 하면서 적용 할 수있었습니다
culture
에 존재하지 않습니다 Child.__dict__
( mappingproxy
클래스와 인스턴스와 혼동하지 마십시오 __dict__
)
- 에
culture
존재 하더라도 c.__dict__
참조되지 않습니다.
왜 클래스에 world_view
의해 Parent
속성이 아닌 것으로 덮어 쓰여 졌는지 짐작할 Child
수있을 것입니다. 때문에 한편, culture
상속, 그것은 단지 내에 존재 mappingproxy
의Grandparent
:
Grandparent.__dict__ is: {
'__module__': '__main__',
'culture': <property object at 0x00694C00>,
'world_view': <property object at 0x00694C00>,
...
}
실제로 제거하려고하면 Parent.culture
:
>>> del Parent.culture
Traceback (most recent call last):
File "<pyshell#67>", line 1, in <module>
del Parent.culture
AttributeError: culture
당신은 심지어 존재하지 않는 것을 알 수 있습니다 Parent
. 객체가 직접을 참조하기 때문 Grandparent.culture
입니다.
그렇다면 결의 명령은 어떻습니까?
따라서 실제 해결 명령을 준수하는 데 관심이 있습니다. Parent.world_view
대신 제거해 보겠습니다 .
del Parent.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
결과가 궁금하십니까?
c.world_view is: Family property
Child.world_view is: <property object at 0x00694C00>
world_view
property
비록 우리가 이전을 성공적으로 할당했지만, 그것은 조부모의 것으로 되돌아갔습니다 self.world_view
! 그러나 world_view
다른 대답과 같이 수업 수준에서 강하게 변화하면 어떻게 될까요? 삭제하면 어떻게 되나요? 현재 클래스 속성을 속성으로 할당하면 어떻게됩니까?
Child.world_view = "Child's independent world_view"
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
del c.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
Child.world_view = property(lambda self: "Child's own property")
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
결과는 다음과 같습니다.
# Creating Child's own world view
c.world_view is: Child's new world_view
Child.world_view is: Child's independent world_view
# Deleting Child instance's world view
c.world_view is: Child's independent world_view
Child.world_view is: Child's independent world_view
# Changing Child's world view to the property
c.world_view is: Child's own property
Child.world_view is: <property object at 0x020071B0>
c.world_view
인스턴스 속성으로 복원되었으므로 Child.world_view
할당 한 속성 이 흥미 롭습니다 . 인스턴스 속성을 제거한 후에는 클래스 속성으로 돌아갑니다. 그리고 Child.world_view
속성에 속성 을 다시 할당 하면 인스턴스 속성에 즉시 액세스 할 수 없게됩니다.
따라서 다음 해결 순서를 추측 할 수 있습니다 .
- 클래스 속성이 존재하는 경우 와 그것이이다
property
, 그것의를 통해 값을 검색 getter
하거나 fget
(나중에에 더). 현재 클래스는 기본 클래스부터 마지막 클래스까지입니다.
- 그렇지 않으면 인스턴스 속성이 존재하면 인스턴스 속성 값을 검색하십시오.
- 그렇지 않으면, 비
property
클래스 속성을 검색하십시오 . 현재 클래스는 기본 클래스부터 마지막 클래스까지입니다.
이 경우 루트를 제거하십시오 property
.
del Grandparent.culture
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')
다음을 제공합니다.
c.culture is: Child's desired new culture
Traceback (most recent call last):
File "<pyshell#74>", line 1, in <module>
print(f'Child.culture is: {Child.culture}')
AttributeError: type object 'Child' has no attribute 'culture'
타다! 에에 대한 강력한 삽입을 기반으로하는 Child
고유 culture
한 기능이 c.__dict__
있습니다. Child.culture
이 정의되지 않았기 때문에, 물론, 존재하지 않는 Parent
또는 Child
클래스 속성과 Grandparent
의 제거했다.
이것이 내 문제의 근본 원인입니까?
실제로는 없습니다 . 할당 할 때 여전히 관찰되는 오류 self.culture
는 완전히 다릅니다 . 그러나 상속 순서는 배경을 답으로 설정합니다 property
.
이전에 언급 한 getter
방법 외에도 property
소매에 깔끔한 트릭이 있습니다. 이 경우 가장 관련이 있는 것은 setter
또는 fset
메소드 self.culture = ...
입니다. 이 메소드는 라인 별로 트리거됩니다 . 당신 property
은 어떤 setter
또는 fget
함수를 구현하지 않았기 때문에 파이썬은 무엇을 해야할지 모르고 AttributeError
대신 ( can't set attribute
)을 던집니다 .
그러나 setter
메소드 를 구현 한 경우 :
@property
def some_prop(self):
return "Family property"
@some_prop.setter
def some_prop(self, val):
print(f"property setter is called!")
# do something else...
Child
클래스를 인스턴스화하면 다음을 얻을 수 있습니다.
Instantiating Child class...
property setter is called!
를받는 대신 AttributeError
실제로 some_prop.setter
메소드를 호출하고 있습니다. 이전의 결과를 통해 객체에 대한 제어력이 향상되었으므로 속성에 도달 하기 전에 클래스 속성을 덮어 써야한다는 것을 알고 있습니다. 이것은 기본 클래스 내에서 트리거로 구현 될 수 있습니다. 다음은 신선한 예입니다.
class Grandparent:
@property
def culture(self):
return "Family property"
# add a setter method
@culture.setter
def culture(self, val):
print('Fine, have your own culture')
# overwrite the child class attribute
type(self).culture = None
self.culture = val
class Parent(Grandparent):
pass
class Child(Parent):
def __init__(self):
self.culture = "I'm a millennial!"
c = Child()
print(c.culture)
결과 :
Fine, have your own culture
I'm a millennial!
TA-DAH! 상속 된 속성을 통해 자신의 인스턴스 속성을 덮어 쓸 수 있습니다!
문제가 해결 되었습니까?
... 실제로. 이 방법의 문제점은 이제 적절한 setter
방법 을 사용할 수 없다는 것 입니다. 에 값을 설정하려는 경우가 있습니다 property
. 그러나 이제는 설정 self.culture = ...
할 때마다 항상 정의 된 기능을 덮어 씁니다 getter
(이 경우 실제로 @property
포장 된 부분 일뿐 입니다. 더 미묘한 측정을 추가 할 수 는 있지만 한 가지 방법은 항상 단순한 것 이상을 포함 self.culture = ...
합니다. 예 :
class Grandparent:
# ...
@culture.setter
def culture(self, val):
if isinstance(val, tuple):
if val[1]:
print('Fine, have your own culture')
type(self).culture = None
self.culture = val[0]
else:
raise AttributeError("Oh no you don't")
# ...
class Child(Parent):
def __init__(self):
try:
# Usual setter
self.culture = "I'm a Gen X!"
except AttributeError:
# Trigger the overwrite condition
self.culture = "I'm a Boomer!", True
그것은 것 절대로 안돼요 이상 다른 대답보다 복잡한 size = None
클래스 수준.
and 또는 추가 메소드 를 처리하기 위해 자체 설명자를 작성하는 것도 고려할 수 있습니다 . 그러나 하루가 끝나면 참조 될 때 항상 먼저 트리거되고 참조 될 때 항상 먼저 트리거됩니다. 내가 시도한 한 주변을 돌아 다니지 않습니다.__get__
__set__
self.culture
__get__
self.culture = ...
__set__
문제의 핵심, IMO
내가 여기서 보는 문제는-당신은 당신의 케이크를 가지고 그것을 먹을 수 없습니다. property
유사한 의미 설명 과 같은 방법에서 편리하게 액세스 할 수있는 getattr
나 setattr
. 이 방법들이 다른 목적을 달성하기를 원한다면 문제를 묻는 것입니다. 아마도 접근 방식을 다시 생각할 것입니다.
- 나는
property
이것을 위해 정말로 필요 합니까?
- 방법이 다르게 제공 될 수 있습니까?
- 내가 필요한 경우
property
덮어 쓸 이유가 있습니까?
- 서브 클래스
property
가 적용되지 않으면 실제로 같은 패밀리에 속합니까?
- /을 덮어 써야 할 경우
property
재 할당이 실수로 property
s를 무효화 할 수 있기 때문에 별도의 방법이 단순히 재 할당하는 것보다 나을까요 ?
포인트 5의 경우 내 접근 방식은 overwrite_prop()
기본 클래스에 현재 클래스 속성을 덮어 쓰는 메소드가 있으므로 property
더 이상 트리거되지 않습니다.
class Grandparent:
# ...
def overwrite_props(self):
# reassign class attributes
type(self).size = None
type(self).len = None
# other properties, if necessary
# ...
# Usage
class Child(Parent):
def __init__(self):
self.overwrite_props()
self.size = 5
self.len = 10
보시다시피 여전히 약간의 생각은 있지만 적어도 cryptic보다 명시 적 size = None
입니다. 즉, 궁극적으로 속성을 덮어 쓰지 않고 루트에서 내 디자인을 다시 생각할 것입니다.
당신이 이것을 지금까지 만들었다면-나와 함께이 여행을 걸어 주셔서 감사합니다. 재미있는 작은 운동이었다.