Bu sadece ücretsiz hizmet verebilir uzun soluklu bir cevap olacak ... ama sorunuz da benim bulguları (ve ağrı) paylaşmak istiyorum tavşan deliğinden aşağı bir yolculuk için götürdü.
Nihayetinde bu cevabı asıl sorununuza yardımcı olmayan bulabilirsiniz. Aslında, benim sonucum şu - bunu hiç yapmazdım. Bunu söyledikten sonra, daha fazla ayrıntı arıyorsanız, bu sonucun arka planı sizi biraz eğlendirebilir.
Bazı yanlış anlamaların giderilmesi
İlk cevap, çoğu durumda doğru olsa da, her zaman böyle değildir . Örneğin, şu sınıfı düşünün:
class Foo:
def __init__(self):
self.name = 'Foo!'
@property
def inst_prop():
return f'Retrieving {self.name}'
self.inst_prop = inst_prop
inst_prop
, a olmasına rağmen property
, geri döndürülemez bir örnek niteliğidir:
>>> 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!'
Her şey nereye bağlı senin property
is ilk etapta tanımladı. Eğer @property
"kapsam" sınıfında tanımlanmışsa (ya da gerçekten namespace
) sınıf özniteliğine dönüşür. Örneğimde, sınıfın kendisi inst_prop
somutlaştırılana kadar kimsenin farkında değil . Tabii ki, burada bir mülk olarak çok kullanışlı değil.
Ama önce miras çözümüyle ilgili yorumunuzu ele alalım ...
Peki kalıtım bu konuyu tam olarak nasıl etkiliyor? Aşağıdaki makale konuya ve Yöntem Çözüm Sırası'na biraz dalmaktadır. biraz ilişkilidir, ancak çoğunlukla Derinlik yerine kalıtımın genişliği tartışılmaktadır.
Bulgularımızla birlikte, aşağıdaki kurulum göz önüne alındığında:
@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"
Bu satırlar yürütüldüğünde ne olacağını düşünün:
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}')
Böylece sonuç:
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>
Nasıl olduğunu fark et:
self.world_view
uygulanırken, self.culture
başarısız rağmen
culture
içinde mevcut değil Child.__dict__
(mappingproxy
sınıfta sınıfın, örnekle karıştırılmaması)__dict__
)
- Olsa
culture
varc.__dict__
kaynak gösterilmemiş.
Neden bir özellik olarak sınıf world_view
üzerine yazıldığını tahmin edebilirsiniz Parent
, bu yüzden Child
de üzerine yazabilirsiniz. Bu arada, culture
kalıtsal olduğundan, sadece aşağıdakilerin içinde mappingproxy
bulunurGrandparent
:
Grandparent.__dict__ is: {
'__module__': '__main__',
'culture': <property object at 0x00694C00>,
'world_view': <property object at 0x00694C00>,
...
}
Aslında kaldırmaya çalışırsanız Parent.culture
:
>>> del Parent.culture
Traceback (most recent call last):
File "<pyshell#67>", line 1, in <module>
del Parent.culture
AttributeError: culture
Bunun için var olmadığını bile fark edeceksiniz Parent
. Çünkü nesne doğrudan geri dönüyor Grandparent.culture
.
Peki, Karar Emri ne olacak?
Bu yüzden asıl Çözüm Siparişini gözlemlemek istiyoruz, Parent.world_view
bunun yerine kaldırmayı deneyelim :
del Parent.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
Acaba sonuç ne?
c.world_view is: Family property
Child.world_view is: <property object at 0x00694C00>
Önceden world_view
property
atamayı başarılı bir şekilde başarmış olsak da, Büyükbaba'ya geri döndü self.world_view
! Ama ya world_view
diğer cevap gibi, sınıf düzeyinde zorla değişirsek ? Ya silersek? Geçerli sınıf niteliğini bir özellik olarak atarsak ne olur?
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}')
Sonuç:
# 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>
Bu ilginçtir, çünkü c.world_view
atadığımız örnek örneğine geri yüklenir Child.world_view
. İnstance niteliğini kaldırdıktan sonra, class özelliğine geri döner. Ve Child.world_view
özelliği yeniden atadıktan sonra , örnek özelliğine erişimini anında kaybediyoruz.
Bu nedenle, aşağıdaki çözünürlük sırasını tahmin edebiliriz :
- Bir sınıf özniteliği varsa ve bir ise
property
, değerinigetter
veya fget
(daha sonra bu konuda daha fazla) yoluyla alın. Geçerli sınıf ilk önce Temel sınıf son.
- Aksi takdirde, bir örnek niteliği varsa, örnek niteliği değerini alın.
- Aksi takdirde,
property
sınıf olmayan özniteliği alın. Geçerli sınıf ilk önce Temel sınıf son.
Bu durumda, kökü kaldıralım property
:
del Grandparent.culture
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')
Hangi verir:
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'
Ta-da! Child
şimdi culture
içine zorla sokma kendi kendi vardır c.__dict__
. Child.culture
o tanımlanan asla bu yana, tabii ki, yoksa Parent
veya Child
sınıf özniteliği ve Grandparent
'ın çıkarıldı.
Sorunumun temel nedeni bu mu?
Aslında hayır . Atarken biz hala gözlemliyoruz Aldığınız hata, self.culture
olduğu tamamen farklı . Ancak miras emri, zeminin cevabına zemin hazırlar - ki bu da property
kendisidir.
Daha önce bahsedilen getter
yöntemin yanı sıra, property
kollarını da birkaç düzgün hile var. Bu durumda en alakalı olan, çizgi ile tetiklenen setter
veya fset
yöntemidir self.culture = ...
. Senin bu yana property
herhangi uygulamak vermedi setter
veya fget
fonksiyonunu, piton ne yapacağını bilmiyor ve bir atar AttributeError
(yani yerine can't set attribute
).
Ancak bir setter
yöntem uyguladıysanız :
@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
Sınıfı başlatırken şunları elde edersiniz:
Instantiating Child class...
property setter is called!
An almak yerine, AttributeError
şimdi some_prop.setter
yöntemi çağırıyorsunuz . Böylece nesneniz üzerinde daha fazla kontrol sahibi olursunuz ... önceki bulgularımızla, mülke ulaşmadan önce üzerine yazılmış bir sınıf niteliğine sahip olmamız gerektiğini biliyoruz . Bu, temel sınıf içinde tetikleyici olarak uygulanabilir. İşte yeni bir örnek:
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)
Sonuç:
Fine, have your own culture
I'm a millennial!
TA-DAH! Artık devralınan bir mülk üzerinde kendi örnek özelliğinizin üzerine yazabilirsiniz!
Sorun çözüldü mü?
... Pek sayılmaz. Bu yaklaşımla ilgili sorun şu ki, artık uygun bir setter
yönteme sahip olamazsınız . Değerlerinizi ayarlamak istediğiniz durumlar vardır property
. Ama şimdi set zaman self.culture = ...
o olacak her zaman size tanımlanan işlev ne olursa olsun üzerine getter
(bu durumda, gerçekten sadece hangi @property
sarılı kısmı. Sen edebilirsiniz daha nüanslı önlemler katmakla kalmıyor öyle ya da böyle her zaman daha fazla sadece dahil edeceğiz self.culture = ...
. Örneğin:
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
Bu oluyor waaaaay daha başka cevap daha komplikesize = None
sınıf düzeyinde.
Ayrıca ve veya ek yöntemleri işlemek için kendi tanımlayıcınızı yazmayı da düşünebilirsiniz . Ancak günün sonunda , atıfta bulunulduğunda, her zaman önce tetiklenir ve ne zaman başvurulursa her zaman ilk olarak tetiklenir. Denediğim kadar etrafta dolaşmak yok.__get__
__set__
self.culture
__get__
self.culture = ...
__set__
Meselenin özü, IMO
Burada gördüğüm sorun, kekini yiyip yiyemezsin. property
, veya gibi yöntemlerden kolay erişime sahip bir tanımlayıcı anlamına gelir . Bu yöntemlerin farklı bir amaca ulaşmasını da istiyorsanız, sadece sorun istiyorsunuz. Belki de yaklaşımı yeniden düşünürüm:getattr
setattr
- Bunun
property
için gerçekten ihtiyacım var mı?
- Bir yöntem bana farklı bir şekilde hizmet edebilir mi?
- Bir ihtiyacım varsa
property
, üzerine yazmam için herhangi bir neden var mı?
- Alt sınıf gerçekten aynı aileye ait mi?
property
geçerli mi?
- Herhangi birinin / tümünün üzerine
property
yazmam gerekirse, yeniden atama yanlışlıkla property
s'yi geçersiz kılabileceğinden, ayrı bir yöntem bana yeniden atamaktan daha iyi hizmet eder mi?
Nokta 5 için, benim yaklaşım overwrite_prop()
taban sınıfında geçerli sınıf özniteliğinin üzerine yazılan bir yöntem property
olacaktır , böylece artık tetiklenmez:
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
Gördüğünüz gibi, hala biraz çelişkili olsa da, en azından bir şifreden daha açık size = None
. Bununla birlikte, nihayetinde, mülkün üzerine yazmayacağım ve tasarımımı kökünden yeniden düşüneceğim.
Şimdiye kadar yaptıysanız - bu yolculuğu benimle yürüdüğünüz için teşekkür ederim. Eğlenceli küçük bir egzersizdi.