전반적인 개념의 다른 구현 또는 전문화 인 클래스 패밀리를 작성하려고한다고 가정하십시오. 일부 파생 속성에 대해 적절한 기본 구현이 있다고 가정합니다. 이것을 기본 클래스에 넣고 싶습니다
class Math_Set_Base:
@property
def size(self):
return len(self.elements)
따라서 서브 클래스는이 어리석은 예제에서 자동으로 요소를 계산할 수 있습니다.
class Concrete_Math_Set(Math_Set_Base):
def __init__(self,*elements):
self.elements = elements
Concrete_Math_Set(1,2,3).size
# 3
그러나 서브 클래스가이 기본값을 사용하지 않으려면 어떻게해야합니까? 작동하지 않습니다.
import math
class Square_Integers_Below(Math_Set_Base):
def __init__(self,cap):
self.size = int(math.sqrt(cap))
Square_Integers_Below(7)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 3, in __init__
# AttributeError: can't set attribute
속성을 속성으로 재정의하는 방법이 있지만이를 피하고 싶습니다. 기본 클래스의 목적은 사용자가 가능한 한 쉽게 생활을 할 수 있도록하기 위해, 서브 클래스의 좁은 관점에서 복잡하고 불필요한 액세스 방법을 강요하여 부풀림을 추가하지 않는 것입니다.
할 수 있습니까? 그렇지 않다면 차선책은 무엇입니까?
답변
속성은 이름이 같은 인스턴스 속성보다 우선하는 데이터 설명자입니다. 고유 한 __get__()
방법 으로 비 데이터 디스크립터를 정의 할 수 있습니다 . 인스턴스 속성이 동일한 이름 의 비 데이터 디스크립터 보다 우선 합니다 (docs 참조) . 여기서 문제는 non_data_property
아래 정의 된 것은 계산 목적으로 만 (세터 또는 삭제자를 정의 할 수 없음) 있지만 귀하의 예에서는 그렇습니다.
import math
class non_data_property:
def __init__(self, fget):
self.__doc__ = fget.__doc__
self.fget = fget
def __get__(self, obj, cls):
if obj is None:
return self
return self.fget(obj)
class Math_Set_Base:
@non_data_property
def size(self, *elements):
return len(self.elements)
class Concrete_Math_Set(Math_Set_Base):
def __init__(self, *elements):
self.elements = elements
class Square_Integers_Below(Math_Set_Base):
def __init__(self, cap):
self.size = int(math.sqrt(cap))
print(Concrete_Math_Set(1, 2, 3).size) # 3
print(Square_Integers_Below(1).size) # 1
print(Square_Integers_Below(4).size) # 2
print(Square_Integers_Below(9).size) # 3
그러나이 변경을 수행하려면 기본 클래스에 액세스 할 수 있다고 가정합니다.
답변
이것은 무료로만 제공 될 수있는 긴 바람의 대답이 될 것입니다 …
궁극적으로이 답변이 실제 문제에 도움이되지 않을 수 있습니다. 사실, 내 결론은-나는 이것을 전혀하지 않을 것입니다. 말했듯이,이 결론의 배경은 자세한 내용을 찾고 있기 때문에 약간 재미있을 수 있습니다.
약간의 오해를 해결
대부분의 경우 첫 번째 대답은 정확하지만 항상 그런 것은 아닙니다 . 예를 들어 다음 클래스를 고려하십시오.
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
입니다. 즉, 궁극적으로 속성을 덮어 쓰지 않고 루트에서 내 디자인을 다시 생각할 것입니다.
당신이 이것을 지금까지 만들었다면-나와 함께이 여행을 걸어 주셔서 감사합니다. 재미있는 작은 운동이었다.
답변
A @property
는 클래스 수준에서 정의됩니다. 문서는 어떻게 작동하는지에 대한 철저한 내용이수록되어 있지만, 그 말을 충분 설정 하거나 점점 특정 메소드를 호출에 속성 결의를. 그러나이 property
프로세스를 관리 하는 객체는 클래스 자체 정의로 정의됩니다. 즉, 클래스 변수로 정의되지만 인스턴스 변수처럼 동작합니다.
이것의 한 가지 결과 는 클래스 레벨에서 자유롭게 재 할당 할 수 있다는 것입니다 .
print(Math_Set_Base.size)
# <property object at 0x10776d6d0>
Math_Set_Base.size = 4
print(Math_Set_Base.size)
# 4
다른 클래스 수준 이름 (예 : 메서드)과 마찬가지로 명시 적으로 다르게 정의하여 하위 클래스에서이를 재정의 할 수 있습니다.
class Square_Integers_Below(Math_Set_Base):
# explicitly define size at the class level to be literally anything other than a @property
size = None
def __init__(self,cap):
self.size = int(math.sqrt(cap))
print(Square_Integers_Below(4).size) # 2
print(Square_Integers_Below.size) # None
실제 인스턴스를 만들 때 인스턴스 변수는 단순히 같은 이름의 클래스 변수를 음영 처리합니다. property
객체는 일반적으로이 과정 (즉, 적용 getter 및 setter)하지만 클래스 수준의 이름을 속성으로 정의되어 있지 않은 경우, 아무것도 특별한 일이 발생을 조작하는 몇 가지 헛소리를 사용하고, 당신이 다른 변수의 예상대로 그래서이 역할을합니다.
답변
과제 ( size
) 가 전혀 필요하지 않습니다 . size
기본 클래스의 속성이므로 자식 클래스에서 해당 속성을 재정의 할 수 있습니다.
class Math_Set_Base:
@property
def size(self):
return len(self.elements)
# size = property(lambda self: self.elements)
class Square_Integers_Below(Math_Set_Base):
def __init__(self, cap):
self._cap = cap
@property
def size(self):
return int(math.sqrt(self._cap))
# size = property(lambda self: int(math.sqrt(self._cap)))
제곱근을 미리 계산하여 이것을 (마이크로) 최적화 할 수 있습니다.
class Square_Integers_Below(Math_Set_Base):
def __init__(self, cap):
self._size = int(math.sqrt(self._cap))
@property
def size(self):
return self._size
답변
size
수업에서 정의하려는 것처럼 보입니다 .
class Square_Integers_Below(Math_Set_Base):
size = None
def __init__(self, cap):
self.size = int(math.sqrt(cap))
다른 옵션은 cap
클래스 에 저장 size
하고 속성 으로 정의 하여 계산하는 것입니다 (기본 클래스의 속성을 재정의 함 size
).
답변
다음과 같이 세터를 추가하는 것이 좋습니다.
class Math_Set_Base:
@property
def size(self):
try:
return self._size
except:
return len(self.elements)
@size.setter
def size(self, value):
self._size = value
이렇게하면 다음 .size
과 같이 기본 속성을 재정의 할 수 있습니다 .
class Concrete_Math_Set(Math_Set_Base):
def __init__(self,*elements):
self.elements = elements
class Square_Integers_Below(Math_Set_Base):
def __init__(self,cap):
self.size = int(math.sqrt(cap))
print(Concrete_Math_Set(1,2,3).size) # 3
print(Square_Integers_Below(7).size) # 2
답변
또한 다음을 할 수 있습니다
class Math_Set_Base:
_size = None
def _size_call(self):
return len(self.elements)
@property
def size(self):
return self._size if self._size is not None else self._size_call()
class Concrete_Math_Set(Math_Set_Base):
def __init__(self, *elements):
self.elements = elements
class Square_Integers_Below(Math_Set_Base):
def __init__(self, cap):
self._size = int(math.sqrt(cap))