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.
Hallo,
Ich spiele gerade etwas mit Python Protocol und mypy dabei ist mir aufgefallen das meine mypy Version, diesen minimalen Code ohne Fehler akzeptiert.
import collections
import typing
class Command(typing.Protocol):
def execute(self):
...
class CommandBatch(collections.UserList[Command]):
def execute(self):
for command in self:
command.execute()
class Duck:
def execute(self):
print("duck quark")
class Swan:
def execute(self):
print("Swan QUARK")
def main():
batch = CommandBatch[Duck]()
batch.append(Duck())
batch.append(Swan())
batch.execute()
if __name__ == '__main__':
main()
Meine Erwartung wäre aber gewesen, dass mypy bei batch.append(Swan()) eine Warnung oder einen Fehler generiert, da Swan zwar das Command Protocol erfüllt, aber die CommandBatch an Duck gebunden wurde. Was ist mein Denkfehler?
Ich kenne mich mit mypy wenig aus, aber aus meiner Sicht ist CommandBatch doch schon vollstaendig typisiert fuer deinen Anwendungsfall hier. CommandBatch braeuchte fuer mein Verstaendnis eine Typvariable, aber die sehe ich nicht. Das [Duck] wuerde doch wenn gar nichts bedeuten, denn Duck ist ja nix (in Bezug auf einen Typ). Es implementiert implizit Command, aber das reicht doch nicht fuer ein Typkalkuel.
__deets__ hat geschrieben: ↑Mittwoch 25. Januar 2023, 18:33
Ich kenne mich mit mypy wenig aus, aber aus meiner Sicht ist CommandBatch doch schon vollstaendig typisiert fuer deinen Anwendungsfall hier. CommandBatch braeuchte fuer mein Verstaendnis eine Typvariable.
Kling erstmal logisch, hast du eine Idee wie ich denn Code ändern kann, damit es für Typkalkuel, reicht und mypy hier eine Warnung/Fehlerausgibt.
@imonbln: mypy prüft nix in `main()` solange Du da keine Typsignatur anbringst. Also den Rückgabetyp von `main()` mit `` -> None`` annotieren und schon meldet mypy auch einen Fehler bei ``CommandBatch[Duck]``:
__blackjack__ hat geschrieben: ↑Mittwoch 25. Januar 2023, 18:47
@imonbln: mypy prüft nix in `main()` solange Du da keine Typsignatur anbringst. Also den Rückgabetyp von `main()` mit `` -> None`` annotieren und schon meldet mypy auch einen Fehler bei ``CommandBatch[Duck]``:
Guter Hinweis ist aber nur die halbe Wahrheit, selbst mit der annotieren Signatur bekomme ich noch nicht, was ich mir eigentlich wünsche.
import collections
import typing
T= typing.TypeVar("T", covariant=True)
class Command(typing.Protocol[T]):
def execute(self):
...
class CommandBatch(collections.UserList[Command[T]]):
def execute(self):
for command in self:
command.execute()
class Duck:
def execute(self):
print("duck quark")
class Swan:
def execute(self):
print("Swan QUARK")
def main() -> None:
batch = CommandBatch[Duck]()
batch.append(Duck())
batch.append(Swan())
batch.execute()
if __name__ == '__main__':
main()
Wenn ich das richtig verstehen müsste hier eigentlich covariant=False stehen, aber dann gibt es einen anderen Fehler, als erwartet. Irgendwie fehlt mir noch ein Stück Erkenntnis.
Na es scheint, als ob collections.UserList eben nur covariante Typen akzeptiert. Wieso das so ist kann ich leider auf die Schnelle nicht eruieren, wie auch immer mypy das ableitet.
Mach mal hilft es das Problem ein wenig ruhen zu lassen, was ich wollte, geht doch. Man muss nur die TypeVar T an das Protokoll binden, dann kann mypy erkennen das Swan keine Duck ist. So erkennt mypy denn Fehler.
Ich habe da nochmal drüber nachgedacht, und was du hier machst bedeutet doch, einfach nur zu sagen „es muss Duck sein“. Warum also der Umweg über ein Protokoll, das ja genau eine solche spezifische Typisierung vermeidet? Dann braucht’s doch das Protokoll garnicht.
Stell dir einfach vor, es gibt noch eine zweite CommandBatch[Swan] und beide platzieren Bestellungen in Handelssysteme. Also ich finde es dann schon sinnvoll, wenn die CommandBatch generisch ist, aber der Statische Codeanalyse erkennt kann, dass ich gerade eine Duck in den Swan Handelsplatz senden will.
Den Teil verstehe ich. Nicht den, warum das über eine Protokoll läuft. Du sagst „hier ist eine Collection of Command“, aber dann soll batch eine Collection of Ducks sein. Mach es doch gleich eine Collection of Ducks?
Aber dann bräuchte ich auch noch eine Collection of Swan, welche exakt den gleichen Code hat wie die Collection of Duck. Daher denke ich, dass es, als eine Art Template hier sinnvoll ist, das Protokoll zu verwenden.
Aber die muss doch keinem Protocol gehorchen, oder vertue ich mich da? Zumindest in C++ würde das gehen, da prüft der Compiler dann,w as der konkrete Typ kann. Aber es kann sein, das genau da mein Missverständnis liegt.