Unterklasse: Ist es möglich, eine Eigenschaft mit einem herkömmlichen Attribut zu überschreiben?

14

Nehmen wir an, wir möchten eine Klassenfamilie erstellen, bei der es sich um unterschiedliche Implementierungen oder Spezialisierungen eines übergreifenden Konzepts handelt. Nehmen wir an, dass es für einige abgeleitete Eigenschaften eine plausible Standardimplementierung gibt. Wir möchten dies in eine Basisklasse einordnen

class Math_Set_Base:
    @property
    def size(self):
        return len(self.elements)

Eine Unterklasse kann also automatisch ihre Elemente in diesem ziemlich dummen Beispiel zählen

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self,*elements):
        self.elements = elements

Concrete_Math_Set(1,2,3).size
# 3

Was aber, wenn eine Unterklasse diese Standardeinstellung nicht verwenden möchte? Das funktioniert nicht:

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

Mir ist klar, dass es Möglichkeiten gibt, eine Eigenschaft mit einer Eigenschaft zu überschreiben, aber ich möchte das vermeiden. Weil der Zweck der Basisklasse darin besteht, dem Benutzer das Leben so einfach wie möglich zu machen und kein Aufblähen hinzuzufügen, indem eine (aus enger Sicht der Unterklasse) verschlungene und überflüssige Zugriffsmethode auferlegt wird.

Kann es gemacht werden? Wenn nicht, was ist die nächstbeste Lösung?

Paul Panzer
quelle

Antworten:

6

Eine Eigenschaft ist ein Datendeskriptor, der Vorrang vor einem gleichnamigen Instanzattribut hat. Sie können einen Nicht-Daten-Deskriptor mit einer eindeutigen __get__()Methode definieren: Ein Instanzattribut hat Vorrang vor dem Nicht-Daten-Deskriptor mit demselben Namen (siehe Dokumente) . Das Problem hierbei ist, dass das non_data_propertyunten definierte nur zu Berechnungszwecken dient (Sie können keinen Setter oder Deleter definieren), aber es scheint in Ihrem Beispiel der Fall zu sein.

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

Dies setzt jedoch voraus, dass Sie Zugriff auf die Basisklasse haben, um diese Änderungen vorzunehmen.

Arkelis
quelle
Das ist fast perfekt. Dies bedeutet also, dass eine Poperty, selbst wenn Sie nur den Getter definieren, implizit einen Setter hinzufügt, der nichts anderes tut, als die Zuweisung zu blockieren? Interessant. Ich werde diese Antwort wahrscheinlich akzeptieren.
Paul Panzer
Ja, nach der Dokumentation definiert eine Eigenschaft immer alle drei Descriptor Methoden ( __get__(), __set__()und __delete__()) , und sie heben ein , AttributeErrorwenn Sie für sie jede Funktion nicht zur Verfügung stellen. Siehe Python-Äquivalent zur Eigenschaftsimplementierung .
Arkelis
Solange Sie sich nicht um das setteroder __set__das Eigentum kümmern , wird dies funktionieren. Ich möchte Sie jedoch darauf hinweisen, dass dies auch bedeutet, dass Sie die Eigenschaft nicht nur auf Klassenebene ( self.size = ...), sondern auch auf Instanzebene (z. B. Concrete_Math_Set(1, 2, 3).size = 10gleichermaßen gültig) leicht überschreiben können . Denkanstoß :)
r.ook
Und Square_Integers_Below(9).size = 1ist auch gültig, da es sich um ein einfaches Attribut handelt. In diesem speziellen Anwendungsfall "Größe" mag dies akward erscheinen (verwenden Sie stattdessen eine Eigenschaft), aber im allgemeinen Fall "eine berechnete Requisite in der Unterklasse leicht überschreiben" kann dies in einigen Fällen gut sein. Sie können den Attributzugriff auch mit steuern __setattr__(), dies kann jedoch überwältigend sein.
Arkelis
1
Ich bin nicht anderer Meinung, dass es möglicherweise einen gültigen Anwendungsfall für diese gibt. Ich wollte nur die Einschränkung erwähnen, da dies die Debugging-Bemühungen in Zukunft erschweren kann. Solange Sie und OP die Auswirkungen kennen, ist alles in Ordnung.
r.ook
10

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:

  1. self.world_viewkonnte angewendet werden, während self.culturefehlgeschlagen
  2. cultureexistiert nicht in Child.__dict__(der mappingproxyKlasse, nicht zu verwechseln mit der Instanz __dict__)
  3. 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 :

  1. 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.
  2. Wenn andernfalls ein Instanzattribut vorhanden ist, rufen Sie den Instanzattributwert ab.
  3. 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:

  1. Benötige ich wirklich eine propertydafür?
  2. Könnte mir eine Methode anders dienen?
  3. Wenn ich eine brauche property, gibt es einen Grund, warum ich sie überschreiben müsste?
  4. Gehört die Unterklasse wirklich zur selben Familie, wenn diese propertynicht zutreffen?
  5. 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.

Turm
quelle
2
Wow, vielen Dank! Ich werde ein bisschen Zeit brauchen, um das zu verdauen, aber ich nehme an, ich habe danach gefragt.
Paul Panzer
6

A @propertywird auf Klassenebene definiert. In der Dokumentation wird ausführlich beschrieben, wie es funktioniert. Es genügt jedoch zu sagen, dass das Festlegen oder Abrufen der Eigenschaft zum Aufrufen einer bestimmten Methode erfolgt. Das propertyObjekt, das diesen Prozess verwaltet, wird jedoch mit der eigenen Definition der Klasse definiert. Das heißt, es ist als Klassenvariable definiert, verhält sich jedoch wie eine Instanzvariable.

Eine Folge davon ist, dass Sie es auf Klassenebene frei neu zuweisen können :

print(Math_Set_Base.size)
# <property object at 0x10776d6d0>

Math_Set_Base.size = 4
print(Math_Set_Base.size)
# 4

Und genau wie bei jedem anderen Namen auf Klassenebene (z. B. Methoden) können Sie ihn in einer Unterklasse überschreiben, indem Sie ihn einfach explizit anders definieren:

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

Wenn wir eine tatsächliche Instanz erstellen, schattiert die Instanzvariable einfach die gleichnamige Klassenvariable. Das propertyObjekt verwendet normalerweise einige Spielereien, um diesen Prozess zu manipulieren (dh Getter und Setter anwenden). Wenn der Name auf Klassenebene jedoch nicht als Eigenschaft definiert ist, geschieht nichts Besonderes und verhält sich so, wie Sie es von jeder anderen Variablen erwarten würden.

Grüner Umhang-Typ
quelle
Danke, das ist erfrischend einfach. Bedeutet dies, dass selbst wenn es in meiner Klasse und ihren Vorfahren niemals Eigenschaften gab, zuerst der gesamte Vererbungsbaum nach einem Namen auf Klassenebene durchsucht wird, bevor es sich überhaupt darum kümmert, die zu betrachten __dict__? Gibt es auch Möglichkeiten, dies zu automatisieren? Ja, es ist nur eine Zeile, aber es ist sehr kryptisch zu lesen, wenn Sie nicht mit den blutigen Details von Eigenschaften usw. vertraut sind
Paul Panzer
@PaulPanzer Ich habe die Verfahren dahinter nicht untersucht, daher konnte ich Ihnen selbst keine zufriedenstellende Antwort geben. Sie können versuchen, es aus dem cpython-Quellcode heraus zu rätseln, wenn Sie möchten. Was die Automatisierung des Prozesses angeht, gibt es meines Erachtens keinen guten Weg, dies zu tun, außer es entweder gar nicht erst zu einer Eigenschaft zu machen oder einfach einen Kommentar / eine Dokumentzeichenfolge hinzuzufügen, damit die Leser Ihres Codes wissen, was Sie tun . So etwas wie# declare size to not be a @property
Green Cloak Guy
4

Sie brauchen überhaupt keine Zuordnung size. sizeist eine Eigenschaft in der Basisklasse, sodass Sie diese Eigenschaft in der untergeordneten Klasse überschreiben können:

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)))

Sie können dies (mikro) optimieren, indem Sie die Quadratwurzel vorberechnen:

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
chepner
quelle
Das ist ein großartiger Punkt, überschreiben Sie einfach eine übergeordnete Eigenschaft mit einer untergeordneten Eigenschaft! +1
r.ook
Dies ist in gewisser Weise unkompliziert, aber ich war daran interessiert und habe speziell nach Möglichkeiten gefragt, wie eine Nicht-Eigenschaftsüberschreibung zugelassen werden kann, um nicht die Unannehmlichkeit aufzuerlegen, eine Memo-Eigenschaft schreiben zu müssen, bei der eine einfache Zuweisung das tun würde Job.
Paul Panzer
Ich bin mir nicht sicher, warum Sie Ihren Code so komplizieren möchten. Für den geringen Preis für das Erkennen, dass dies sizeeine Eigenschaft und kein Instanzattribut ist, müssen Sie nichts Besonderes tun .
Chepner
Mein Anwendungsfall sind viele untergeordnete Klassen mit vielen Attributen. Nicht 5 statt einer Zeile schreiben zu müssen, rechtfertigt ein gewisses Maß an Fantasie, IMO.
Paul Panzer
2

Es sieht so aus, als ob Sie definieren möchten size in Ihrer Klasse :

class Square_Integers_Below(Math_Set_Base):
    size = None

    def __init__(self, cap):
        self.size = int(math.sqrt(cap))

Eine andere Möglichkeit besteht darin, capin Ihrer Klasse zu speichern und diese mit einer sizeals Eigenschaft definierten Eigenschaft zu berechnen (die die Eigenschaft der Basisklasse überschreibt size).

Uri
quelle
2

Ich schlage vor, einen Setter wie folgt hinzuzufügen:

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

Auf diese Weise können Sie die Standardeigenschaft .sizewie folgt überschreiben :

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
Amir
quelle
1

Auch können Sie als nächstes tun

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))
Ryabchenko Alexander
quelle
Das ist ordentlich, aber du brauchst es nicht wirklich _sizeoder _size_callda. Sie könnten den Funktionsaufruf innerhalb sizeals Bedingung eingebrannt und try... except... zum Testen verwendet haben, _sizeanstatt eine zusätzliche Klassenreferenz zu verwenden, die nicht verwendet wird. Ungeachtet dessen halte ich dies für noch kryptischer als das einfache size = NoneÜberschreiben.
r.ook