@Bykl: Du weisst das der Einstiegspunkt in einem C++-Programm eine `main()`-Funktion ist, aber nicht was Klassen sind?
Klassen fassen Werte und darauf operierende Funktionen zusammen. In Python kann man die Begriffe ”Klasse” und ”Datentyp” synomym verwenden, weil jedem Datentyp eine Klasse zugrunde liegt. In Deinem Programm ist beispielsweise `Workbook` eine Klasse:
Code: Alles auswählen
In [43]: import inspect
In [44]: inspect.isclass(xlwt.Workbook)
Out[44]: True
Globale Variablen/Container sollten nirgends platziert werden, es sollte sie gar nicht geben. Die machen Code unnötig schwer verständlich und damit fehleranfälliger und schwerer testbar, wodurch Fehler dann auch noch schwerer zu finden sind. Funktionen und Methoden bekommen alles was sie ausser Konstanten brauchen als Argument(e) übergeben und liefern Ergebnisse als Rückgabewerte an den Aufrufer. Container sollten möglichst nicht verändert werden, es sei denn das ist für den Aufrufer nicht überraschend oder Dokumentiert. Damit es nicht überraschend ist. So vermeidet man unnötige Fehler die man nicht so leicht finden kann.
Zu den Leerzeichen: Statt ``name=wert`` besser ``name = wert``, statt ``a+b/c`` besser ``a + b / c``, statt ``a,b,c`` besser ``a, b, c``. Also im Grunde die gleichen Richtlinien die man bei Text auch befolgt. Ausnahme bei den Zuweisungen sind Argumentlisten, also entweder bei Funktions-/Methodendefinitionen oder bei deren Aufruf. So kann man die leicht von den anderen, ”normalen” Zuweisungen unterscheiden, auch wenn sich die Argumentliste über mehrer Zeilen erstreckt. Ein Blick in den
Style Guide for Python Code lohnt sich da.
Die Erklärung mit den zwei Funktionen verstehe ich nicht und ich denke das ist ein Missverständnis deinerseits wie Funktionen funktionieren. Dafür spricht auch Deine Erklärung für das `j` als Laufvariable. Jeder Aufruf von `primteiler_a()` erzeugt eine neue Liste mit Ergebnissen. Da braucht man keine identische zweite Funktion die nur andere Namen verwendet für. Die Namen die lokal in einer Funktion verwendet werden haben auch nichts mit Namen in anderen Funktionen oder Modulen zu tun. Das ist ja gerade der Witz von Funktionen, dass das in sich geschlossene ”Miniprogramme” sein sollten, wo man über die Argumente Werte reinfüttert und per ``return`` einen Rückgabwert liefern kann, und das dazwischen am besten keinen Einfluss auf den Rest des Programms haben sollte. Denn dann kann man Funktionen isoliert betrachten und nachvollziehen, ohne das ganze Programm kennen zu müsssen und sie auch einzeln testen, ob sie das tun was sie sollen. Zum Beispiel um Fehler zu finden, oder automatisierte Tests zu schreiben.
Ja es gibt ``pass``. Die blosse Existenz eines Schlüsselwortes sagt aber nicht, dass das überall wo man es hinschreiben kann Sinn macht und verwendet werden sollte. Im Grunde noch nicht einmal das es überhaupt verwendet werden sollte. ``global`` ist beispielsweise etwas von dem man eher grundsätzlich die Finger lassen sollte.
Es gibt Stellen da *muss* man tatsächlich aus syntaktischen Gründen etwas hinschreiben, auch wenn da gar nichts passieren soll. Ein ``if``- oder ein ``else``-Zweig gehören nicht dazu. Ein ``else: pass`` kann man einfach weg lassen ohne das sich am Verhalten des Programms etwas ändert. Und bei einem ``if bedingung: pass`` mit einem folgenden ``else`` kann man die Bedingung einfach negieren und den ``else``-Zweig in den ``if``-Zweig packen und das ``else`` weg lassen.
Einsatzzwecke für ``pass`` sind beispielsweise Klassen von denen man nur ableitet, aber sonst nichts verändern will. Typisch dafür sind Ausnahmen, falls man da keinen DocString schreiben will. Oder ``except``-Blöcke wo nichts passieren soll. Sieht man oft beim behandeln von `KeyboardInterrupt`.
Manchmal sieht man das als Platzhalter für noch nicht geschriebenen Code. Da sollte man aber einen entsprechenden Kommentar dazu schreiben, damit man diese Verwendung von den anderen, wo das ``pass`` tatsächlich da stehen soll, unterscheiden kann. Ich persönlich nehme dafür lieber das `Ellipsis`-Objekt das man als ``...`` schreiben kann.
Der Grund für eine ``while``-Schleife ist, dass man eine Bedingung hat, die man immer wieder aufs neue prüfen muss, und deren Bestandteile nicht so aktualisiert werden, dass man eine ``for``-Schleife verwenden könnte. Wenn man also eine ``while``-Schleife sieht, bei der vorher eine ganzzahlige Variable definiert wird, die in der Bedingung mit einem anderen Wert verglichen wird, dann kann es eigentlich nicht sein, dass die ”Laufvariable” in jedem Schleifendurchlauf bedingungslos gleichartig verändert wird und/oder der andere Wert konstant bleibt. Wenn man so ein ``while`` sieht, muss man beim lesen davon ausgehen, das da irgend etwas komplizierteres vor sich geht. Wenn wenn man dafür eine einfache ``for``-Schleife hätte schreiben können, hätte man das ja gemacht. Bei ``for i in range(n):`` weiss ich schon bei dieser einen Zeile am Anfang der Schleife wie sich die Werte von `i` in jedem Durchlauf ergeben. Bei ``i = 0`` gefolgt von ``while i < n:`` habe ich keine Ahnung wie sich `i` entwickeln wird, ohne den gesamten Schleifenkörper gelesen zu haben.
`primteiler_von_a_raussuchen()` enthält die unnötige Information `von_a`. Ansonsten ja, ich würde das `berechne_primteiler()` oder so ähnlich nennen. Man sieht daran, dass es eine Funktion ist, und man kann das *Ergebnis* davon dann `primteiler` oder `primteiler_a` nennen, ohne eine Namenskollision mit der Funktion zu bekommen.
`rechts` und `links` oder `oben` und `unten` machen aber nur Sinn wenn diese Namen tatsächlich bei den jeweiligen Werten Sinn machen.
Schlechte Namen stehen nicht in jedem Tutorium, aber ja, leider in vielen. Oft weil es dort einfach bei supergenerischen Beispielen einfach keinen passenderen Namen gibt, leider aber auch oft wenn es welche gäbe. Viele Tutorials gehen leider davon aus, das man nur die Syntax vermitteln muss. Gute Namensgebung ist aber wichtig und nicht nur Kosmetik, weil sie zum Verständnis des Programms beiträgt. Und nicht nur bei ”fremden” Lesern, sondern auch beim Programmierer selbst. Bei schlechten Namen können Fehler passieren die bei guten Namen auffallen würden. Wenn man Probleme hat einen guten Namen für einen Wert zu finden, ist das häufig ein Zeichen, dass man entweder das Problem oder die Lösung nicht ganz verstanden hat. Oder das man versucht Daten die nicht zusammengehören in einem Objekt oder einer Datenstruktur zusammenzufassen.
Wenn Du nicht verstehst warum da ein `set()` hin muss, warum hast *Du* das denn da hin geschrieben? Letztlich wird dort die Schnittmenge der beiden Teilerlisten ermittelt und dafür hat der `set()`-Datentyp eine Operation. Zusätzlich ”filtert” der mehrfache Elemente aus, weil jedes Element nur einmal in einer Menge vorkommen kann.
Bei dem `i` ist eigentlich immer `b` geht es weniger um programmatische Effizienz, denn diese eine zusätzliche Variable ist ja nichts was den Rechner jetzt stark belasten würde, sondern darum das für den Leser einfach zu halten. Der Mensch kann nur eine bestimmte Menge an Informationen/Variablen gleichzeitig im Kopf jonglieren. Je nach Studie sind das wohl so zwischen fünf bis sieben ”Dinge”, weshalb man mit der Anzahl an unterschiedlichen Variablen in einer Funktion oder mindestens in einem Code-Abschnitt sparsam sein sollte.
Ich sehe nicht warum bei dem gezeigten Code das überschreiben von Zellwerten erlaubt sein müsste. Aber selbst wenn, ist es halt falsch das `True` durch so eine total verwirrende Zeichenkette zu ersetzen, die aussieht als würde dort ein Schlüsselwort-Argument übergeben. Insbesonder weil da "cell_overwrite_ok=False" genau den gleichen Effekt hätte, weil das eben auch im boole'schen Kontext `True` ist.
Ich habe die Hinweise mal umgesetzt und dabei hoffentlich keinen Fehler gemacht (ungetestet):
Code: Alles auswählen
#!/usr/bin/env python3
import xlwt
from sympy.ntheory import isprime
def berechne_primteiler(zahl):
primteiler = []
for i in range(1, zahl // 2):
if zahl % i == 0:
teiler = zahl // i
#
# TODO Warum wird hier die 2 als Primfaktor ausgenommen? Und warum
# wird dieser Umstand so umständlich/indirekt formuliert und nicht
# durch ein ``teiler != 2`` anstelle von ``teiler % 2 > 0``?
#
if teiler % 2 > 0 and isprime(teiler):
primteiler.append(teiler)
return primteiler
def main():
zahl = 9996 # Zahl mit Endziffer 6 angeben.
assert zahl % 10 == 6, "`zahl` hat falsche Endziffer"
anzahl_treffer = 0
b = None
for kandidaten_nummer, b in enumerate(range(3, zahl // 2 + 1, 2), 1):
a = zahl - b
primteiler_a = berechne_primteiler(a)
primteiler_b = berechne_primteiler(b)
if primteiler_a and primteiler_b:
anzahl_treffer += 1
print(
"Nr:",
kandidaten_nummer,
"Nr1",
anzahl_treffer,
"Ohne gem PT:",
a,
primteiler_a,
b,
primteiler_b,
set(primteiler_a) & set(primteiler_b),
a / b,
)
if b is not None:
book = xlwt.Workbook(encoding="utf-8")
sheet = book.add_sheet("Zahlsucher")
for i, text in enumerate(
["Nr", "Zahl", "a", "PrTa", "b", "PrTb", "a//b"]
):
sheet.write(0, i, text)
#
# TODO Ausser `i` und `teiler` enthalten alle Spalten in jeder Zeile den
# gleichen Wert. Das sieht falsch aus‽
#
quotient = a // b
for i, teiler in enumerate(primteiler_a, 1):
sheet.write(i, 0, i)
sheet.write(i, 1, teiler)
sheet.write(i, 2, a)
sheet.write(i, 3, primteiler_a)
sheet.write(i, 4, b)
sheet.write(i, 5, primteiler_b)
sheet.write(i, 6, quotient)
book.save("Zahlsucher.xls")
if __name__ == "__main__":
main()