これは長続きする答えであり、補足になるだけかもしれません...しかし、あなたの質問は私にウサギの穴を下るのにかかりましたので、私の発見(と痛み)も共有したいと思います。
最終的には、この答えが実際の問題に役立たない場合があります。実際、私の結論は次のとおりです。これはまったく行いません。そうは言っても、詳細を探しているので、この結論の背景は少し楽しませるかもしれません。
誤解に対処する
最初の答えは、ほとんどの場合正しいですが、常にそうであるとは限りません。たとえば、次のクラスを考えます。
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
クラス「スコープ」(または実際にはnamespace
)内で定義されている場合、それはクラス属性になります。私の例では、クラス自体はinst_prop
インスタンス化されるまで何も認識しません。もちろん、ここではプロパティとしてはあまり役に立ちません。
しかし、最初に、継承の解決に関するコメントに取り組みましょう...
では、継承はこの問題にどの程度正確に影響するのでしょうか?次の記事では、このトピックについて少し詳しく説明しますが、メソッド解決の順序には多少の関連がありますが、主に、深さではなく継承の幅について説明しています。
以下の設定を前提として、私たちの発見と組み合わせます:
@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
割り当てに成功したにもかかわらず、それはGrandparentのに戻りました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
実装していないので、pythonは何をすべきかわからず、代わりに(つまり)をスローします。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
それはだwaaaaayより、他の答えよりも複雑なsize = None
クラスレベルで。
and 、または追加のメソッドを処理する代わりに、独自の記述子を作成することも検討できます。しかし、結局のところ、が参照されると、が常に最初にトリガーされ、参照されると、常に最初にトリガーされます。私が試した限り、それを回避する方法はありません。__get__
__set__
self.culture
__get__
self.culture = ...
__set__
問題の核心、IMO
私がここで見る問題は、あなたがケーキを持って食べられないことです。 property
以下のように意味される記述子のようなメソッドからの便利なアクセスを持ちますgetattr
かsetattr
。これらの方法でも別の目的を達成したい場合は、問題を求めているだけです。私はおそらくアプローチを再考するでしょう:
property
これには本当に必要ですか?
- メソッドは私に何か異なるものを提供できますか?
- 私が必要な場合は
property
、私はそれを上書きする必要があります何らかの理由はありますか?
- これら
property
が当てはまらない場合、サブクラスは本当に同じファミリーに属していますか?
- いずれかまたはすべて
property
のs を上書きする必要がある場合、再割り当てによって誤って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
ご覧のように、まだ少し工夫されていますが、不可解なものより少なくとも明白ですsize = None
。とはいえ、最終的にはプロパティをまったく上書きせず、設計を根本から見直します。
ここまで来たなら、私と一緒にこの旅を歩いてくれてありがとう。それは楽しい小さな運動でした。