eine Klassenmethode aufrufen

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
joh#
User
Beiträge: 139
Registriert: Freitag 6. November 2009, 13:16

Hallo Gemeinde,

soweit ich es bisher verstanden habe, sollte eine Klassenmethode (@classmethod....) doch
aufrufbar sein, wie jede andere Funktion auch?
Im Codebeispiel unten sollen der Reihe nach die states aus dem dict stateTab_XXX
eingenommen, d.h. dann die eingetragene Funktion ausgeführt werden. Da das im ganzen Programm
nur ein Mal passiert, halte ich erst eine Instanz bilden für entbehrlich. Nur sagt der Interpreter beim Aufruf
in der Funktion doStateChain :
"TypeError: 'classmethod' object is not callable" :

Code: Alles auswählen

from time import sleep

dev = 'dev'


###############################################################################
class State():
    '''
    '''

    def __init__(self, name, wsbFkt, param1, next):
        self.name = name
        self.wsbFkt = wsbFkt
        self.param1 = param1
        self.next = next


###############################################################################
class StateChain():
    """
    """
    state = None

    @classmethod
    def go(cls):
        '''
        '''
        return True

    @classmethod
    def wait_ms(cls, ms):
        '''
        '''
        sleep(ms/1000.)
        return True

    @classmethod
    def doNothing(cls):
        '''

        :return: 
        '''
        print('do nothing')
        return

    @classmethod
    def doStateChain(cls, chain, start):
        cls.state = start
        while cls.state != 'end':
            ch = chain[cls.state]
            print(f'{ch.name}') #####
            ch.wsbFkt(ch.param1)
            cls.state = ch.next


    stateTab_XXX = {'st1' : State('st1', wait_ms, (3000,), 'st2'),
                    'st2' : State('st2', go, (), 'st3'),
                    'st3' : State('st3', wait_ms, (5000,), 'st4'),
                    'st4' : State('st4', wait_ms, (1000,), 'end')
                    }



StateChain.doStateChain(StateChain.stateTab_XXX, 'st1')

???
VG
joh
Benutzeravatar
__blackjack__
User
Beiträge: 14054
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@joh#: Also erst einmal ist `StateChain.state` eine globale Variable, womit das sowieso alle so überhaupt keine gute Idee ist, weil Du hier einfach nur sehr viel komplexeren Code schreibst um ``global`` zu vermeiden, was das letztlich ja ist.

Das Problem ist das `classmethod()` einen Deskriptor erzeugt der dafür sorgt, dass das erste Argument an die Klasse gebunden wird von der man den Deskriptor abfragt. Dazu muss man den aber auch tatsächlich als Attribut von der Klasse abfragen. Und das geht an der Stelle nicht, denn die existiert erst nachdem der Körper von der ``class``-Anweisung komplett abgearbeitet wurde.

Am Ende wäre auch nur `doStateChain()` tatsächlich eine Klassenmethode, denn nur in der Methode wird etwas mit `cls` gemacht. Das andere sind einfach nur Funktionen. Also `staticmethod()` wenn man die denn unbedingt in eine Klasse stecken möchte, wo ich aber den Sinn nicht sehe warum man das tun will‽ Python ist nicht Java (oder C#); man muss nicht zwanghaft alles in Klassen stopfen.

Und das globale `state` wird auch nirgends verwendet, also warum ist das da überhaupt? Also letztlich braucht man nicht einmal ``global``.

Leere Docstrings sind keine gute Idee. Das könnte Werkzeuge dazu verleiten anzunehmen das der Code zu 100% dokumentiert ist, nur weil da formal Docstrings vorhanden sind, effektiv ist der aber so überhaupt nicht dokumentiert.

Die Rückgabewerte (``return True``) werden nicht verwendet.

Bei >100 Beiträgen wurde sicher schon mal erwähnt wie die Namenskonventionen bei Python aussehen.

Darüber hinaus sind kryptische Namen/Namensteile und angehängte Nummern wie in `wsbFkt`, `stateTab_XXX`, und `param1` nicht gut.

Bei `param1` ist das Problem das Du in der Datenstruktur ein Tupel mit Argumenten an dieses Attribut bindest, das dann später aber als *ein* Argument beim Aufruf übergibst. Da wolltest Du das sicher auf die Argumente der Funktion/Methode verteilen, die aufgerufen wird‽

Da es einen `State`-Typ gibt, würde man `state` eher `state_name` nennen, denn bei `state` würde der Leser sonst erwarten, dass es sich dabei um ein `state`-Objekt handeln würde. Was uns zum Namen `ch` bringt, der wie eine Abkürzung von `chain` wirkt, aber gar keine ”Kette” enthält, sondern einen `State`, womit *hier* dann `state` tatsächlich ein passender Name wäre.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
from time import sleep


class State:
    def __init__(self, name, function, arguments, next_name):
        self.name = name
        self.function = function
        self.arguments = arguments
        self.next_name = next_name


def go():
    print("go")


def wait_ms(ms):
    sleep(ms / 1000)


def do_nothing():
    print("do nothing")


def execute_state_chain(name_to_state, start_name):
    state_name = start_name
    while state_name != "end":
        state = name_to_state[state_name]
        print(state.name)
        state.function(*state.arguments)
        state_name = state.next_name


def main():
    name_to_state = {
        "st1": State("st1", wait_ms, [3000], "st2"),
        "st2": State("st2", go, [], "st3"),
        "st3": State("st3", wait_ms, [5000], "st4"),
        "st4": State("st4", wait_ms, [1000], "end"),
    }
    assert all(
        state.next_name == "end" or state.next_name in name_to_state
        for state in name_to_state.values()
    )

    execute_state_chain(name_to_state, "st1")


if __name__ == "__main__":
    main()
`State` könnte man noch durch einen `collections.namedtuple()`-Datentyp ersetzen. Oder Aufrufbar machen, damit man das aufrufen der Funktion mit den Argumenten nicht ausserhalb machen muss.

Letztlich ist das aber auch immer noch alles reichlich überkompliziert um eine Reihe von Funktionen aufzurufen. Das kann man zu folgendem vereinfachen:

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial
from time import sleep


def go():
    print("go")


def wait_ms(ms):
    sleep(ms / 1000)


def do_nothing():
    print("do nothing")


def main():
    actions = [
        partial(wait_ms, 3000),
        go,
        partial(wait_ms, 5000),
        partial(wait_ms, 1000),
    ]

    for action in actions:
        action()


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
joh#
User
Beiträge: 139
Registriert: Freitag 6. November 2009, 13:16

@__blackjack__
allerbesten Dank,
wohin kann ich die Kaffeespende adressieren?
Antworten