Discord Bot mit ZaehlerKlasse

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.
Phobit
User
Beiträge: 185
Registriert: Freitag 4. Mai 2018, 18:13

Hallo,
wie in einem vorherigen Post schonmal erwähnt, wrde ich gerne in keinen Discord Bot einen Counter einbetten.
Dieser soll den Befehlsaufruf "mitzählen" und je nach aufruf die Discord Presence des Bots ändern.
Mir wurde gesagt, dass dies nur mit Klassen möglich sei, also habe ich mich mal ein bisschen mit Klassen auseinander gesetzt, ich kann es halt noch nicht "richtig". Hier ist mein Entwurf für den Code mit einer "Zaehler" Klasse:

Code: Alles auswählen

async def ex(message, client, invoke, args):

    class Zaehler(object):
        counter = 0

        def __init__(self):
            type(self).counter += 1
            print(type(self).counter)
            if type(self).counter == 7:
                type(self).counter == 1
    Zaehler()
    if Zaehler.counter == 1:
        await client.change_presence(game=Game(name="Presence1"))
    elif Zaehler.counter == 2:
        await client.change_presence(game=Game(name="Presence2"))
    elif Zaehler.counter == 3:
        await client.change_presence(gamne=Game(name="Presence3"))
    elif Zaehler.counter == 4:
        await client.change_presence(game=Game(name="Presence4"))
    elif Zaehler.counter == 5:
        await client.change_presence(game=Game(name="Presence5"))
    elif Zaehler.counter == 6:
        await client.change_presence(game=Game(name="Presence6"))
Bei Ausführng des Codes, genauer dem Aufruf dieses Moduls passiert folgendes:
er schreibt in die Konsole "1" und drunter gleich nommal "1". Dann der fette Error:

1
1
Ignoring exception in on_message
Traceback (most recent call last):
File "/home/phobit/PycharmProjects/DiscordSippenBot/Discord/Main.py", line 140, in on_message
await cmd.ex(args, message, client, invoke)
File "/home/phobit/PycharmProjects/DiscordSippenBot/Discord/commands/cmd_presence.py", line 21, in ex
await client.change_presence(game=Game(name="Lotro"))
AttributeError: 'Message' object has no attribute 'change_presence'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/home/phobit/PycharmProjects/DiscordSippenBot/venv/lib/python3.6/site-packages/discord/client.py", line 307, in _run_event
yield from getattr(self, event)(*args, **kwargs)
File "/home/phobit/PycharmProjects/DiscordSippenBot/Discord/Main.py", line 142, in on_message
await cmd.ex(args, message, client, invoke)
File "/home/phobit/PycharmProjects/DiscordSippenBot/Discord/commands/cmd_presence.py", line 21, in ex
await client.change_presence(game=Game(name="Lotro"))
AttributeError: 'Message' object has no attribute 'change_presence'

Sobald ich die Klasse ausbaue, erkennt Python change_presence wieder, ich habe auch keine Ahnung wie er da auf "Message" kommt...

Weiß wer weiter?
Mir egal, ob der Code schön ist oder nicht.
Hauptsache er funkt!
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Das kann nicht sein, weil die Klasse (so sinnfrei sie auch sein mag) nichts an client verändert. Was man aber an der Fehlermeldung ablesen kann, ist, dass `ex` mit args, message, client, invoke aufgerufen wird, die Parameter Deiner Funktion haben aber eine andere Reihenfolge haben.
Phobit
User
Beiträge: 185
Registriert: Freitag 4. Mai 2018, 18:13

ok gut, danke jetzt läufts ohne fehlermeldung. Allerdings wäre da immer noch ein anderes Problem, der Counter erhöht sich nicht, er bleibt bei jedem Befehlsaufruf auf 1, weiß da jemand was? (Der Code ist bis auf das args, message, client, invoke noch der selbe)
Mir egal, ob der Code schön ist oder nicht.
Hauptsache er funkt!
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Phobit: Die Funktion wird aufgerufen, dann wird eine neue Klasse `Zaehler` erstellt mit einem Klassenattribut `counter` das mit 0 initialisiert wird. Danach wird ein Exemplar dieser Klasse erstellt. Dabei wird in der `__init__()` auf eine interessante, umständliche, Art und weise das `counter`-Attribut auf der Klasse um eins erhöht. Das Exemplar was da erzeugt wurde, wird einfach sofort weggeworfen. Und nachdem der Rest der Funktion abgearbeitet wurde, wird auch die Klasse weggeworfen. Beim nächsten Aufruf von `ex()` wiederholt sich das alles.

Diese Zählerklasse und alles was damit gemacht wird ist ziemlich unsinnig. Man würde hier mit `partial()` ein zusätzliches Argument an `ex()` binden, an der Stelle wo man die Funktion als Rückruffunktion registriert. Zum Beispiel einen Iterator der mit `itertools.cycle()` und `range()` erstellt wurde und die gewünschten Zahlen liefert. Oder `ex()` müsste eine Methode auf einem Objekt sein, dass den Zahlwert als Attribut hat.

Diese ganze ``if``/``elif``-Kaskade ist zu viel Code. Da wird doch überall das gleiche gemacht, ausser das sich ein Zeichen in einer Zeichenkette unterscheidet. Und das ist immer der Wert des Zählers als Zeichenkette. Das wäre also ein einzige Quelltextzeile.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Phobit
User
Beiträge: 185
Registriert: Freitag 4. Mai 2018, 18:13

@Phobit: Die Funktion wird aufgerufen, dann wird eine neue Klasse `Zaehler` erstellt mit einem Klassenattribut `counter` das mit 0 initialisiert wird. Danach wird ein Exemplar dieser Klasse erstellt. Dabei wird in der `__init__()` auf eine interessante, umständliche, Art und weise das `counter`-Attribut auf der Klasse um eins erhöht. Das Exemplar was da erzeugt wurde, wird einfach sofort weggeworfen. Und nachdem der Rest der Funktion abgearbeitet wurde, wird auch die Klasse weggeworfen. Beim nächsten Aufruf von `ex()` wiederholt sich das alles.
Wie genau kann ich dieses wiederholen stoppen, sodass sich Counter aufrecht erhält?
Diese Zählerklasse und alles was damit gemacht wird ist ziemlich unsinnig. Man würde hier mit `partial()` ein zusätzliches Argument an `ex()` binden, an der Stelle wo man die Funktion als Rückruffunktion registriert. Zum Beispiel einen Iterator der mit `itertools.cycle()` und `range()` erstellt wurde und die gewünschten Zahlen liefert. Oder `ex()` müsste eine Methode auf einem Objekt sein, dass den Zahlwert als Attribut hat.
Mag sein dass es falsch ist, aber immerhin hast du mir erst letztes mal geschireben dass ich das ganze mit ner Klasse machen soll, könntest du mir vllt bitte einen Entwurf schreiben, wie du das machen würdest?
Itertools ist soweit ich weiß ein externes Modul und Discord .py kommt damit nicht wirklich klar...
Diese ganze ``if``/``elif``-Kaskade ist zu viel Code. Da wird doch überall das gleiche gemacht, ausser das sich ein Zeichen in einer Zeichenkette unterscheidet. Und das ist immer der Wert des Zählers als Zeichenkette. Das wäre also ein einzige Quelltextzeile.
Wie obenn gesagt, könntest du mir vllt einen Entwurf schicken wie du das ganze lösen würdest?
Mir egal, ob der Code schön ist oder nicht.
Hauptsache er funkt!
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Phobit: `itertools` ist in der Standardbibliothek. Selbst wenn es das nicht wäre, gibt's da nichts mit dem Discord.py nicht klarkommen könnte.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Phobit
User
Beiträge: 185
Registriert: Freitag 4. Mai 2018, 18:13

Ich hab mir Itertools mal angeschaut, und bin auf diesen Code fetzen gekommen:

Code: Alles auswählen

async def ex(args, message, client, invoke):
    preferences = ["Presence1", "Presence2", "Presence3"]
    Cycler = itertools.cycle(preferences)
    print(Cycler)
    await client.change_presence(game=Game(name=Cycler))
wenn ich das change_presence Weglasse, sieht die Ausgabe z.B. so aus:
<itertools.cycle object at 0x7f9bf407fe58>
<itertools.cycle object at 0x7f9bf407f8b8>
Scheint ja ganz gut zu sein, doch sobald ich change_presence hinzufüge, folgender Error:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/phobit/PycharmProjects/DiscordSippenBot/Discord/Main.py", line 138, in on_message
    await cmd.ex(args, message, client, invoke)
  File "/home/phobit/PycharmProjects/DiscordSippenBot/Discord/commands/cmd_presence.py", line 13, in ex
    await client.change_presence(game=Game(name=Cycler))
  File "/home/phobit/PycharmProjects/DiscordSippenBot/venv/lib/python3.6/site-packages/discord/client.py", line 1934, in change_presence
    yield from self.ws.change_presence(game=game, status=status, afk=afk)
  File "/home/phobit/PycharmProjects/DiscordSippenBot/venv/lib/python3.6/site-packages/discord/gateway.py", line 475, in change_presence
    sent = utils.to_json(payload)
  File "/home/phobit/PycharmProjects/DiscordSippenBot/venv/lib/python3.6/site-packages/discord/utils.py", line 252, in to_json
    return json.dumps(obj, separators=(',', ':'), ensure_ascii=True)
  File "/usr/lib/python3.6/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/lib/python3.6/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.6/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python3.6/json/encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'cycle' is not JSON serializable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/phobit/PycharmProjects/DiscordSippenBot/venv/lib/python3.6/site-packages/discord/client.py", line 307, in _run_event
    yield from getattr(self, event)(*args, **kwargs)
  File "/home/phobit/PycharmProjects/DiscordSippenBot/Discord/Main.py", line 155, in on_message
    await cmd.ex(args, message, client, invoke)
  File "/home/phobit/PycharmProjects/DiscordSippenBot/Discord/commands/cmd_presence.py", line 13, in ex
    await client.change_presence(game=Game(name=Cycler))
  File "/home/phobit/PycharmProjects/DiscordSippenBot/venv/lib/python3.6/site-packages/discord/client.py", line 1934, in change_presence
    yield from self.ws.change_presence(game=game, status=status, afk=afk)
  File "/home/phobit/PycharmProjects/DiscordSippenBot/venv/lib/python3.6/site-packages/discord/gateway.py", line 475, in change_presence
    sent = utils.to_json(payload)
  File "/home/phobit/PycharmProjects/DiscordSippenBot/venv/lib/python3.6/site-packages/discord/utils.py", line 252, in to_json
    return json.dumps(obj, separators=(',', ':'), ensure_ascii=True)
  File "/usr/lib/python3.6/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/lib/python3.6/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.6/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python3.6/json/encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'cycle' is not JSON serializable
Und mal abgesehen von meinem schlechtem Englisch verstehe ich nicht was dort los ist... :K
Mir egal, ob der Code schön ist oder nicht.
Hauptsache er funkt!
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Phobit: Das dort beim weglassen von `change_presence()` *nicht* Presence1, Presence2, und so weiter ausgegeben wird, scheint gut zu sein? Das ist doch offensichtlich falsch, denn als `name` willst Du ja so eine Zeichenkette übergeben und kein `cycle`-Objekt. An das jeweils nächste Element aus einem Iterator kommt man mit der `next()`-Funktion. Aber das würde Dir hier so noch nichts nützen, denn auch hier ist ja das Problem das Du das `cycle`-Objekt in jedem Aufruf neu erstellst und am Ende wieder verwirfst. Das heisst ein `next()`-Aufruf würde immer das erste Element aus der Liste liefern.

Das Objekt darf nur einmal erstellt werden. Bevor `ex()` aufgerufen wird. Und müsste dann als Argument in die Funktion hinein kommen. Deswegen ja auch der Hinweis auf `functools.partial()`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Phobit
User
Beiträge: 185
Registriert: Freitag 4. Mai 2018, 18:13

functools.partial(func, *args, **keywords)
Return a new partial object which when called will behave like func called with the positional arguments args and keyword arguments keywords. If more arguments are supplied to the call, they are appended to args. If additional keyword arguments are supplied, they extend and override keywords. Roughly equivalent to:

def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
The partial() is used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature. For example, partial() can be used to create a callable that behaves like the int() function where the base argument defaults to two:

>>>
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18


Das findet man auf python.org zum Thema functools.partial, allerdings verstehe ich nicht mal ansatweise wie mir das in meinem Code weiterhilft...
Mir egal, ob der Code schön ist oder nicht.
Hauptsache er funkt!
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Du mußt, die cycle-Instanz außerhalb von `ex` erzeugen, so dass bei jedem Aufruf von `ex` die selbe Instanz benutzt werden kann:

Code: Alles auswählen

async def ex(args, message, client, invoke, names):
    await client.change_presence(game=Game(name=next(names)))

# irgendwo anders im Code, wo `ex` an irgendwas gebunden wird:
# set_ex_for_discord(ex) wird ersetzt durch
preferences = ["Presence1", "Presence2", "Presence3"]
set_ex_for_discord(partial(ex, names=itertools.cycle(preferences)))
Phobit
User
Beiträge: 185
Registriert: Freitag 4. Mai 2018, 18:13

Ok, es happert wieder am verständnis ^^ Durch was genau wird set_ex_for_discord ersetzt?
Mir egal, ob der Code schön ist oder nicht.
Hauptsache er funkt!
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Phobit: Das musst Du schon selber wissen, Du registrierst den Rückruf ja irgendwo/irgendwie.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Phobit
User
Beiträge: 185
Registriert: Freitag 4. Mai 2018, 18:13

Da liegt ja das Problem, ich verstehe das mit dem Rückruf nicht, mir gehts drum was ich da einbauen soll (ich versteh das mit dem Rückruf nicht,peinlich...)

Hast du vllt schnell ein Beispiel parat? :? :K
Mir egal, ob der Code schön ist oder nicht.
Hauptsache er funkt!
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Wie sieht denn Dein komplettes Programm aus?
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ein Rückruf (engl. Callback) ist eine Funktion, die man übergibt. Der Empfänger ruft diese Funktion später auf. Häufig werden dabei ein oder mehrere Argumente übergeben, womit die Funktion dann umgehen muss. Es ist dabei wirklich die Funktion an sich gemeint, nicht das Ergebnis eines Funktionsaufrufs. Vielleicht ist dir das schon unter dem Begriff handler begegnet oder hier eben als ex() (vermutlich für executor).
Phobit
User
Beiträge: 185
Registriert: Freitag 4. Mai 2018, 18:13

Hier der on_message Code:

Code: Alles auswählen

# hier der Befehlsregistrierung, die cmds werden weiter oben impotiert
commands = {

    "ping": cmd_ping,
    "autorole": cmd_autorole,
    "support": cmd_support,
    "help": cmd_help,
    "help2": cmd_help2,
    "clear": cmd_clear,
    "map": cmd_map,
    "info": cmd_info,
    "wer": cmd_wer,
    "sippe": cmd_sippe,
    "presence": cmd_presence

}
@client.event
async def on_message(message):
    if message.content.startswith(STATICS.PREFIX):
        invoke = message.content[len(STATICS.PREFIX):].split(" ")[0]
        args = message.content.split(" ")[1:]
        if commands.__contains__(invoke):
            cmd = commands[invoke]
            try:
                if not perms.check(message.author, cmd.perm):
                    await client.send_message(message.channel, embed=Embed(color=Color.red(), description="Fehlende Berechtigung zur Ausführung des Befehls!"))
                   
                    
            except:
                await cmd.ex(args, message, client, invoke)
                pass
            
            
        else:
            await client.send_message(message.channel, embed=Embed(color=Color.red(), description="Der Befehl **%s** wurde falsch geschreiben oder ist nicht verfügbar" % (invoke)))
Und hier dann der cmd_presence:

Code: Alles auswählen

from functools import partial
import itertools
from discord import Game
import time

perm = 2

async def ex(args, message, client, invoke, names):
    await client.change_presence(game=Game(name=next(names)))

preferences = ["Lotro", "Lotro2", "Lotro3"]
preferences(partial(ex, names=itertools.cycle(preferences)))
Und hier weiß ich eben nicht was da statt dem prefeerences am Ende hinsoll. Nochmal ex klappt nicht, da ihm dann args,message,client und invoke fehlen...
Mir egal, ob der Code schön ist oder nicht.
Hauptsache er funkt!
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Ach, die cmd_xy sind Module! Da muß man auch erst mal drauf kommen.

Einen String zwei mal zu splitten, um mal den ersten Teil mal den Rest zu verwenden, ist nicht nur Rechenzeitverschwendung sondern auch schwierig zu lesen.
Magic-Methods ruft man nicht direkt auf, hier benutzt man einfach den in-Operator.
Nackte Excepts nicht verwenden, weil das alles abfängt auch viele Programmierfehler. Welchen Fehler erwartest Du denn im try-Block? Ich kann keinen erkennen. ex sollte also besser handle_exception heißen, da es aber gar nicht den Fehler als Argument bekommt, wie soll dann eine sinnvolle Fehlerbehandlung funktionieren?
Das `pass` ist dann überflüssig.

Code: Alles auswählen

@client.event
async def on_message(message):
    invoke, *args = message.content.split(" ")
    if invoke.startswith(STATICS.PREFIX):
        try:
            try:
                cmd = commands[invoke[len(STATICS.PREFIX):]]
            except KeyError as err:
                raise RuntimeError("Der Befehl **%s** wurde falsch geschreiben oder ist nicht verfügbar" % err.args)
            else:
                try:
                    if not perms.check(message.author, cmd.perm):
                        raise RuntimeError("Fehlende Berechtigung zur Ausführung des Befehls!")
                    # was passiert hier?
                except SpecificException as err:
                    await cmd.handle_exception(args, message, client, invoke)
        except RuntimeError as err:
            await client.send_message(message.channel, embed=Embed(color=Color.red(), description=err.args[0]))
Da Du sowieso alles mit globalen Variablen machst, kannst Du das names auch noch global lassen.
Phobit
User
Beiträge: 185
Registriert: Freitag 4. Mai 2018, 18:13

Ja, hätte dazusagen sollen dass es Module sind, my bad :)

Ich möchte nicht unhöflich wirken, aber jetzt ist der Bot eben bis auf den einen Befehl eigentlich fertig und läuft flüssig, sauber und einwandfrei, also werde ich jetzt nichts mehr ändern was nicht "lebensnotwendig" ist, also wodurch der Bot nicht mehr funktionieren würde etc...

Bei dem Try-Except Block kommt ein permission system ins Spiel, werd ich jetzt aber nicht schicken da das mit dem eigentlichen Problem nichts mehr zu tun hat...
Das System versucht zu überprüfen, ob der Bentzer die benötogte "perm" Stufe hat (über eine .json geregelt) und sagt nein wenn jemand die Stufe nicht besitzt, und (except) lässt es einfach durchlaufen wenn die Perms passen. Kann man sicher schöner/effizienter regeln, aber wie gesagt, es läuft und ich möchte jetzt am "Main" nich mehr viel rumschrauben.



Blos das ganze hat mir jetzt immer noch nicht beantwortet, was in den cmd_presence an die Stelle muss:

Code: Alles auswählen

preferences(partial(ex, names=itertools.cycle(preferences)))
Weil selbst ch weiß dass da preferences komplett falsch ist ;) ^^
Mir egal, ob der Code schön ist oder nicht.
Hauptsache er funkt!
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Mit welcher Logik bedeutet eine Ausnahme, dass jemand die nötigen Berechtigungen besitzt?
Das ist weit weg von sauber und einwandfrei.

Logisch wäre zum Beispiel das:

Code: Alles auswählen

    if perms.check(message.author, cmd.perm):
        await cmd.action(args, message, client, invoke)
    else:
        raise RuntimeError("Fehlende Berechtigung zur Ausführung des Befehls!")
Phobit
User
Beiträge: 185
Registriert: Freitag 4. Mai 2018, 18:13

Aber wie jetzt auch schon mehrmals erwähnt, hat das NICHTS mit meinem eigentlichen Problem zu tun...?!
Das ganze wirft keinen fatalen Error, der Bot läuft einwandfrei und ohne Probleme wie auch oben schon erwähnt und mir geht es jetzt nur noch um das in der ersten Nachricht erwähnte Problem, dass ich auch an dieser Stelle im letzten Post nochmal erwähnt habe:
Blos das ganze hat mir jetzt immer noch nicht beantwortet, was in den cmd_presence an die Stelle muss:

preferences(partial(ex, names=itertools.cycle(preferences)))
Weil selbst ch weiß dass da preferences komplett falsch ist ;) ^^
Mir egal, ob der Code schön ist oder nicht.
Hauptsache er funkt!
Antworten