@sebastian0202: Es gibt kein '%0ANZAHL_STELLENd' aber '%0*d'. Und bei `format()` kann man Platzhalter auch verschachteln. Notfalls gäbe es auf Zeichenketten auch noch die `zfill()`-Methode.
Code: Alles auswählen
In [13]: '%0*d' % (4, 42)
Out[13]: '0042'
In [14]: '{0:0{1}d}'.format(42, 4)
Out[14]: '0042'
In [15]: str(42).zfill(4)
Out[15]: '0042'
`sorted()` kannst Du natürlich trotzdem nehmen wenn Du es auf die umgewandelten Daten anwendest, also einfach statt der „list comprehension“ die `sorted()`-Funktion mit einem Generatorausdruck als Argument:
sorted_numbers = sorted(int(s) for s in strings)
Bezüglich `any()`/`all()`: Aus diesem hier:
Code: Alles auswählen
def add_group(self, zahl, liste):
gefunden = False
for group in self.groups:
if zahl in group:
gefunden = True
break
if not gefunden:
self.groups.append(liste)
wird das hier (ungetestet):
Code: Alles auswählen
def add_group(self, zahl, liste):
if all(zahl not in group for group in self.groups):
self.groups.append(liste)
`Code` macht IMHO schon wieder zu viel. Das `codes`-Attribut hat da nichts zu suchen. `_generation` eigentlich auch nicht, denn ich würde erwarten das `Code` ein Wertobjekt ist und der Wert nur aus dem Zahlwert und der Länge eines Codes besteht. Du packst da gleich noch alle Codes die aus dem Code folgen mit hinein, wobei das aber abhängig davon ist ob und wie viele man davon hat generieren lassen.
`next_code()` würde ich `advance_code()` nennen, weil man nicht nur den nächsten Code bekommen kann, sondern auch beliebig weite Sprünge machen kann. Das und auch das mit dem Zufallswert in der `__init__()` scheint mir hier überflüssig zu sein‽
Wenn `Code` kein Werttyp ist, dann würde ich da einen Iterator draus machen statt eine `next_code()`-Methode zur Verfügung zu stellen. `CodeIterator` wäre dann auch ein passenderer Name IMHO. Denn es ist ja kein Code sondern enthält einen (aktuellen) Code (und eventuell alle seine Vorgänger).
``while generations > 0:`` sollte eine ``for``-Schleife sein.
`get_codes()` und `get_generation()` sind trivialer Getter → weg damit. Aus `get_code()` könnte man ein `property()` machen.
`show_generations()` wäre in Haskell vielleicht der passende Namen, aber in Python eher nicht. Da würde man bei `show_irgendwas()` erwarten das etwas ausgegeben oder angezeigt wird. Die Methode ist vom Inhalt her auch mehr was für die Benutzerinteraktion und nicht für die Programmlogik.
”Magische” Methoden wie `__str__()` erwartet man eigentlich am Anfang der Klasse vor Properties und normalen Methoden und nicht versteckt als letzte Methode.
``self.ziffern[self.laenge*-1:]`` würde ich als ``self.ziffern[-self.laenge:]`` schreiben. Eine `collections.deque` statt einer Liste würde die Sache vereinfachen und eventuell auch effizienter machen.
`CodeGruppe` enthält mehr als eine Gruppe, sollte also `CodeGruppen` heissen. Das `_group` kann aus allen Methodennamen verschwinden, denn es doppelt nur noch mal die Typinformation.
Wenn ``if``/``else`` in den zweigen nur `True` oder `False` zurück gibt, dann ist das Konstrukt unnötig, denn die Bedingung vom ``if`` ist ja schon der Wert den man haben möchte, oder seine Negation, die man mit ``not`` bekommt. Also wird `number_exists()` zu einem Einzeiler: ``return code in self._codes``.
Eine Funktion sollte immer nur einen (Duck)Typ als Ergebnis liefern, also beispielsweise nicht mal `False` und mal eine Zahl. Das lässt sich in diesem Fall auch nur über einen Typtest prüfen, denn `False` ist eine Zahl und hat den gleichen Wert wie 0 beim Vergleichen und allen anderen Operationen die auf `int` definiert sind:
Code: Alles auswählen
In [21]: isinstance(False, int)
Out[21]: True
In [22]: False == 0
Out[22]: True
In [23]: False + 42
Out[23]: 42
In [24]: False * 42
Out[24]: 0
Statt spezielle Fehlercodes zurück zu geben, verwendet man in Python Ausnahmen. `get_group_number()` sollte eine Auslösen wenn der `code` nicht enthalten ist. `KeyError` würde sich anbieten.
`get_group_numbers()` nutzt `enumerate()` wo ein `range()` ausreichen würde. In Python 3 mit einem `list()` um da tatsächlich eine Liste draus zu machen. Sofern das benötigt wird tatsächlich eine Liste vorliegen zu haben.
Die API von `get_group_info()` ist mir zu magisch. Warum nicht einfach eine Liste mit Nummern statt ein *-Parameter? Was ist der Anwendungsfall für einen Aufruf mit mehr als einer Nummer bei der beim Aufruf *kein* Sternchen gebraucht wird? Der zudem einen besseren Namen als `*arg` haben könnte. Der Leser will nicht wissen das da irgendwelche Argumente übergeben werden können, sondern was sie bedeuten.
Eventuell würde es die API von `CodeGruppen` schöner machen, wenn man da die ”magischen” Methoden für einen Containertyp implementiert. Wohl eine Abbildung in diesem Fall.
Im Hauptprogramm sind 1000, 4, und 4800 Zahlen die zusammenhängen. Wobei 4800 IMHO einer Erklärung bedarf. Warum ist diese Zahl sicher? Wie berechnet die sich aus der Stellenanzahl? Ist der Code robust gegen den Fall das eine Kombination niemals wieder erreicht würde? Was wäre da das Gegenargument?
Zur Frage wann es sich lohnt ein Objekt zu erstellen: Implementiere das doch mal nur mit Funktionen. Dann sieht man in der Regel leichter welche Daten und welche Funktionen man zusammenfassen kann/sollte. Wie gesagt Funktionen sind nicht böse und Klassen sind kein Selbstzweck. Wenn man sie nicht benötigt, sollte man sie auch nicht schreiben und man sollte auch nicht versuchen krampfhaft jede ``def``\inition in eine Klasse stecken zu wollen.
Edit: Man braucht die Fähigkeit Code sinnvoll auf Funktionen aufzuteilen auch bei OOP, denn dort muss man ihn sinnvoll auf Klassen und Methoden aufteilen. Wenn man also schon bei der Aufteilung auf Funktionen Probleme hat, dann wird das bei OOP nur noch schwieriger. Ich erwähne das weil Dein Versuch weiter oben im Thema die ursprüngliche Aufgabe auf Funktionen zu verteilen auch schon nicht gut war. Auch ein Grund erst einmal Funktionen zu üben.