Zuweisung an self.__class__: Ist dieser Code denn noch zu retten?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
gotridofmyphone
User
Beiträge: 33
Registriert: Mittwoch 15. März 2017, 08:54

Hallo,

mir ist klar, dass hier zunächst eine Warnung kommen muss: Kinder, macht das nicht zu Hause nach. Zuweisung an self.__class__ ist böse, frisst kleine Kätzchen, oder so.

Nun zu meinem Problem. Wie mache ich es, ohne an __class__ herumzupfuschen? Von so code smell, der mich bitter an Hacks in Perl mit bless erinnert. will ich eigentlich nichts in meinem Programm haben, mir fällt aber leider keine Alternative ein. Kann ein Hinweis auf ein Design bug sein, vielleicht so meine Hoffnung, klärt mich jemand auf.

Code: Alles auswählen

class SoundGenerator(Shape):
    __slots__ = tuple()

    def __init__(self, initial, the_list, term):
        # ...
        super(SoundGenerator, self).__init__((mylist[-2][0], 0), *mylist[1:-1])
        del self.coords[0]

    @classmethod
    def weighted_average(cls, l, dist, r):

        if not l.length == r.length:
            shorter, longer = sorted((l, r), key=lambda s: s.length )
            div = shorter.length / longer.length
            shorter.length = longer.length
            for c in shorter.coords: c.x *= div
            shorter.coords.append( longer.coords[-1].new_alike(y=0) )

        self = super().weighted_average(l, dist, r, coord0=False)
        self.__class__ = cls
        del self.coords[0]

        return self

Code: Alles auswählen

class Shape:
    __slots__ = ['length', 'coords', 'y_max']
    def __init__(self, span, *coords):

    @classmethod
    def weighted_average (cls, left, dist, right, coord0=True):
        """
        Assuming a smooth transition to a right curve in a distance,
        Shape.weighted_average(left_shape, distance, right_shape) returns an
        intermediate shape at a given position < 1 and > 0.
        """
       # ...
       return Shape( (adj_length, max_y), *coords[int(coord0):] )
Den Gedanken, dass in der letzten Zeile "Shape" durch cls ersetzt werden könnte, hab ich schon gehabt. Der liegt ja nahe, scheitert aber leider daran, dass der Konstruktor von SoundGenerator andere Aufrufparameter erwartet als der von seiner Basisklasse.

Das Programm in seiner Gesamtheit funktioniert wie es soll, also grundsätzlich, mein Problem sind halt ein paar hässliche Stellen. Ich hoffe daher, etwaige Änderungsvorschläge sind minimalinvasiv. Falls jemand gerne mehr Kontext hätte – den vollständigen Code zu den zitierten Fragmenten gibt es auf Github: sound_generator.py bzw. shape/__init__.py


Danke!
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Indem du das Argument cls der classmethod auch benutzt. Das ist immer die Klasse auf der du die Klassenmethode aufgerufen hast.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@__deets__: das geht nicht, weil die __init__-Signaturen nicht passend sind.

@gotridofmyphone: warum haben die Klassen so verschiedene __init__s? Beim Überfliegen habe ich viele Classmethods gefunden, die eigentlich keine sind. In Python muß man nicht alles in Klassen stopfen. Oft reichen normale Funktionen, die Exemplare verschiedener Klassen erzeugen. Mit Funktionen kannst Du auch dafür sorgen, dass das Erzeugen zwar individuelle Parameter hat, die Klassen an sich aber allgemeiner sind.

__slots__ sind selten sinnvoll. Lass sie weg.
Ein not a == b wäre besser a != b.
Wenn man einer Funktion Parameter übergibt, ist es sehr überraschend (eine böse Fehlerquelle), wenn sich die Objekte verändern. Für das umskalieren der coords wäre eine Methode passend, die ein neues Objekt liefert.
gotridofmyphone
User
Beiträge: 33
Registriert: Mittwoch 15. März 2017, 08:54

Sirius3 hat geschrieben:@gotridofmyphone: warum haben die Klassen so verschiedene __init__s? Beim Überfliegen habe ich viele Classmethods gefunden, die eigentlich keine sind. In Python muß man nicht alles in Klassen stopfen. Oft reichen normale Funktionen, die Exemplare verschiedener Klassen erzeugen. Mit Funktionen kannst Du auch dafür sorgen, dass das Erzeugen zwar individuelle Parameter hat, die Klassen an sich aber allgemeiner sind.
Der Nachteil der schlichten Funktionen ist halt, dass Polymorphie so nicht möglich ist. Ich glaube zwar nicht, dass ich die hier brauche, möchte mir den Weg aber auch nicht unnötig verbauen. Als Kompromiss werde ich der SoundGenerator-Klasse daher eine weitere Klassenmethode assemble_from_definition() geben. Führe ich eines Tages eine Klasse ein, die von SoundGenerator erbt, kann ich z.B. sagen LittleSoundGenerator.assemble_from_definition(), wobei dank der Vererbung SoundGenerator.assemble_from_definition() aufgerufen wird. Mit einfachen Funktionen ist das nicht möglich.
__slots__ sind selten sinnvoll. Lass sie weg.
Umgehend nach einer stichhaltigen Begründung. :wink:
Nach allem was ich über __slots__ gelesen habe, eignen sie sich für Klassen mit einem fixen Set von Attributen (trifft zu), von denen mutmaßlich viele Instanzen im Speicher vorgehalten werden müssen (trifft zu) und mutmaßlich keine Mehrfachvererbung von Klassen ohne __slots__ stattfindet (ditto). __slots__ bewirkt eine effizientere Speicherung der Objektattribute in Listen, wobei der Listenindex aus der Instanz ermittelt wird. Damit entfällt das sonst allfällige Hashing von Attributen bei Zugriffen. Hab ich gelesen, lasse mich durchaus gern eines besseren belehren.
Ein not a == b wäre besser a != b.
Oft ja. Man sollte dabei aber beachten, dass __eq__() und __ne__() unabhängig voneinander überschrieben werden können. Sie sind also nicht notwendig gleichbedeutend. Zudem verstehe ich unter "!=" "verschieden", nicht so negativ wie "nicht gleich". Am Ende ist das halt Geschmackssache, aber ich hab ja schließlich um Meinungen gefragt. Eher muss ich mir vorwerfen lassen, dass ich nicht konsistent bin, im Eingangspost benutze ich nämlich not ==.
Wenn man einer Funktion Parameter übergibt, ist es sehr überraschend (eine böse Fehlerquelle), wenn sich die Objekte verändern. Für das umskalieren der coords wäre eine Methode passend, die ein neues Objekt liefert.
Ja, das sehe ich ein, ist auch mehr Code in progress. Die Passage im ersten Codeblock wird aber wohl eh nicht lange überleben. Das Problem der Interpolation, die die Abstände zwischen den Teiltönen eines Klangs verfälscht und das dafür sorgt, dass bestimmte Akkorde durch zu dominante Schwebungen verwaschen werden, muss ich ganz anders angehen, glaube ich.


Danke!
gotridofmyphone
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@gotridofmyphone: die Argumentation sollte umgekehrt sein, hast Du Probleme, wenn Du `__slots__` wegläßt? Solange Du keine hast, brauchst Du es auch nicht benutzen.

Wenn != nicht not == ist, dann hast Du ein schweres Logikproblem. Dass es möglich ist, heißt nicht, dass man das jemals machen sollte. != ist einfach nur kürzer und besser lesbar.
Antworten