Schleife über alle Instanzen/Objekte einer Klasse

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
Holger Chapman
User
Beiträge: 35
Registriert: Samstag 12. Juli 2014, 01:59

Hallo,

ich möchte in einem Programm eine "for"-Schleife über alle Instanzen/Objekte einer Klasse laufen lassen. Ich brauche also eine Liste aller Instanzen/Objekte der Klasse X. - Klingt ziemlich einfach, aber ich habe leider nicht herausgefunden, wie das geht. - Könnt ihr mir helfen?

Herzlichen Dank!


Holger
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Damit bewegst du dich auf exotischem Terrain. Man könnte das über ein Klassenattribut (zB ein Set) und mit der __new__()-Methode lösen, wo man mittels cls die neue Instanz hinzufügt und in __del__() die abzuräumenden Instanzen löscht. Das ist aber mehr oder weniger schwarze Magie und nur zu empfehlen, wenn man weiß, was man tut. Fast immer gibt es bessere Alternativen, die man dem Verfahren vorziehen sollte.
Sirius3
User
Beiträge: 18265
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Antwort ist, packe alle Instanzen, die Du betrachten möchtest, in eine Liste. Dann kannst Du einfach darauf zugreifen.
Kannst Du näher beschreiben, warum Du denkst, dass Du alle Instanzen einer Klasse auf magische Weise brauchst?
Benutzeravatar
__blackjack__
User
Beiträge: 14030
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

An der Stelle bitte ganz dringend `__del__()` wieder vergessen. snafu Du Schuft, wie kannst Du nur!!!1!

Wenn man so etwas machen wollen würde, dann bitte einen Blick ins `weakref`-Modul werfen um Referenzen zu erstellen die nicht ”gelten” wenn sie die einzige Referenz sind, also so zwar den Zugriff ermöglichen wenn das Objekt irgendwo anders noch referenziert werden kann, aber nicht das Löschen/Abräumen verhindern, falls es keine ”starken” Referenzen mehr gibt.

Ist aber Voodoo/schwarze Magie, und mich würde auch erst mal interessieren wozu das eigentlich gebraucht wird.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Falls man wirklich meinen Ansatz nutzen möchte, ist das weakref-Modul tatsächlich dem __del__() vorzuziehen. Mit einem WeakSet wird automatisch das Abräumen von nicht mehr referenzierten Objekten übernommen.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier mal etwas Code dazu:

Code: Alles auswählen

#!/usr/bin/env python3
from weakref import WeakSet

class RefHolder:
    _refs = WeakSet()

    def __new__(cls, *args, **kwargs):
        instance = object.__new__(cls)
        cls._refs.add(instance)
        return instance

    @classmethod
    def get_references(cls):
        yield from cls._refs


class Person(RefHolder):
    def __init__(self, name, alter, geschlecht):
        self.name = name
        self.alter = alter
        self.geschlecht = geschlecht


def create_persons():
    return [
        Person("Bob", 42, "m"),
        Person("Clara", 18, "w"),
        Person("Yoki", 22, "d")
    ]

def main():
    persons = create_persons()
    for obj in Person.get_references():
        print(obj.name)

if __name__ == "__main__":
    main()
Hier ist das natürlich unsinnig, weil die Personen ja bereits in der zuvor erzeugten Liste drinstehen. Jedenfalls kann das get_references() natürlich auch von jeder anderen Stelle im Code abgefragt werden. Ob man das wirklich in dieser Form braucht, kommt auf den Anwendungsfall an. Ganz trivial ist die Nutzung von __new__() und Klassenattributen halt nicht.
Sirius3
User
Beiträge: 18265
Registriert: Sonntag 21. Oktober 2012, 17:20

@Holger Chapman: safu zeigt zwar, was technisch möglich ist, aber so ein Konstrukt möchte man nicht in einem sauberen Programm haben, das ist zu magisch. Magie bedeutet, dass etwas im Hintergrund verborgen passiert, was man am schwer nachvollziehen kann, und deshalb zu Fehlern führen kann, die man nicht einfach versteht.

Was ist Dein eigentliches Problem, das Du mit Deiner "Lösungsidee" versuchst zu lösen?
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das mit der Magie ist so ein bisschen Ansichtssache. Zu f-Strings gab es hier auch Stimmen, die es zu magisch fanden, dass im String "irgendwie" Bezug auf Objekte aus dem Namensraum genommen wurde. Oder dataclasses: Da wird auch basierend auf Klassenattributen magisch alles mögliche an Methoden hinzugefügt. Oder was SQLAlchemy so treibt, wirkt anfangs auch wie Magie und spielt sogar im Namen noch darauf an. Trotzdem sind diese Tools allesamt sehr praktisch und gar nicht mehr sooo magisch, wenn man mal dahinter geblickt hat.

Klar kann es hier bei der Fragestellung auch ein klassischer Fall von "Wie nummeriere ich Variablen" sein, wo das Problem bloß ist, dass jemand noch keine Listen oder andere geeignete Datenstrukturen kennt und somit erstmal an Nummerierung denkt. Da müsste sich der OP halt mal näher zu äußern...
Holger Chapman
User
Beiträge: 35
Registriert: Samstag 12. Juli 2014, 01:59

Sirius3 hat geschrieben: Dienstag 4. Januar 2022, 07:41 [...]
Was ist Dein eigentliches Problem, das Du mit Deiner "Lösungsidee" versuchst zu lösen?
Ich versuche ein kleines Programm zu schreiben, das mir in einer Verzeichnishierarchie einen Überblick über alle Verzeichnisse (und Unterverzeichnisse usw.) verschaffen soll. (Z. B.: enthält x MP3s. Oder: hat y Unterordner. Oder: enthält Dateien mit insgesamt z MB.)
Mein Ansatz war: Ich bilde jedes Verzeichnis als Instanz der Klasse "dir" ab. Die Klasse hat eine Methode "scan", die alle Einträge in dem Verzeichnis (in der Regel: Dateien und Unterverzeichnisse) durchgeht und Informationen sammelt. Im Fall von Unterverzeichnissen ruft die Methode für dieses Unterverzeichnis die Methode "scan" (und damit implizit auch "__init__") auf usw.
Das scheint soweit auch zu funktionieren. Ich habe am Ende für jedes (Unter-)Verzeichnis, das ich abfrage, ein Objekt. Aber ich habe keine Liste aller Objekte, über die ich iterieren kann, um zum Beispiel Ordner mit bestimmten Eigenschaften aufzulisten.

Hab ich mich verständlich ausgedrückt?

Danke für eure Hilfe und
viele Grüße!


Holger
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Was hindert dich, diese Objekte in eine Liste zu stecken? So macht man das einfach. Du operierst hier schon wieder gedanklich auf globalen Datenstrukturen. Das war auch vor einigen Jahren schon eine doofe Idee, und ist es immer noch.
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Eben, stecke die Ergebnisse in geeignete Strukturen. Wie willst du so ein globales Wirrwarr sonst handhaben? Das wäre bei meinem weakref-Code übrigens auch der Fall. Denn da wird (bisher) nicht nach Typ unterschieden, sondern stumpf alles an neuen Instanzen in das Set geschmissen. Das ist zur produktiven Nutzung also eher Murks. In erster Linie wollte ich zeigen, dass es prinzipiell funktioniert.

EDIT:
Klappt aber, wenn man sich auch den Typen merkt. Sieht nur etwas seltsam aus:

Code: Alles auswählen

from weakref import WeakKeyDictionary, WeakSet

class RefHolder:
    _refs = WeakKeyDictionary()

    def __new__(cls, *args, **kwargs):
        instance = object.__new__(cls)
        if cls not in cls._refs:
            cls._refs[cls] = WeakSet()
        cls._refs[cls].add(instance)
        return instance

    @classmethod
    def get_references(cls):
        yield from cls._refs[cls]
Ich würde das letztlich wohl auch eher als Decorator lösen, ähnlich wie @dataclass das macht. Trotzdem dürfte das keine angemessene Lösung für den OP sein.
Holger Chapman
User
Beiträge: 35
Registriert: Samstag 12. Juli 2014, 01:59

Hallo,

ich habe mal versucht, eure Anregungen zu berücksichtigen, und ein Beispiel geschrieben: ein kleines Programm, das mir (beginnend mit $HOME) die Verzeichnisse mit dem meisten Speicherverbrauch ausgibt.

(Das Programm ist erstmal nur ein Grundgerüst. Es ignoriert zum Beispiel (fehlende) Zugriffsrechte und Verzeichnis-Einträge, die nicht normale Dateien oder Unterverzeichnisse sind.)

Mich interessiert, ob das Programm aus eurer Sicht sauber geschrieben ist, oder was ich verbessern könnte.

Vielen Dank!


Holger

Code: Alles auswählen

#!/usr/bin/env python3

# list 10 dirs with maximum recursive file size (starting with $HOME)

import os

class dir:
    def __init__(self, abspath):
        self.path = abspath
        self.recursivefilesizesum = 0
    def scan(self, scanneddirs):
        self = dir(self)
        for direntry in os.listdir(self.path):
            direntry = os.path.join(self.path, direntry)
            if os.path.isdir(direntry):
                scanneddirs, additionalfilesize = dir.scan(direntry, scanneddirs)
                self.recursivefilesizesum = self.recursivefilesizesum + additionalfilesize
            elif os.path.isfile(direntry):
                self.recursivefilesizesum = self.recursivefilesizesum + file.getsize(direntry)
        scanneddirs.append(self)
        return scanneddirs, self.recursivefilesizesum

class file:
    def __init__(self, abspath):
        self.path = abspath
        self.size = 0
    def getsize(self):
        self = file(self)
        filesize = os.stat(self.path).st_size
        return filesize

def humanReadableFilesize(filesize):
    if filesize > (1024**4):
        filesizeString = format(filesize/(1024**4), '.1f').rjust(5) + ' T'
    elif filesize > (1024**3):
        filesizeString = format(filesize/(1024**3), '.1f').rjust(5) + ' G'
    elif filesize > (1024**2):
        filesizeString = format(filesize/(1024**2), '.1f').rjust(5) + ' M'
    elif filesize > 1024:
        filesizeString = format(filesize/1024, '.1f').rjust(5) + ' K'
    else:
        filesizeString = format(filesize, '.1f').rjust(5) + ' B'
    return filesizeString

def printlargestdirs(dirlist, number):
    dirsizes = {}
    for dir in dirlist:
        dirsizes[dir.path] = dir.recursivefilesizesum
    dirssorted = list(reversed(sorted(dirsizes.items(), key=lambda x: x[1])))
    for path, size in dirssorted[0:number]:
        print(humanReadableFilesize(size) + " in dir: " + str(path))

def main():
    path = os.environ.get("HOME")
    showdirs = 10
    scanneddirs, x = dir.scan(path, [])
    printlargestdirs(scanneddirs, showdirs)

if __name__ == "__main__":
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 14030
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Holger Chapman: die Klassen machen keinen Sinn weil bei beiden die Klasse gar nicht wirklich verwendet wird, und das was da gemacht wird ist Murks. `self` sollte bitte `self` bleiben und nicht gleich am Anfang der ”Methode” durch etwas anderes ersetzt werden, was extrem verwirrend ist.

Bei den Klassen sind `size` und `recursivefilesizesum` keine sinnvollen Attribute wenn die nach `__init__()` nicht den tatsächlichen Wert haben.

Klassennamen werden in PascalCase geschrieben. `dir()` ist zudem auch noch der Name einer eingebauten Funktion. Ausser Konstanten (KOMPLETT_GROSS) werden Namen `klein_mit_unterstrichen` geschrieben. Und Unterstriche sind für Leerzeichen zwischen Worten, also `printlargestdirs` istaucheherschwezulesensoganzohne Leerzeichen.

In neuem Code würde man eher zu `pathlib` statt zum alten `os.path` greifen.

Zusammenstückeln von Zeichenketten und Werten mit `str()` und ``+`` ist eher BASIC denn Python. Python hat dafür f-Zeichenkettenliterale.

Das formatieren der Dateigrösse(n) ist ziemlich regelmässig aufgebaut, das liesse sich mit einer Schleife kompakter schreiben.

Code: Alles auswählen

#!/usr/bin/env python3
# list 10 dirs with maximum recursive file size (starting with $HOME)
from itertools import islice
from pathlib import Path


def format_file_size(size):
    for exponent, unit in reversed(list(enumerate("BKMGT"))):
        factor = 1024 ** exponent
        if size >= factor:
            return f"{size / factor:.1f} {unit}"

    raise ValueError(f"illegal file size {size!r}")


def scan_directories(path):
    result = []

    def recurse(path):
        size = 0
        for sub_path in path.iterdir():
            if sub_path.is_dir():
                size += recurse(sub_path)
            elif sub_path.is_file():
                size += sub_path.stat().st_size
        result.append((size, path))
        return size

    recurse(path)
    return result


def print_largest_dirs(sizes_and_paths, count):
    for size, path in islice(sorted(sizes_and_paths, reverse=True), count):
        print(f"{format_file_size(size)} in dir: {path}")


def main():
    print_largest_dirs(scan_directories(Path.home()), 10)


if __name__ == "__main__":
    main()
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Holger Chapman
User
Beiträge: 35
Registriert: Samstag 12. Juli 2014, 01:59

Hallo __blackjack__,

vielen Dank für Deine Anregungen und Deine Mühe! Das ging ja deutlich über eine Fünf-Minuten-Antwort hinaus. Super!

Vor allem das doppelte Definieren von "self" kam mir schon selbst komisch vor, aber anders hab ich die Funktionalität zunächst nicht hingekriegt.

Ich werde mir Deine Anregungen in Ruhe ansehen ... ich glaube, damit komme ich schon ein ganzes Stück weiter!

Viele Grüße


Holger
Antworten