Suche Alternative zu ThreadPoolExecutor um Threads bzw. Prozesse zu beenden

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
kasi45
User
Beiträge: 7
Registriert: Montag 11. August 2025, 18:44

Hallo,

zu meinem Python Problem:

Innerhalb einer Funktion starte ich mit ThreadPoolExecutor (concurrent.futures) mehrere Threads.
Die Funktion 8main) läuft weiter und es wird eine Schleife mit timeout startet.
In dieser Schleife wird u.a. abgefragt welche Threads zu dem Zeitpunkt noch nicht beendet sind.

Nach dem Ende der Schleife möchte ich alle Threads, die ihre Aufgabe bis dahin nicht geschafft haben, beendet (kill oder so)
Mit "ThreadPoolExecutor " könnte man zu diesem Zeitpunkt nur noch nicht begonnene Threads beenden, keine noch laufende.

Auch mit "multiprocessing" und 'threading' konnte ich dass gewünschte nicht umsetzen.

Im Voraus vielen Dank für Anregungen und Hinweise.
Gruß kasi45

Code: Alles auswählen

## info: sourceDict = self.sourceDict
## info: from concurrent.futures import ThreadPoolExecutor
## info: self.executor = ThreadPoolExecutor(max_workers=10)

futures = {self.executor.submit(self._getSource, titles, year, season, episode, imdb, provider[0], provider[1]): provider[0] for provider in sourceDict}

# der main thread laüft weiter
# weiterer code        
timeout = 30
## hier beginnt eine Schleife  mit "timeout"
for i in range(0, 4 * timeout):
    if control.abortRequested: return sys.exit()
    try:
        if progressDialog.iscanceled():
            #TODO
            self.executor.shutdown(wait=False) # nur noch nicht begonnene threads kann man so beenden
            break
    except Exception as e:
        pass
  
    # weiterer code
    # abfrage info zu allen noch nicht beendeten Threads
    
    info = [name.upper() for future, name in futures.items() if not future.done()]
    
    # weiterer code
    
    # Ende Timeout Schleife
# hier sollten alle "Threads" beendet werden die in der vorgegebenen Zeit ihre Aufgabe nicht beenden konnten

return self.sources

Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kasi45: Mit Threads geht das auch nicht wenn Du das nicht selber im Thread vorsiehst, das der beispielsweise regelmässig prüft ob er sich beenden soll.

Warum hast Du das mit `multiprocessing` nicht hinbekommen? `Process`-Objekte haben eine `terminate()`-Methode.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
kasi45
User
Beiträge: 7
Registriert: Montag 11. August 2025, 18:44

Hallo @__blackjack__ ,
`Process`-Objekte haben eine `terminate()`-Methode.
Genau dies war der Grund es mal mit "multiprocessing" zu versuchen.

hier die Änderungen im Sourcecode bei meinem Versuch mit "multiprocessing" zum Ziel zu kommen:

Code: Alles auswählen

    #futures = {self.executor.submit(self._getSource, titles, year, season, episode, imdb, provider[0], provider[1]): provider[0] for provider in sourceDict}
    threads = []
    for i in sourceDict:
        threads.append(multiprocessing.Process(target=self._getSource(titles, year, season, episode, imdb,  i[0], i[1])))
    [i.start() for i in threads]

    import pydevd
    pydevd.settrace('10.128.5.144', port=12345, stdoutToServer=True, stderrToServer=True)

    s = [i[0] + (i[1],) for i in zip(sourceDict, threads)]
    s = [(i[3].getName(), i[0], i[2]) for i in s]
    sourcelabelDict = dict([(i[0], i[1].upper()) for i in s])
Nach dem Start - [i.start() for i in threads] - rödeln die Prozesse aber mein "main" läuft nicht weiter. Erst nach einer Weile, wenn wohl alle Prozesse schon beendet sind. Ich komme so gar nicht zu der Zeitschleife zum überprüfen welche Prozesse schon beenden sind.

Das kann ich mir nicht erklären.

Zu meinen Kenntnissen bzgl. Python, ich bin Rentner und habe mir alles zum programmieren in den letzten Jahren selbst beigebracht. Ich wollte einfach Kodi Addons selber für mich schreiben.

Gruß kasi45
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kasi45: Noch Anmerkungen zu den Quelltextfragmenten im ersten Beitrag:

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Grunddatentypen haben nichts in Namen verloren. Den Typen ändert man gar nicht so selten mal während der Programmentwicklung und dann muss man überall im Programm die betroffenen Namen ändern, oder man hat falsche, irreführende Namen im Quelltext.

Bei Wörterbüchern möchte man in der Regel auch wissen was Schlüssel und Werte bedeuten, also nicht statt `source_dict` einfach `self.sources` sondern ein Namen nach dem Muster <Schlüssel>_to_<Wert>, wobei man das hier alles raten muss ohne den anderen Code zu kennen, was ein schönes Beispiel ist warum das Sinn macht das sinnvoller zu benennen. Über den Schlüssel wissen wir das es ein Tupel ist, und über den Wert jeweiligen Wert kann man nur spekulieren.

Über den Schlüssel weiss der Leser an der Stelle auch deswegen so wenig, weil auch diese Werte nicht an sinnvolle Namen gebunden werden, sondern auf das Tupel über magische Indexwerte zugegriffen wird, die nichts über die Bedeutung aussagen. Bei dem einen kann man noch erraten, dass es ein Name ist, weil der Wert in späteren Code dann doch mal an den Namen `name` gebunden wird.

`futures` sollte dann auch eher `future_to_name` heissen. Wobei fraglich ist, ob das überhaupt ein Wörterbuch sein sollte. Wird denn da jemals über den Schlüssel auf einen Wert zugegriffen? Oder würde es reichen wenn das eine Liste mit Paaren wäre? Also:

Code: Alles auswählen

        futures = [
            (
                self.executor.submit(
                    self._get_source,
                    titles,
                    year,
                    season,
                    episode,
                    imdb,
                    provider_name,
                    some_value,
                ),
                provider_name,
            )
            for provider_name, some_value in source_dict
        ]
Der Code der `info` definiert, würde dann so aussehen:

Code: Alles auswählen

            info = [
                name.upper() for future, name in futures if not future.done()
            ]
Zu der „Timeout-Schleife“: Falls der Wert von `i` innerhalb der Schleife nirgends verwendet wird, ist die übliche Namenskonvention für (gewollt) nicht verwendete Namen einfach `_` zu verwenden. Das weiss der Leser (und statische Code-Analyse-Werkzeuge), dass dieser Name nicht verwendet wird, und das kein Fehler ist.

Blöcke werden normalerweise immer in eine eigene Zeile und eingerückt gesetzt, auch wenn sie nur eine Zeile lang sind.

Die Kombination ``return sys.exit()`` habe ich noch nicht gesehen. Das ``return`` macht keinen Sinn, weil der `sys.exit()`-Aufruf nicht nur keinen sinnvollen Rückgabewert hat, sondern auch gar nicht zurückkehrt. Das riecht auch komisch diesen Aufruf hier zu haben. So etwas gehört wenn dann direkt in die Hauptfunktion oder sehr nahe dran und man benutzt die Funktion auch nur wenn man wenigstens potentiell einen anderen Rückgabecode als 0 an den Aufrufer zurückgeben möchte. Das ist keine Funktion um sich um einen sauberen Programmablauf zu drücken.

Das ``try``/``except`` ist zu weit gefasst, sowohl was den betroffenen Code betrifft, als auch die Ausnahme. Welche Ausnahme kann bei dem `iscanceled()` auftreten? Und welcher bei dem `shutdown()`? Und warum ist das okay *jede* Ausnahme hier einfach lautlos zu ignorieren? Ich würde sagen diese ”Ausnahmebehandlung” gehört da komplett weg.

Entweder setzt man ein Ergebnis als öffentliches Attribut, oder man gibt es per ``return`` zurück. Beides macht keinen Sinn.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kasi45: Einbuchstabige Namen gehen bis auf ein paar Ausnahmen gar nicht. Insbesondere geht `i` nicht wenn das nicht für eine ganze Zahl steht. Was üblich ist, sind `i`, `j`, `k` für ganze Zahlen die als Zählvariablen in Schleifen und als Index verwendet werden, und `x`, `y`, und `z` für Koordinaten. So ziemlich alles andere sollte man ordentlich und sinnvoll benennen. So das man beim lesen des Namens weiss, was die Werte im Programm bedeuten.

Du begehst den (nicht unüblichen) Fehler das Du nicht die Funktion und die Argumente an `Process` übergibst, sondern die Funktion Aufrufst und dann den Rückgabewert von der Funktion an `Process` anstelle der Funktion übergibst. Dann ist die Funktion aber schon durchgelaufen, denn das muss sie ja wenn man das Ergebnis übergibt. Und `Process` kann auch mit dem Ergebnis ziemlich sicher nichts anfangen, denn das ist ja nicht aufrufbar (vermute ich). An `target` wird das Aufrufbare Objekt übergeben. Und an `args` die Argument für den Aufruf. Denn `Process` muss das ja in einem eigenen Prozess aufrufen, nicht Du *vorher*.

`Process`-Objekte sollte man vielleicht auch nicht in eine Liste stecken die `threads` heisst.

Diese Liste wäre ein Kandidat für eine „list comprehension“ statt der ``for``-Schleife. Dagegen sollte das Starten der Prozesse in eine Schleife, denn „list comprehension“ sind zum erstellen von Listen, nicht um ``for``-Schleife in einem Ausdruck schreiben zu können wo man dann mit der entstandenen Liste überhaupt gar nichts macht.

Code: Alles auswählen

        processes = [
            multiprocessing.Process(
                target=self._get_source,
                args=[titles, year, season, episode, imdb, name, some_value],
            )
            for name, some_value in source_dict
        ]
        for process in processes:
            process.start()
Die letzten drei Zeilen sind komplett irre. Das versteht doch kein Mensch was da passiert. Ich hab's versucht, aber anscheinend hat `sourceDict` Tupel mit mehr als zwei Werten als Schlüssel‽ Und welches Objekt hat da eine `getName()`-Methode? `Process`-Objekte haben die nicht. Und falls das noch von `Thread` sein sollte: die Methode ist veraltet und sollte nicht mehr verwendet werden. Beide Typen haben ein `name`-Attribut.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
kasi45
User
Beiträge: 7
Registriert: Montag 11. August 2025, 18:44

Hallo __blackjack__,

das war aber mal eine menge an Input für mich. :)

So einige Teile von diesem Code sind schon uralt und kommen von einem Kodi Addon dass es schon lange nicht mehr gibt.
Du hast in allem recht - und weil so viel Chaos im Code war und auch noch ist wollte ich die damaligen Entwickler dazu bewegen den Kern vom Addon zu überarbeiten um ihn stabiler und es besser zu machen. Weil niemand daran Interesse hatte fing ich an Python zu lernen. Ich hab mich dann erst mal um die Dinge gekümmert die überhaupt nicht funzten und mich dann stückchenweise durch alles hindurch gehangelt.
Und jetzt bin ich bei der Funktion getSources() gelandet und will die verändern.

Jetzt lese und teste ich erst mal und melde mich dann wieder.

Vielen Dank!
kasi45
User
Beiträge: 7
Registriert: Montag 11. August 2025, 18:44

Hallo,

zum besseren Verständnis woher "sourceDict" kommt. Im Verzeichnis "sourceSubFolders" liegen PythonFiles alle mit einer Klasse 'source' in der sich die Funktion run() befindet. Alle Funktionen run() sollen über multiprocessing verarbeitet werden.

Code: Alles auswählen

   for i in sourceSubFolders:
        for loader, module_name, is_pkg in pkgutil.walk_packages([os.path.join(sourceFolderLocation, i)]):
            if is_pkg:
                continue
            if enabledCheck(module_name):
                try:
                    spec = importlib.util.spec_from_file_location(
                        module_name,
                        os.path.join(sourceFolderLocation, i, module_name + ".py")
                    )
                    if spec:
                        module = importlib.util.module_from_spec(spec)
                        spec.loader.exec_module(module)
                        sourceDict.append((module_name, module.source()))
                except Exception as e:
                    __addon__.setSetting('provider.' + module_name, 'false')
                    if debug:
                        log_utils.log('Error: Loading module: "%s": %s' % (module_name, e), log_utils.LOGERROR)
                    pass
    return sourceDict

Das ist die Funktion die mit process.start() ausgeführt werden soll

Code: Alles auswählen

    def _getSource(self, titles, year, season, episode, imdb, source, call):
        try:
            sources = call.run(titles, year, season, episode, imdb)
            if sources == None or sources == []: raise Exception()
            sources = [json.loads(t) for t in set(json.dumps(d, sort_keys=True) for d in sources)]
            for i in sources:
                i.update({'provider': source})
                if not 'priority' in i: i.update({'priority': 100})
                if not 'prioHoster' in i: i.update({'prioHoster': 100})
            self.sources.extend(sources)
        except Exception as e:
            print(e)
            pass
Und so mach ich dies im Moment:

Code: Alles auswählen

   processes = [
        multiprocessing.Process(
            target=self._getSource,
            args=[titles, year, season, episode, imdb, module_name, module_source],
        )
        for module_name, module_source in self.sourceDict
    ]
    for process in processes:
        try:
            process.start()
        except Exception as e:
            print(e)
Ich hab damit leider keinen Erfolg.
Ein Sprung in die Funktion "_getSource" erfolgt nicht.

Immer Exception "Can't pickle <class 'einschalten.source'>: import of module 'einschalten' failed" ('einschalten' an erster Stelle im sourceDict) zweiter Stelle wäre dann 'filmpalsat' gewesen - soweit kommt es nicht

Ich las " import of module 'einschalten' failed" und habe in meinem "jugendlichen" Wahn einfach ein path.append zum Verzeichnis gemacht.
Natürlich hat sich die Situation nicht verbessert - nur eine andere Fehlermeldung
"Can't pickle <class 'einschalten.source'>: it's not the same object as einschalten.source"

Was da los ist, das kann ich mit meinem Wissen nicht ergründen.

mit concurrent.futures ThreadPoolExecutor und auch mit threading.Thread lief das ganze ja - aber eben als Threads - die man ja nicht von außen beenden kann.

Können die Profis mir verraten was hier los ist?

Vielen Dank & Gruß
kasi45
kasi45
User
Beiträge: 7
Registriert: Montag 11. August 2025, 18:44

"Senile Bettflucht" - PC einschalten und testen :lol:

Zur besseren Übersicht nur mit zwei processes getestet und ein paar print hinzugefügt

Code: Alles auswählen

    for process in processes:
        print('process: '+ str(process))
        print('process name: '+ str(process.name))
        print('process args: '+ str(process._args))
        try:
            process.start()
        except Exception as e:
            print(e)
Und das kam dabei raus:

process: <Process name='Process-1' parent=8684 initial>
process name: Process-1
process args: (['wilhelm tell', 'william tell'], 2025, 0, 0, 'tt22478818', 'filmpalast', <filmpalast.source object at 0x000001CA09C38670>)
Can't pickle <class 'filmpalast.source'>: import of module 'filmpalast' failed :?:

process: <Process name='Process-2' parent=8684 initial>
process name: Process-2
process args: (['wilhelm tell', 'william tell'], 2025, 0, 0, 'tt22478818', 'filmpro', <filmpro.source object at 0x000001CA09D7CBB0>)
Can't pickle <class 'filmpalast.source'>: import of module 'filmpalast' failed :?:

In meinem Kopf sammeln sich langsam die vielen :?: und ich habe keinen Plan wie man die schnell wieder los wird :?
kasi45
User
Beiträge: 7
Registriert: Montag 11. August 2025, 18:44

Hallo,
ein kleiner Schritt näher am Ziel.
Can't pickle <class 'filmpalast.source'>: import of module 'filmpalast' failed
Diese Exception gibt es nun nicht mehr!

Zwei Beiträge weiter oben, im ersten Codeblock habe ich eine Zeile ergänzt:

Code: Alles auswählen

        if spec:
            module = importlib.util.module_from_spec(spec)
            sys.modules[module_name] = module  			# Add
            spec.loader.exec_module(module)
            sourceDict.append((module_name, module.source()))
Dafür gibt es jetzt neue Exception:

cannot pickle '_queue.SimpleQueue' object :shock:

Was bedeutet dies nun wieder?
Sirius3
User
Beiträge: 18276
Registriert: Sonntag 21. Oktober 2012, 17:20

In den Funktionen gibt es ja noch viel zu viele überflüssige Zeichen:

Code: Alles auswählen

    def _getSource(self, titles, year, season, episode, imdb, source, call):
        try:
            k = call.run(titles, year, season, episode, imdb)
            if k in (None, []): raise Exception()
            k = [json.loads(t) for t in set(json.dumps(d, sort_keys=True) for d in k)]
            for i in k:
                i.update({'provider': source})
                if not 'priority' in i: i.update({'priority': 100})
                if not 'prioHoster' in i: i.update({'prioHoster': 100})
            self.sources.extend(k)
        except Exception as e:
            print(e)
Eine Funktion, die getSource heißt, als Argument schon eine `source` erhält und dann nichts zurückgibt, oh je.
Somit haben wir schon drei unterschiedliche Dinge, die alle source heißen:
1. source ist der Modulname
2. source ist eine Methode im Modul, dessen Rückgabewert hier `call` heißt und anscheinend eine run-Methode hat.
3. source ist der Rückgabewert dieser Run-Methode und anscheinend ein Wörterbuch

Es ist also keine gute Idee, irgend etwas davon source zu nennen. Dass Klassen bei multiprocess bekannt sein müssen, und nicht auf kryptischem Wege geladen werden sollten, hast Du ja jetzt schon gemerkt. Dass es keine gute Idee ist, die gesamte Instanz `self` via multiprocessing zu übergeben, merkst Du auch schon.
Dann ist es nicht gut, wenn in einem Thread (oder Subprocess) gemeinsam genutzte Datenstrukturen einfach so verändert werden, wie `self.sources`. Funktionen die man dafür benutzt müssen noch stärker keine Seiteneffekte haben, als es eh schon der Fall sein sollte.

update ist nicht dazu da einzelne Keys eines Wörterbuchs zu setzen.
Die ganze Funktion sollte also so aussehen:

Code: Alles auswählen

def load_movie_information(titles, year, season, episode, imdb, provider_name, provider):
    entries = provider.run(titles, year, season, episode, imdb)
    seen = set()
    for entry in entries:
        key = json.dumps(entry, sort_keys=True)
        if key in seen:
            # duplicate entry
            continue
        seen.add(key)
        entry['provider'] = provider_name
        entry.set_default("priority", 100)
        entry.set_default("prioHoster", 100)
    return entries
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kasi45: Ah, da gibt es jetzt einiges an Informationen mehrfach, weil Sirius3 schneller war mit posten. :-)

Wie immer Anmerkungen zum Code: Das `i` kein guter Name für etwas anderes als ganze Zahlen ist, habe ich ja schon mal erwähnt.

`os.path.join()` & Co sind mittlerweile vom `pathlib`-Modul abgelöst. Die Funktion wird auch zweimal verwendet um beim zweiten Mal ein Teilergebnis zu bekommen, was man beim ersten mal schon hatte.

``continue`` versuche ich so weit möglich zu vermeiden, und das ist eigentlich so gut wie immer möglich. Das ist ein Sprung den man der Einrückung nicht ansieht, und es macht es schwieriger Code aus Schleifen in Funktionen/Methoden heraus zu ziehen, oder etwas am Ende jedes Schleifendurchlaufs hinzuzufügen.

In diesem Fall führt das nicht mal zu einer tieferen Einrückung, weil man die geschachtelte ``if``-Bedingung einfach mit in das umschliessende ``if`` aufnehmen kann.

Und nochmal ein Fall von „Grunddatentypen gehören nicht in Namen“ — schön anschaulich warum das keine gute Idee ist: ``sourceDict.append()`` ist falsch. Wörterbücher haben keine `append()`-Methode, das scheint eine Liste zu sein.

``pass`` sollte nirgends stehen wo es keinen Zweck erfüllt.

Das könnte dann so aussehen (ungetestet):

Code: Alles auswählen

    sources = []
    for subfolder_name in source_subfolder_names:
        subfolder_path = source_folder_path / subfolder_name
        for module_info in pkgutil.walk_packages([str(subfolder_path)]):
            if not module_info.ispkg and enabled_check(module_info.name):
                try:
                    spec = importlib.util.spec_from_file_location(
                        module_info.name,
                        subfolder_path / f"{module_info.name}.py",
                    )
                    if spec:
                        module = importlib.util.module_from_spec(spec)
                        spec.loader.exec_module(module)
                        sources.append((module_info.name, module.source()))
                except Exception as error:
                    __addon__.setSetting(
                        f"provider.{module_info.name}", "false"
                    )
                    if debug:
                        log_utils.log(
                            f"Error: Loading module: {module_info.name!r}: {error}",
                            log_utils.LOGERROR,
                        )

    return sources
Bei `_getSource()` sehe ich wieder Probleme mit Namen. Wir hatten eben schon `sources` und Module mit einer `source`-Funktion. Dann die Funktion `_getSource()` die ein Argument `source` bekommt und Obwohl sie nur `_getSource()` — Einzahl — heisst, eine Liste um mehrere Sourcen erweitert. Wobei `source`/`sourcen` hier in mehreren Funktionen/Methoden für sehr unterschiedliche Typen/Datenstrukturen steht. Das ist verwirrend. Endweder sollte das nicht alles etwas mit `source`/`sources` heissen, oder man braucht noch zusätzliche Info im Namen was für eine Quelle das jetzt jeweils ist.

Eine Funktion oder Methode mit `get*` im Namen muss etwas zurückgeben. Das ist eine ziemlich feste Erwartung die man als Programmierer an so einen Namen hat. Und die `_getSource()` sollte das auch tatsächlich machen. Mit dem Ergebnis eine Liste zu erweitern kann der Aufrufer machen. Und dann kann das auch zu einer Funktion werden, denn das Objekt wird dann nicht mehr benötigt.

Das Auslösen der Ausnahme ist ein bisschen sinnlos wenn die gleich in der Funktion wieder mit einem `print()` behandelt wird. Da könnte man dort auch einfach ein `print()` reinschreiben. Oder falls das nicht wirklich ein Ereignis ist, das man so aufbauschen muss, dann ist die gesamte Abfrage unnötig.

Es gibt wieder schlechte namen mit `d` und `t`. Bei `d` kann ich noch erraten wofür das steht, bei `t` wird das schon schwieriger.

Die `dict.update()` ist dazu da wenn man ein Wörterbuch mit einem anderen oder einem iterierbaren Objekt mit Schlüssel-/Wert-Paaren erweitern/aktualisieren möchte. Nicht um *einem* Schlüssel einen Wert zuzuordnen. Dafür erstellt man kein unnötiges Wörterbuch mit diesem einem Paar, was danach dann ja gleich wieder verworfen wird.

Die Funktion könnte dann so aussehen:

Code: Alles auswählen

def _get_sources(titles, year, season, episode, imdb, provider_name, provider):
    sources = provider.run(titles, year, season, episode, imdb)
    if sources is not None:
        for source in map(
            json.loads,
            {json.dumps(source, sort_keys=True) for source in sources},
        ):
            source["provider"] = provider_name
            source.setdefault("priority", 100)
            source.setdefault("prioHoster", 100)

    return sources
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Nachtrag: Upsie, ich sehe gerade das ich einen Fehler gemacht habe, der auch bei dem Code von Sirius3 ist: Die Doubletten werden gar nicht raus gefiltert.

Und wenn man sich das merken von Sachen, die man schon mal gesehen hat, nicht selber schreiben möchte, dann hat das externe `more_itertools`-Modul etwas passendes (ungetestet):

Code: Alles auswählen

from functools import partial

from more_itertools import unique_everseen

# ...

def _get_sources(titles, year, season, episode, imdb, provider_name, provider):
    sources = provider.run(titles, year, season, episode, imdb)

    if sources is not None:
        sources = list(
            unique_everseen(sources, partial(json.dumps, sort_keys=True))
        )
        for source in sources:
            source["provider"] = provider_name
            source.setdefault("priority", 100)
            source.setdefault("prioHoster", 100)

    return sources
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
Dennis89
User
Beiträge: 1560
Registriert: Freitag 11. Dezember 2020, 15:13

@Sirius3 gibt es einen Grund für `continue` in diesem Fall? Ich hätte das, nach euren Lehrbeiträgen, so geschrieben:

Code: Alles auswählen

def load_movie_information(titles, year, season, episode, imdb, provider_name, provider):
    entries = provider.run(titles, year, season, episode, imdb)
    seen = set()
    for entry in entries:
        key = json.dumps(entry, sort_keys=True)
        if key not in seen:
            seen.add(key)
            entry['provider'] = provider_name
            entry.set_default("priority", 100)
            entry.set_default("prioHoster", 100)
    return entries
@__blackjack__ Ich weiß nicht ob ich dir ganz folgen kann, werden die Doubletten in Sirius3's Code nicht mit dem `set` rausgefiltert?

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Nee, Doubletten werden nicht bearbeitet, aber sie bleiben ja trotzdem unbearbeitet in `entries`. Da müssten sie aber raus. Den Fehler habe ich im ersten Anlauf ja auch gemacht.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
Dennis89
User
Beiträge: 1560
Registriert: Freitag 11. Dezember 2020, 15:13

Ah ich verstehe und habe erst jetzt in deinem ersten Code gesehen, dass der ja noch ähnlicher zu Sirius3 Code ist, als ich davor gedacht habe. Habe erst gerade dein `set` entdeckt.

Danke!
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
snafu
User
Beiträge: 6873
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Da Sets die ursprüngliche Reihenfolge der Elemente nicht zwingend einhalten, Dicts aber schon, könnte man es auch so schreiben:

Code: Alles auswählen

for entry in dict.fromkeys(entries):
    ...
Wäre weniger Code und vermutlich auch marginal schneller.

``for entry in set(entries)`` ginge auch, macht aber potenziell die Reihenfolge kaputt. Will man die erhalten, sollte man die Finger davon lassen. Oder eben das "seen.add"-Konstrukt meiner Vorposter verwenden. Wobei ein ``list(seen)`` am Ende eben auch wieder die Reihenfolge kaputt machen kann. Ich würde wie gesagt einfach das Dict "missbrauchen", wenn ich dafür kein externes Modul voraussetzen möchte.
Benutzeravatar
__blackjack__
User
Beiträge: 14069
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@snafu: Da müsste aber in den `...` in der Schleife dann ein ``result.append(entry)`` und am Ende dann `result` zurückgeben und nicht `entries`, denn die Doubletten müssen ja tatsächlich raus aus den Daten. Oder halt so:

Code: Alles auswählen

entries = list(dict.fromkeys(entries))
for entry in entries:
    ...
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Sirius3
User
Beiträge: 18276
Registriert: Sonntag 21. Oktober 2012, 17:20

Ja, so schrecklicher Code verwirrt mich so sehr, dass ich nicht mehr klar denken kann. Eine der wichtigsten Grundsätze: "ändere niemals eine Liste, sondern erzeuge immer eine neue" gilt auch hier.
Also frisch auf:

Code: Alles auswählen

def load_movie_information(titles, year, season, episode, imdb, provider_name, provider):
    entries = provider.run(titles, year, season, episode, imdb)
    unique_entries = unique_everseen(entries, partial(json.dumps, sort_keys=True))
    defaults = {
        "provider": provider_name,
        "priority": 100,
        "prioHoster": 100,
    }
    return [
        defaults | entry
        for entry in unique_entries
    ]
kasi45
User
Beiträge: 7
Registriert: Montag 11. August 2025, 18:44

Hallo,

und wieder ging es einen kleinen Schritt weiter
Connected to pydev debugger (build 173.4127.16)
process gestartet: Process-1
process gestartet: Process-2
Aber zu früh gefreut - die run funktion (filmpalst, filmpro wird nicht ausgeführt) :?

Gruß kasi45
Antworten