Dies wird eine langwierige Antwort sein, die möglicherweise nur als Komplementär dient ... aber Ihre Frage hat mich zu einer Fahrt durch das Kaninchenloch geführt, sodass ich auch meine Erkenntnisse (und Schmerzen) mitteilen möchte.
Möglicherweise ist diese Antwort für Ihr eigentliches Problem nicht hilfreich. In der Tat ist meine Schlussfolgerung, dass - ich würde das überhaupt nicht tun. Der Hintergrund dieser Schlussfolgerung könnte Sie jedoch ein wenig unterhalten, da Sie nach weiteren Details suchen.
Ein Missverständnis ansprechen
Die erste Antwort ist zwar in den meisten Fällen richtig, aber nicht immer der Fall. Betrachten Sie zum Beispiel diese Klasse:
class Foo:
def __init__(self):
self.name = 'Foo!'
@property
def inst_prop():
return f'Retrieving {self.name}'
self.inst_prop = inst_prop
inst_propist ein propertyunwiderrufliches Instanzattribut:
>>> 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!'
Es hängt alles davon , wo Ihr propertyin der ersten Stelle definiert. Wenn Ihr @propertyinnerhalb der Klasse "scope" (oder wirklich der namespace) definiert ist, wird es zu einem Klassenattribut. In meinem Beispiel sind der Klasse selbst keine bekannt, inst_propbis sie instanziiert wird. Natürlich ist es als Immobilie hier überhaupt nicht sehr nützlich.
Aber lassen Sie uns zuerst Ihren Kommentar zur Auflösung der Vererbung ansprechen ...
Wie genau spielt die Vererbung in diesem Problem eine Rolle? Dieser folgende Artikel befasst sich ein wenig mit dem Thema, und die Reihenfolge der Methodenauflösung ist etwas verwandt, obwohl hauptsächlich die Breite der Vererbung anstelle der Tiefe behandelt wird.
Kombiniert mit unserem Ergebnis, angesichts der folgenden Einstellungen:
@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"
Stellen Sie sich vor, was passiert, wenn diese Zeilen ausgeführt werden:
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}')
Das Ergebnis ist also:
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>
Beachte wie:
self.world_viewkonnte angewendet werden, während self.culturefehlgeschlagen
cultureexistiert nicht in Child.__dict__(der mappingproxyKlasse, nicht zu verwechseln mit der Instanz __dict__)
- Obwohl in
cultureexistiert c.__dict__, wird nicht darauf verwiesen.
Möglicherweise können Sie erraten, warum - world_viewwurde von der ParentKlasse als Nicht-Eigenschaft Childüberschrieben , sodass Sie es auch überschreiben konnten. In der Zwischenzeit existiert es, da culturees vererbt wird, nur innerhalb mappingproxyvonGrandparent :
Grandparent.__dict__ is: {
'__module__': '__main__',
'culture': <property object at 0x00694C00>,
'world_view': <property object at 0x00694C00>,
...
}
In der Tat, wenn Sie versuchen zu entfernen Parent.culture:
>>> del Parent.culture
Traceback (most recent call last):
File "<pyshell#67>", line 1, in <module>
del Parent.culture
AttributeError: culture
Sie werden feststellen, dass es nicht einmal existiert Parent. Weil sich das Objekt direkt auf verweist Grandparent.culture.
Was ist also mit der Resolution Order?
Wir sind also daran interessiert, die tatsächliche Auflösungsreihenfolge zu beachten. Versuchen wir Parent.world_viewstattdessen , Folgendes zu entfernen :
del Parent.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
Frage mich, was das Ergebnis ist?
c.world_view is: Family property
Child.world_view is: <property object at 0x00694C00>
Es kehrte zu Großeltern zurück world_view property, obwohl wir es erfolgreich geschafft hatten, das self.world_viewvorher zuzuweisen ! Aber was ist, wenn wir uns world_viewauf Klassenebene gewaltsam ändern , wie die andere Antwort? Was ist, wenn wir es löschen? Was ist, wenn wir das aktuelle Klassenattribut einer Eigenschaft zuweisen?
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}')
Das Ergebnis ist:
# 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>
Dies ist interessant, da das c.world_viewInstanzattribut wiederhergestellt wird, während Child.world_viewes das von uns zugewiesene ist. Nach dem Entfernen des Instanzattributs wird auf das Klassenattribut zurückgegriffen. Und nachdem Child.world_viewwir das der Eigenschaft neu zugewiesen haben , verlieren wir sofort den Zugriff auf das Instanzattribut.
Daher können wir die folgende Auflösungsreihenfolge vermuten :
- Wenn ein Klassenattribut vorhanden ist und es ein ist
property, rufen Sie seinen Wert über getteroder ab fget(dazu später mehr). Aktuelle Klasse zuerst bis Basisklasse zuletzt.
- Wenn andernfalls ein Instanzattribut vorhanden ist, rufen Sie den Instanzattributwert ab.
- Andernfalls rufen Sie das Nichtklassenattribut ab
property. Aktuelle Klasse zuerst bis Basisklasse zuletzt.
In diesem Fall entfernen wir die Wurzel property:
del Grandparent.culture
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')
Welches gibt:
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 Dah! Childhat jetzt ihre eigene culturebasierend auf dem kraftvollen Einfügen in c.__dict__. Child.cultureexistiert natürlich nicht, da es nie in Parentoder ChildKlassenattribut definiert wurde und Grandparent's entfernt wurde.
Ist das die Hauptursache für mein Problem?
Eigentlich nein . Der Fehler, den Sie beim Zuweisen immer noch beobachten self.culture, ist völlig anders . Aber die Vererbungsreihenfolge bildet den Hintergrund für die Antwort - die das propertySelbst ist.
Haben Sie neben der zuvor erwähnten getterMethode propertyauch ein paar nette Tricks im Ärmel. Am relevantesten ist in diesem Fall die Methode setteroder fset, die durch die self.culture = ...Leitung ausgelöst wird . Da Sie propertykeine setteroder keine fgetFunktion implementiert haben , weiß Python nicht, was zu tun ist, und wirft AttributeErrorstattdessen eine (dh can't set attribute).
Wenn Sie jedoch eine setterMethode implementiert haben :
@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...
Wenn Sie die ChildKlasse instanziieren , erhalten Sie:
Instantiating Child class...
property setter is called!
Anstatt eine zu erhalten AttributeError, rufen Sie jetzt tatsächlich die some_prop.setterMethode auf. Dadurch haben Sie mehr Kontrolle über Ihr Objekt. Aufgrund unserer früheren Erkenntnisse wissen wir, dass ein Klassenattribut überschrieben werden muss, bevor es die Eigenschaft erreicht. Dies könnte innerhalb der Basisklasse als Trigger implementiert werden. Hier ist ein neues Beispiel:
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)
Was in ... resultiert:
Fine, have your own culture
I'm a millennial!
TA DAH! Sie können jetzt Ihr eigenes Instanzattribut über eine geerbte Eigenschaft überschreiben!
Also, Problem gelöst?
... Nicht wirklich. Das Problem bei diesem Ansatz ist, dass Sie jetzt keine richtige setterMethode haben können. Es gibt Fälle, in denen Sie Werte für Ihre festlegen möchten property. Aber jetzt , wenn stellen Sie self.culture = ...sie immer überschreiben , was Funktion , die Sie in der definiert getter(was in diesem Fall wirklich nur der ist @propertyumschlungenen Bereich. Sie können in differenzierteren Maßnahmen hinzufügen, aber eine oder andere Weise , es wird immer mehr als nur beinhalten self.culture = .... z.B:
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
Es ist auf Klassenebene komplizierter als die andere Antwort size = None.
Sie können auch einen eigenen Deskriptor schreiben , um die Methoden __get__und __set__oder zusätzliche Methoden zu verarbeiten. Aber am Ende des Tages, wenn self.cultureverwiesen wird, __get__wird immer zuerst ausgelöst, und wenn self.culture = ...verwiesen wird, __set__wird immer zuerst ausgelöst. Soweit ich es versucht habe, führt kein Weg daran vorbei.
Der Kern des Problems, IMO
Das Problem, das ich hier sehe, ist - Sie können Ihren Kuchen nicht haben und ihn auch essen. propertyist wie ein Deskriptor mit bequemem Zugriff von Methoden wie getattroder gemeint setattr. Wenn Sie auch möchten, dass diese Methoden einen anderen Zweck erfüllen, fragen Sie nur nach Problemen. Ich würde vielleicht den Ansatz überdenken:
- Benötige ich wirklich eine
propertydafür?
- Könnte mir eine Methode anders dienen?
- Wenn ich eine brauche
property, gibt es einen Grund, warum ich sie überschreiben müsste?
- Gehört die Unterklasse wirklich zur selben Familie, wenn diese
propertynicht zutreffen?
- Wenn ich ein oder alle
propertys überschreiben muss , würde mir eine separate Methode besser dienen als nur eine Neuzuweisung, da eine Neuzuweisung die propertys versehentlich ungültig machen kann ?
Für Punkt 5 wäre mein Ansatz eine overwrite_prop()Methode in der Basisklasse, die das aktuelle Klassenattribut überschreibt, so dass das propertynicht mehr ausgelöst wird:
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
Wie Sie sehen können, ist es, obwohl es noch ein bisschen erfunden ist, zumindest expliziter als ein kryptisches size = None. Letztendlich würde ich die Eigenschaft jedoch überhaupt nicht überschreiben und mein Design von Grund auf überdenken.
Wenn Sie es bis hierher geschafft haben - danke, dass Sie diese Reise mit mir gegangen sind. Es war eine lustige kleine Übung.