Changeset fe7d338 in flexograder
- Timestamp:
- 11/09/25 22:08:21 (2 months ago)
- Branches:
- master
- Children:
- bb42e13
- Parents:
- e77bfb3
- Files:
-
- 4 added
- 5 edited
-
builder/question_factory.py (modified) (1 diff)
-
examples/python/intermediate.json (modified) (1 diff)
-
gui/actions_panel.py (added)
-
gui/announcer.py (added)
-
gui/detail_panel.py (modified) (1 diff)
-
gui/gui.py (modified) (33 diffs)
-
gui/menu.py (added)
-
gui/option_question_editor.py (modified) (3 diffs)
-
gui/select_panel.py (added)
Legend:
- Unmodified
- Added
- Removed
-
builder/question_factory.py
re77bfb3 rfe7d338 34 34 35 35 domain = q.get("domain") or default_domain 36 print(domain)37 36 text = q.get("text", "") 38 37 estate = EntityState.DRAFT -
examples/python/intermediate.json
re77bfb3 rfe7d338 4 4 "generated_at": "2025-10-20", 5 5 "questions": [ 6 { "domain": "PY-ARITHM", "type": "radio", "text": "Welcher Operator liefert in Python 3 die Ganzzahl-Division (Abrunden Richtung −∞)?", "options": [ 7 {"text": "//", "points": 1}, 8 {"text": "/" , "points": 0}, 9 {"text": "%", "points": 0} 10 ]}, 11 { "domain": "PY-ARITHM", "type": "checkbox", "text": "Welche Aussagen über Ganzzahlen (int) in Python sind korrekt?", "options": [ 12 {"text": "int kann beliebig groß werden (nur durch RAM begrenzt).", "points": 1}, 13 {"text": "int hat fest 64-Bit.", "points": 0}, 14 {"text": "Negatives Ergebnis bei -7 // 3 ist -3.", "points": 1}, 15 {"text": "Bei 7 % 3 ist der Rest 1.", "points": 1} 16 ]}, 17 { "domain": "PY-ARITHM", "type": "radio", "text": "Welches Ergebnis hat 2 ** 3 ** 2?", "options": [ 18 {"text": "512 (weil Exponentiation rechtsassoziativ ist).", "points": 1}, 19 {"text": "64", "points": 0}, 20 {"text": "Fehler, da unklar.", "points": 0} 21 ]}, 22 { "domain": "PY-ARITHM", "type": "checkbox", "text": "Welche Funktionen helfen beim Runden/Umwandeln?", "options": [ 23 {"text": "round(x)", "points": 1}, 24 {"text": "int(x)", "points": 1}, 25 {"text": "float.to_int(x)", "points": 0}, 26 {"text": "abs(x)", "points": 1} 27 ]}, 28 { "domain": "PY-ARITHM", "type": "radio", "text": "Was liefert 0.1 + 0.2 in Python typischerweise?", "options": [ 29 {"text": "Einen Wert nahe 0.3 wegen Binär-Float, daher vergleicht man besser mit math.isclose.", "points": 1}, 30 {"text": "Genau 0.3, immer.", "points": 0}, 31 {"text": "Eine Exception.", "points": 0} 32 ]}, 33 34 { "domain": "PY-CNTRLSTRCT", "type": "radio", "text": "Wie wiederholt man Code, bis eine Bedingung erfüllt ist?", "options": [ 35 {"text": "while-Schleife", "points": 1}, 36 {"text": "if-Anweisung", "points": 0}, 37 {"text": "import-Anweisung", "points": 0} 38 ]}, 39 { "domain": "PY-CNTRLSTRCT", "type": "checkbox", "text": "Welche Schlüsselwörter passen zu Schleifen?", "options": [ 40 {"text": "break (Schleife vorzeitig beenden)", "points": 1}, 41 {"text": "continue (nächste Iteration)", "points": 1}, 42 {"text": "nextloop (existiert nicht)", "points": 0}, 43 {"text": "else: läuft, wenn kein break passierte", "points": 1} 44 ]}, 45 { "domain": "PY-CNTRLSTRCT", "type": "radio", "text": "Wie iteriert man idiomatisch über Index und Element einer Liste?", "options": [ 46 {"text": "for i, x in enumerate(items):", "points": 1}, 47 {"text": "for i in items: x = items[i]", "points": 0}, 48 {"text": "for (i, x) in range(items):", "points": 0} 49 ]}, 50 { "domain": "PY-CNTRLSTRCT", "type": "checkbox", "text": "Welche Teile gehören zur if-Syntax?", "options": [ 51 {"text": "if …:", "points": 1}, 52 {"text": "elif …:", "points": 1}, 53 {"text": "elseif …:", "points": 0}, 54 {"text": "else:", "points": 1} 55 ]}, 56 { "domain": "PY-CNTRLSTRCT", "type": "radio", "text": "Wofür steht der Walrus-Operator (:=)?", "options": [ 57 {"text": "Zuweisung in einem Ausdruck (z. B. while (n := input()) != '')", "points": 1}, 58 {"text": "Vergleich auf Gleichheit", "points": 0}, 59 {"text": "Exponentiation", "points": 0} 60 ]}, 61 62 { "domain": "PY-FILES", "type": "radio", "text": "Wie öffnet man eine Textdatei sicher zum Lesen in UTF-8?", "options": [ 63 {"text": "open(path, 'r', encoding='utf-8')", "points": 1}, 64 {"text": "open(path)", "points": 0}, 65 {"text": "open('utf-8', path)", "points": 0} 66 ]}, 67 { "domain": "PY-FILES", "type": "checkbox", "text": "Welche Vorteile hat der with-Block beim Datei-I/O?", "options": [ 68 {"text": "Automatisches Schließen der Datei.", "points": 1}, 69 {"text": "Weniger Risiko für Ressourcen-Leaks.", "points": 1}, 70 {"text": "Er macht das Lesen schneller per se.", "points": 0}, 71 {"text": "Kompakterer Code.", "points": 1} 72 ]}, 73 { "domain": "PY-FILES", "type": "radio", "text": "Welcher Modus legt eine neue Datei an und wirft Fehler, falls sie existiert?", "options": [ 74 {"text": "'x'", "points": 1}, 75 {"text": "'w'", "points": 0}, 76 {"text": "'a'", "points": 0} 77 ]}, 78 { "domain": "PY-FILES", "type": "checkbox", "text": "Welche Bibliotheken sind für Pfade hilfreich?", "options": [ 79 {"text": "pathlib.Path", "points": 1}, 80 {"text": "os.path", "points": 1}, 81 {"text": "sys.path zum Dateischreiben", "points": 0}, 82 {"text": "glob für Dateimuster", "points": 1} 83 ]}, 84 { "domain": "PY-FILES", "type": "radio", "text": "Wie liest man große Dateien speicherschonend Zeile für Zeile?", "options": [ 85 {"text": "for line in f:", "points": 1}, 86 {"text": "lines = f.readlines()", "points": 0}, 87 {"text": "text = f.read()", "points": 0} 88 ]}, 89 90 { "domain": "PY-TYPES", "type": "radio", "text": "Wozu dienen Typannotationen in Python?", "options": [ 91 {"text": "Sie unterstützen Tools/IDE/Typchecker; zur Laufzeit optional.", "points": 1}, 92 {"text": "Sie erzwingen strikt die Typen zur Laufzeit.", "points": 0}, 93 {"text": "Sie ersetzen Docstrings.", "points": 0} 94 ]}, 95 { "domain": "PY-TYPES", "type": "checkbox", "text": "Welche sind gültige Typannotationen (Beispiele ab Python 3.9)?", "options": [ 96 {"text": "list[int]", "points": 1}, 97 {"text": "dict[str, int]", "points": 1}, 98 {"text": "Optional[int] bzw. int | None", "points": 1}, 99 {"text": "tuple<int>", "points": 0} 100 ]}, 101 { "domain": "PY-TYPES", "type": "radio", "text": "Welche Annotation beschreibt 'Funktion erhält int und gibt bool zurück'?", "options": [ 102 {"text": "Callable[[int], bool]", "points": 1}, 103 {"text": "function(int)->bool", "points": 0}, 104 {"text": "call[int->bool]", "points": 0} 105 ]}, 106 { "domain": "PY-TYPES", "type": "checkbox", "text": "Welche Stdlib-Hilfen definieren einfache Datenobjekte?", "options": [ 107 {"text": "dataclasses.dataclass", "points": 1}, 108 {"text": "typing.TypedDict", "points": 1}, 109 {"text": "collections.namedtuple", "points": 1}, 110 {"text": "functools.singledispatch", "points": 0} 111 ]}, 112 { "domain": "PY-TYPES", "type": "radio", "text": "Was bedeutet `Any`?", "options": [ 113 {"text": "Beliebiger Typ (Typchecker schränkt kaum ein).", "points": 1}, 114 {"text": "Nur Ganzzahlen erlaubt.", "points": 0}, 115 {"text": "Ist identisch zu `object` in jeder Hinsicht.", "points": 0} 116 ]}, 117 118 { "domain": "PY-STREAMS", "type": "radio", "text": "Was gibt eine Listen-Comprehension typischerweise zurück?", "options": [ 119 {"text": "Eine neue Liste", "points": 1}, 120 {"text": "Einen Generator", "points": 0}, 121 {"text": "Ein Tupel", "points": 0} 122 ]}, 123 { "domain": "PY-STREAMS", "type": "checkbox", "text": "Welche sind Lazy-Konstrukte?", "options": [ 124 {"text": "(x for x in seq) (Generator-Expression)", "points": 1}, 125 {"text": "map(func, seq)", "points": 1}, 126 {"text": "filter(func, seq)", "points": 1}, 127 {"text": "[x for x in seq]", "points": 0} 128 ]}, 129 { "domain": "PY-STREAMS", "type": "radio", "text": "Wofür steht `yield` in einer Funktion?", "options": [ 130 {"text": "Es macht die Funktion zu einem Generator (liefert Werte schrittweise).", "points": 1}, 131 {"text": "Es beendet das Programm.", "points": 0}, 132 {"text": "Es importiert ein Modul.", "points": 0} 133 ]}, 134 { "domain": "PY-STREAMS", "type": "checkbox", "text": "Welche Tools sind nützlich für Iterables?", "options": [ 135 {"text": "itertools (z. B. chain, islice)", "points": 1}, 136 {"text": "enumerate", "points": 1}, 137 {"text": "re (Regex) als Iteratorersatz", "points": 0}, 138 {"text": "sum zur Konkatenation von Listen", "points": 0} 139 ]}, 140 { "domain": "PY-STREAMS", "type": "radio", "text": "Welcher Ausdruck ist am speicherschonendsten?", "options": [ 141 {"text": "sum(x for x in range(1_000_000))", "points": 1}, 142 {"text": "sum([x for x in range(1_000_000)])", "points": 0}, 143 {"text": "list(range(1_000_000))", "points": 0} 144 ]}, 145 146 { "domain": "PY-FUNCTOOLS", "type": "radio", "text": "Wozu dient `functools.lru_cache`?", "options": [ 147 {"text": "Ergebnisse teurer Funktionsaufrufe zwischenspeichern.", "points": 1}, 148 {"text": "Dateien schneller lesen.", "points": 0}, 149 {"text": "Asynchronität bereitstellen.", "points": 0} 150 ]}, 151 { "domain": "PY-FUNCTOOLS", "type": "checkbox", "text": "Welche Aussagen zu `partial` stimmen?", "options": [ 152 {"text": "`partial` bindet einige Argumente vor.", "points": 1}, 153 {"text": "Die neue Funktion verhält sich wie die ursprüngliche mit den gebundenen Werten.", "points": 1}, 154 {"text": "`partial` ersetzt kwargs vollständig.", "points": 0}, 155 {"text": "`partial` ist ein Decorator für Klassen.", "points": 0} 156 ]}, 157 { "domain": "PY-FUNCTOOLS", "type": "radio", "text": "Was macht `@wraps` beim Schreiben eigener Decorators?", "options": [ 158 {"text": "Überträgt Metadaten (Name, Docstring) auf die Wrapper-Funktion.", "points": 1}, 159 {"text": "Beschleunigt die Funktion automatisch.", "points": 0}, 160 {"text": "Erstellt eine Klasse.", "points": 0} 161 ]}, 162 { "domain": "PY-FUNCTOOLS", "type": "checkbox", "text": "Welche Aussagen zu `singledispatch` sind korrekt?", "options": [ 163 {"text": "Erzeugt generische Funktionen je nach Typ des ersten Arguments.", "points": 1}, 164 {"text": "Weitere Varianten werden mit @<func>.register definiert.", "points": 1}, 165 {"text": "Funktioniert nicht mit Klassenmethoden/Methoden-Workarounds.", "points": 0}, 166 {"text": "Ist Teil der Stdlib.", "points": 1} 167 ]}, 168 { "domain": "PY-FUNCTOOLS", "type": "radio", "text": "Wie leert man den Cache einer mit `lru_cache` dekorierten Funktion?", "options": [ 169 {"text": "funktion.cache_clear()", "points": 1}, 170 {"text": "del funktion", "points": 0}, 171 {"text": "Cache leert sich automatisch bei Programmstart nicht", "points": 0} 172 ]}, 173 174 { "domain": "PY-COLLECTIONS", "type": "radio", "text": "Welche Struktur eignet sich für schnelle Anhänge am Ende?", "options": [ 175 {"text": "list.append", "points": 1}, 176 {"text": "tuple +=", "points": 0}, 177 {"text": "str +=", "points": 0} 178 ]}, 179 { "domain": "PY-COLLECTIONS", "type": "checkbox", "text": "Welche Aussagen zu Mengen (set) sind korrekt?", "options": [ 180 {"text": "Speichern einzigartige, hashbare Elemente.", "points": 1}, 181 {"text": "Unterstützen Vereinigungs-/Schnittmengen-Operationen.", "points": 1}, 182 {"text": "Erhalten die Einfügereihenfolge garantiert vor 3.7.", "points": 0}, 183 {"text": "Doppelte Einträge bleiben erhalten.", "points": 0} 184 ]}, 185 { "domain": "PY-COLLECTIONS", "type": "radio", "text": "Wofür ist `collections.Counter` gut?", "options": [ 186 {"text": "Häufigkeiten von Elementen zählen.", "points": 1}, 187 {"text": "Dateien öffnen.", "points": 0}, 188 {"text": "Zahlen runden.", "points": 0} 189 ]}, 190 { "domain": "PY-COLLECTIONS", "type": "checkbox", "text": "Welche Datenstrukturen sind unveränderlich (immutable)?", "options": [ 191 {"text": "tuple", "points": 1}, 192 {"text": "frozenset", "points": 1}, 193 {"text": "list", "points": 0}, 194 {"text": "dict", "points": 0} 195 ]}, 196 { "domain": "PY-COLLECTIONS", "type": "radio", "text": "Was bewirkt `defaultdict(list)`?", "options": [ 197 {"text": "Fehlende Schlüssel bekommen automatisch eine leere Liste.", "points": 1}, 198 {"text": "Fehlende Schlüssel werfen KeyError.", "points": 0}, 199 {"text": "Es ist identisch zu dict.", "points": 0} 200 ]}, 201 202 { "domain": "PY-DATETIME", "type": "radio", "text": "Welche Bibliothek liefert Zeitzonen ohne Zusatzpakete (ab 3.9)?", "options": [ 203 {"text": "zoneinfo", "points": 1}, 204 {"text": "pytz", "points": 0}, 205 {"text": "tzlocal", "points": 0} 206 ]}, 207 { "domain": "PY-DATETIME", "type": "checkbox", "text": "Welche Aussagen zu datetime sind korrekt?", "options": [ 208 {"text": "Naive datetimes haben keine Zeitzone.", "points": 1}, 209 {"text": "Aware datetimes tragen eine tzinfo.", "points": 1}, 210 {"text": "UTC ist eine sinnvolle interne Normalform.", "points": 1}, 211 {"text": "time.time() ist immer monoton.", "points": 0} 212 ]}, 213 { "domain": "PY-DATETIME", "type": "radio", "text": "Wie misst man kurze Zeitdauern zuverlässig?", "options": [ 214 {"text": "time.perf_counter()", "points": 1}, 215 {"text": "datetime.now()", "points": 0}, 216 {"text": "time.sleep()", "points": 0} 217 ]}, 218 { "domain": "PY-DATETIME", "type": "checkbox", "text": "Welche Formate sind für Logs/Datenaustausch robust?", "options": [ 219 {"text": "ISO-8601 (z. B. 2025-10-20T15:00:00Z)", "points": 1}, 220 {"text": "Ortsabhängige Datumsstrings", "points": 0}, 221 {"text": "Unix-Timestamps (Sekunden seit Epoch)", "points": 1}, 222 {"text": "Freitext ohne Norm", "points": 0} 223 ]}, 224 { "domain": "PY-DATETIME", "type": "radio", "text": "Wie erstellt man 'jetzt in UTC' als aware datetime?", "options": [ 225 {"text": "datetime.now(timezone.utc)", "points": 1}, 226 {"text": "datetime.utcnow()", "points": 0}, 227 {"text": "datetime.now()", "points": 0} 228 ]}, 229 230 { "domain": "PY-NAMESPACES", "type": "radio", "text": "Wofür steht LEGB?", "options": [ 231 {"text": "Local, Enclosing, Global, Builtins (Namensauflösung)", "points": 1}, 232 {"text": "Library, Env, Git, Bytecode", "points": 0}, 233 {"text": "List, Eval, Generator, Bytes", "points": 0} 234 ]}, 235 { "domain": "PY-NAMESPACES", "type": "checkbox", "text": "Welche Aussagen zu `global`/`nonlocal` sind korrekt?", "options": [ 236 {"text": "`global` bezieht sich auf das Modul-Namespace.", "points": 1}, 237 {"text": "`nonlocal` greift auf die nächst-äußere Funktionsvariable zu.", "points": 1}, 238 {"text": "`nonlocal` kann globale Variablen binden.", "points": 0}, 239 {"text": "`global` wirkt nur in Klassenmethoden.", "points": 0} 240 ]}, 241 { "domain": "PY-NAMESPACES", "type": "radio", "text": "Wie markiert man interne Modulnamen konventionell?", "options": [ 242 {"text": "Mit führendem Unterstrich: _name", "points": 1}, 243 {"text": "Mit Großschreibung", "points": 0}, 244 {"text": "Mit @internal", "points": 0} 245 ]}, 246 { "domain": "PY-NAMESPACES", "type": "checkbox", "text": "Welche sind gute Praktiken, um Namenskonflikte zu vermeiden?", "options": [ 247 {"text": "Aussagekräftige Modul-/Paketnamen", "points": 1}, 248 {"text": "Kein `from x import *`", "points": 1}, 249 {"text": "Alles in eine Datei schreiben", "points": 0}, 250 {"text": "Konstante Namen in UPPER_CASE", "points": 1} 251 ]}, 252 { "domain": "PY-NAMESPACES", "type": "radio", "text": "Wozu dient `__all__`?", "options": [ 253 {"text": "Steuert Exporte bei `from mod import *`.", "points": 1}, 254 {"text": "Definiert die Versionsnummer.", "points": 0}, 255 {"text": "Erzeugt automatisch Docstrings.", "points": 0} 256 ]}, 257 258 { "domain": "PY-RECURSION", "type": "radio", "text": "Warum ist tiefe Rekursion in Python oft problematisch?", "options": [ 259 {"text": "Begrenzter Call-Stack; keine Tail-Call-Optimierung.", "points": 1}, 260 {"text": "Python erlaubt Rekursion gar nicht.", "points": 0}, 261 {"text": "Listen unterstützen keine Rekursion.", "points": 0} 262 ]}, 263 { "domain": "PY-RECURSION", "type": "checkbox", "text": "Wie kann man eine rekursive Lösung vereinfachen?", "options": [ 264 {"text": "Iterative Variante mit explizitem Stack/Queue.", "points": 1}, 265 {"text": "Memoization bei sich wiederholenden Teilproblemen.", "points": 1}, 266 {"text": "Zufällige Abbrüche einbauen.", "points": 0}, 267 {"text": "Auf unendliche Rekursion hoffen.", "points": 0} 268 ]}, 269 { "domain": "PY-RECURSION", "type": "radio", "text": "Welche Variante ist für Fibonacci(n) effizienter?", "options": [ 270 {"text": "Iterativ oder mit lru_cache.", "points": 1}, 271 {"text": "Naiv rekursiv ohne Cache.", "points": 0}, 272 {"text": "Mit eval auf Zeichenketten.", "points": 0} 273 ]}, 274 { "domain": "PY-RECURSION", "type": "checkbox", "text": "Welche Probleme treten bei Graph-Rekursion auf?", "options": [ 275 {"text": "Zyklen → unendliche Rekursion ohne Visited-Set.", "points": 1}, 276 {"text": "Große Tiefe → RecursionError.", "points": 1}, 277 {"text": "Listen sind nicht iterierbar.", "points": 0}, 278 {"text": "Rekursion verbraucht keinen Stack.", "points": 0} 279 ]}, 280 { "domain": "PY-RECURSION", "type": "radio", "text": "Wie traversiert man einen Baum iterativ (DFS)?", "options": [ 281 {"text": "Mit explizitem Stack (list) und Schleife.", "points": 1}, 282 {"text": "Mit globaler Variablen.", "points": 0}, 283 {"text": "Gar nicht möglich.", "points": 0} 284 ]}, 285 286 { "domain": "PY-MODULES", "type": "radio", "text": "Was macht Code auf Modul-Top-Level beim Import?", "options": [ 287 {"text": "Er wird genau einmal ausgeführt (Initialisierung).", "points": 1}, 288 {"text": "Er wird jedes Mal neu ausgeführt.", "points": 0}, 289 {"text": "Er wird ignoriert.", "points": 0} 290 ]}, 291 { "domain": "PY-MODULES", "type": "checkbox", "text": "Welche Aussagen zu Imports sind korrekt?", "options": [ 292 {"text": "Module werden in sys.modules gecacht.", "points": 1}, 293 {"text": "Relative Importe funktionieren nur innerhalb von Paketen.", "points": 1}, 294 {"text": "`if __name__ == '__main__':` trennt Skript-Start vom Import.", "points": 1}, 295 {"text": "`from x import *` ist immer empfohlen.", "points": 0} 296 ]}, 297 { "domain": "PY-MODULES", "type": "radio", "text": "Wie bindet man einen Paket-Logger korrekt?", "options": [ 298 {"text": "logging.getLogger(__name__)", "points": 1}, 299 {"text": "print statt Logging", "points": 0}, 300 {"text": "logging.getLogger()", "points": 0} 301 ]}, 302 { "domain": "PY-MODULES", "type": "checkbox", "text": "Welche Werkzeuge sind für Paket-Ressourcen nützlich?", "options": [ 303 {"text": "importlib.resources", "points": 1}, 304 {"text": "pkgutil", "points": 1}, 305 {"text": "random.resources", "points": 0}, 306 {"text": "sys.path zum Lesen von Dateien", "points": 0} 307 ]}, 308 { "domain": "PY-MODULES", "type": "radio", "text": "Wie führt man ein Modul als Skript aus?", "options": [ 309 {"text": "python -m paket.modul", "points": 1}, 310 {"text": "python paket/modul", "points": 0}, 311 {"text": "run paket.modul", "points": 0} 312 ]}, 313 314 { "domain": "PY-OOP", "type": "radio", "text": "Wofür steht `@property`?", "options": [ 315 {"text": "Liest/erzeugt berechnete Attribute wie ein Getter.", "points": 1}, 316 {"text": "Markiert eine Klassenmethode.", "points": 0}, 317 {"text": "Erstellt automatisch __init__.", "points": 0} 318 ]}, 319 { "domain": "PY-OOP", "type": "checkbox", "text": "Welche Aussagen zu Klassenmethoden/staticmethods stimmen?", "options": [ 320 {"text": "@classmethod erhält die Klasse als erstes Argument (cls).", "points": 1}, 321 {"text": "@staticmethod erhält weder self noch cls.", "points": 1}, 322 {"text": "@classmethod und @staticmethod sind identisch.", "points": 0}, 323 {"text": "Beide können auf der Klasse aufgerufen werden.", "points": 1} 324 ]}, 325 { "domain": "PY-OOP", "type": "radio", "text": "Wie vergleicht man zwei Instanzen auf Wertgleichheit korrekt?", "options": [ 326 {"text": "__eq__ implementieren und ggf. __hash__ bei Immutables.", "points": 1}, 327 {"text": "Nur __repr__ implementieren.", "points": 0}, 328 {"text": "Nur __hash__ implementieren.", "points": 0} 329 ]}, 330 { "domain": "PY-OOP", "type": "checkbox", "text": "Welche Methoden sind für Containerklassen nützlich?", "options": [ 331 {"text": "__len__", "points": 1}, 332 {"text": "__iter__", "points": 1}, 333 {"text": "__getitem__", "points": 1}, 334 {"text": "__cmp__", "points": 0} 335 ]}, 336 { "domain": "PY-OOP", "type": "radio", "text": "Welche Basisklasse nutzt man für abstrakte Klassen?", "options": [ 337 {"text": "abc.ABC", "points": 1}, 338 {"text": "typing.Any", "points": 0}, 339 {"text": "objectonly", "points": 0} 340 ]}, 341 342 { "domain": "PY-EXCEPTIONS", "type": "radio", "text": "Wie fängt man eine konkrete Exception?", "options": [ 343 {"text": "try: ... except ValueError:", "points": 1}, 344 {"text": "try: ... catch ValueError:", "points": 0}, 345 {"text": "except(ValueError): ... ohne try", "points": 0} 346 ]}, 347 { "domain": "PY-EXCEPTIONS", "type": "checkbox", "text": "Welche Blöcke kann ein try-Statement enthalten?", "options": [ 348 {"text": "except", "points": 1}, 349 {"text": "else", "points": 1}, 350 {"text": "finally", "points": 1}, 351 {"text": "then", "points": 0} 352 ]}, 353 { "domain": "PY-EXCEPTIONS", "type": "radio", "text": "Wie wirft man dieselbe Exception im except-Block erneut?", "options": [ 354 {"text": "raise (ohne Argumente)", "points": 1}, 355 {"text": "return e", "points": 0}, 356 {"text": "throw e", "points": 0} 357 ]}, 358 { "domain": "PY-EXCEPTIONS", "type": "checkbox", "text": "Welche sind gute Praktiken beim Fehler-Handling?", "options": [ 359 {"text": "Nur spezifische Exceptions abfangen.", "points": 1}, 360 {"text": "Kontext mit `raise ... from e` erhalten.", "points": 1}, 361 {"text": "Bare `except:` nur in seltenen Ausnahmefällen.", "points": 1}, 362 {"text": "Alle Fehler still ignorieren.", "points": 0} 363 ]}, 364 { "domain": "PY-EXCEPTIONS", "type": "radio", "text": "Welche Ausnahmen sind KEINE Unterklassen von Exception?", "options": [ 365 {"text": "SystemExit/KeyboardInterrupt/GeneratorExit", "points": 1}, 366 {"text": "RuntimeError", "points": 0}, 367 {"text": "ValueError", "points": 0} 368 ]}, 369 370 { "domain": "PY-CNTXTMNGR", "type": "radio", "text": "Wozu dient ein Context Manager (with)?", "options": [ 371 {"text": "Ressourcen sicher öffnen/schließen (z. B. Dateien).", "points": 1}, 372 {"text": "Nur fürs Logging.", "points": 0}, 373 {"text": "Nur in Tests nutzbar.", "points": 0} 374 ]}, 375 { "domain": "PY-CNTXTMNGR", "type": "checkbox", "text": "Welche bereitgestellten Kontexte sind nützlich?", "options": [ 376 {"text": "contextlib.suppress", "points": 1}, 377 {"text": "contextlib.redirect_stdout", "points": 1}, 378 {"text": "contextlib.ExitStack", "points": 1}, 379 {"text": "contextlib.magic", "points": 0} 380 ]}, 381 { "domain": "PY-CNTXTMNGR", "type": "radio", "text": "Welche Methoden implementiert ein eigener Context-Manager (Klasse)?", "options": [ 382 {"text": "__enter__ und __exit__", "points": 1}, 383 {"text": "__open__ und __close__", "points": 0}, 384 {"text": "__start__ und __stop__", "points": 0} 385 ]}, 386 { "domain": "PY-CNTXTMNGR", "type": "checkbox", "text": "Welche Aussage zu `__exit__(exc_type, exc, tb)` stimmt?", "options": [ 387 {"text": "True zurückgeben unterdrückt die Exception.", "points": 1}, 388 {"text": "False/None propagiert die Exception weiter.", "points": 1}, 389 {"text": "Die Parameter sind immer None, auch bei Fehlern.", "points": 0}, 390 {"text": "__exit__ wird nicht aufgerufen, wenn ein Fehler auftritt.", "points": 0} 391 ]}, 392 { "domain": "PY-CNTXTMNGR", "type": "radio", "text": "Wie kombiniert man mehrere Kontexte flexibel?", "options": [ 393 {"text": "Mit contextlib.ExitStack()", "points": 1}, 394 {"text": "Gar nicht möglich.", "points": 0}, 395 {"text": "Mit globalen Variablen.", "points": 0} 396 ]}, 397 398 { "domain": "PY-REGEXP", "type": "radio", "text": "Welche Stdlib nutzt man für reguläre Ausdrücke?", "options": [ 399 {"text": "re", "points": 1}, 400 {"text": "regexlib", "points": 0}, 401 {"text": "rx", "points": 0} 402 ]}, 403 { "domain": "PY-REGEXP", "type": "checkbox", "text": "Welche Konstrukte gehören zur re-Syntax?", "options": [ 404 {"text": "Gruppen: (...)", "points": 1}, 405 {"text": "Benannte Gruppen: (?P<name>...)", "points": 1}, 406 {"text": "Quantifizierer: *, +, ?", "points": 1}, 407 {"text": "Operator := für Teilmuster", "points": 0} 408 ]}, 409 { "domain": "PY-REGEXP", "type": "radio", "text": "Wie findet man alle nicht-überlappenden Treffer in einem Text?", "options": [ 410 {"text": "re.findall(pattern, text)", "points": 1}, 411 {"text": "re.match überall aufrufen", "points": 0}, 412 {"text": "re.compile(pattern).group()", "points": 0} 413 ]}, 414 { "domain": "PY-REGEXP", "type": "checkbox", "text": "Welche Flags sind korrekt zugeordnet?", "options": [ 415 {"text": "re.IGNORECASE: Groß/Kleinschreibung ignorieren", "points": 1}, 416 {"text": "re.MULTILINE: ^ und $ auf Zeilen anwenden", "points": 1}, 417 {"text": "re.DOTALL: Punkt matcht auch Newlines", "points": 1}, 418 {"text": "re.GLOBAL: existiert in re", "points": 0} 419 ]}, 420 { "domain": "PY-REGEXP", "type": "radio", "text": "Wie ersetzt man Texte mit einer Funktion je Match?", "options": [ 421 {"text": "re.sub(pattern, func, text)", "points": 1}, 422 {"text": "re.replace(pattern, func, text)", "points": 0}, 423 {"text": "text.replace_re(func)", "points": 0} 424 ]}, 425 426 { "domain": "PY-PARALLEL", "type": "radio", "text": "Wofür eignen sich Threads in CPython besonders?", "options": [ 427 {"text": "I/O-gebundene Aufgaben (z. B. Netzwerk, Dateien).", "points": 1}, 428 {"text": "Schwere CPU-Berechnungen parallelisieren.", "points": 0}, 429 {"text": "Ohne Synchronisation immer korrekt.", "points": 0} 430 ]}, 431 { "domain": "PY-PARALLEL", "type": "checkbox", "text": "Welche Bibliotheken gehören zur Stdlib für Parallelität/Konkurrenz?", "options": [ 432 {"text": "threading", "points": 1}, 433 {"text": "multiprocessing", "points": 1}, 434 {"text": "concurrent.futures", "points": 1}, 435 {"text": "numpy.threads", "points": 0} 436 ]}, 437 { "domain": "PY-PARALLEL", "type": "radio", "text": "Welche Klasse erstellt einen Prozess-Pool einfach?", "options": [ 438 {"text": "concurrent.futures.ProcessPoolExecutor", "points": 1}, 439 {"text": "threading.Pool", "points": 0}, 440 {"text": "os.pool", "points": 0} 441 ]}, 442 { "domain": "PY-PARALLEL", "type": "checkbox", "text": "Welche Aussagen zu Locks sind richtig?", "options": [ 443 {"text": "Locks schützen kritische Abschnitte.", "points": 1}, 444 {"text": "Ohne Locks kann es zu Race Conditions kommen.", "points": 1}, 445 {"text": "Mit GIL sind Locks nie nötig.", "points": 0}, 446 {"text": "RLock erlaubt denselben Thread mehrfach zu sperren.", "points": 1} 447 ]}, 448 { "domain": "PY-PARALLEL", "type": "radio", "text": "Was beschreibt `asyncio` korrekt?", "options": [ 449 {"text": "Kooperative Nebenläufigkeit per Event-Loop und await.", "points": 1}, 450 {"text": "Preemptives Scheduling wie OS-Threads.", "points": 0}, 451 {"text": "Automatische Mehrkern-Parallelisierung.", "points": 0} 452 ]}, 453 454 { "domain": "PY-NETWORK", "type": "radio", "text": "Welche Bibliothek ist der Low-Level-Baustein für TCP/UDP?", "options": [ 455 {"text": "socket", "points": 1}, 456 {"text": "email", "points": 0}, 457 {"text": "http.client", "points": 0} 458 ]}, 459 { "domain": "PY-NETWORK", "type": "checkbox", "text": "Welche Aussagen zu HTTP in der Stdlib sind korrekt?", "options": [ 460 {"text": "urllib.request kann einfache HTTP-Requests senden.", "points": 1}, 461 {"text": "http.client ist sehr Low-Level.", "points": 1}, 462 {"text": "ssl unterstützt TLS-Konfiguration.", "points": 1}, 463 {"text": "email ist für HTTP-Requests gedacht.", "points": 0} 464 ]}, 465 { "domain": "PY-NETWORK", "type": "radio", "text": "Wie setzt man ein Timeout für einen Socket?", "options": [ 466 {"text": "sock.settimeout(seconds)", "points": 1}, 467 {"text": "socket.timeout=True", "points": 0}, 468 {"text": "sock.timeout(seconds)", "points": 0} 469 ]}, 470 { "domain": "PY-NETWORK", "type": "checkbox", "text": "Welche Helfer gibt es zum Arbeiten mit URLs?", "options": [ 471 {"text": "urllib.parse (urlsplit, urlencode, parse_qs)", "points": 1}, 472 {"text": "json.parse_url", "points": 0}, 473 {"text": "urllib.robotparser für robots.txt", "points": 1}, 474 {"text": "re.url", "points": 0} 475 ]}, 476 { "domain": "PY-NETWORK", "type": "radio", "text": "Wie validiert man standardmäßig TLS-Zertifikate mit urllib?", "options": [ 477 {"text": "Standard-Handler prüfen Zertifikate, wenn CA-Store korrekt ist.", "points": 1}, 478 {"text": "urllib kann TLS nie prüfen.", "points": 0}, 479 {"text": "Man muss verify=False setzen.", "points": 0} 480 ]}, 481 482 { "domain": "PY-PACKAGING", "type": "radio", "text": "Welche Datei beschreibt moderne Projekt-Metadaten/Build-System?", "options": [ 483 {"text": "pyproject.toml", "points": 1}, 484 {"text": "requirements.txt", "points": 0}, 485 {"text": "Pipfile.lock", "points": 0} 486 ]}, 487 { "domain": "PY-PACKAGING", "type": "checkbox", "text": "Welche Felder sind typisch in pyproject.toml (PEP 621)?", "options": [ 488 {"text": "project.name / version / dependencies", "points": 1}, 489 {"text": "project.scripts für CLI-Einträge", "points": 1}, 490 {"text": "build-system (Backend/Requires)", "points": 1}, 491 {"text": "kernel.modules", "points": 0} 492 ]}, 493 { "domain": "PY-PACKAGING", "type": "radio", "text": "Was ist ein Wheel (.whl)?", "options": [ 494 {"text": "Vorgebaute Distributionsdatei für schnelle Installation.", "points": 1}, 495 {"text": "Quellpaket (sdist).", "points": 0}, 496 {"text": "Virtuelle Umgebung.", "points": 0} 497 ]}, 498 { "domain": "PY-PACKAGING", "type": "checkbox", "text": "Welche Tools helfen beim Bauen/Veröffentlichen?", "options": [ 499 {"text": "`python -m build` (extern)", "points": 1}, 500 {"text": "`pip install .`", "points": 1}, 501 {"text": "twine für Uploads", "points": 1}, 502 {"text": "setup.py zwingend in jedem Projekt", "points": 0} 503 ]}, 504 { "domain": "PY-PACKAGING", "type": "radio", "text": "Wo definiert man ein Konsolen-Skript ohne setup.py?", "options": [ 505 {"text": "In pyproject.toml unter project.scripts (Backend-abhängig).", "points": 1}, 506 {"text": "In requirements.txt", "points": 0}, 507 {"text": "In README.md", "points": 0} 508 ]} 6 { 7 "domain": "PY_ARITHM", 8 "qtype": "single_choice", 9 "text": "Welcher Operator liefert in Python 3 die Ganzzahl-Division (Abrunden Richtung −∞)?", 10 "options": [ 11 { 12 "text": "//", 13 "points": 1, 14 "id": "A" 15 }, 16 { 17 "text": "/", 18 "points": 0, 19 "id": "B" 20 }, 21 { 22 "text": "%", 23 "points": 0, 24 "id": "C" 25 } 26 ] 27 }, 28 { 29 "domain": "PY_ARITHM", 30 "qtype": "multiple_choice", 31 "text": "Welche Aussagen über Ganzzahlen (int) in Python sind korrekt?", 32 "options": [ 33 { 34 "text": "int kann beliebig groß werden (nur durch RAM begrenzt).", 35 "points": 1, 36 "id": "A" 37 }, 38 { 39 "text": "int hat fest 64-Bit.", 40 "points": 0, 41 "id": "B" 42 }, 43 { 44 "text": "Negatives Ergebnis bei -7 // 3 ist -3.", 45 "points": 1, 46 "id": "C" 47 }, 48 { 49 "text": "Bei 7 % 3 ist der Rest 1.", 50 "points": 1, 51 "id": "D" 52 } 53 ] 54 }, 55 { 56 "domain": "PY_ARITHM", 57 "qtype": "single_choice", 58 "text": "Welches Ergebnis hat 2 ** 3 ** 2?", 59 "options": [ 60 { 61 "text": "512 (weil Exponentiation rechtsassoziativ ist).", 62 "points": 1, 63 "id": "A" 64 }, 65 { 66 "text": "64", 67 "points": 0, 68 "id": "B" 69 }, 70 { 71 "text": "Fehler, da unklar.", 72 "points": 0, 73 "id": "C" 74 } 75 ] 76 }, 77 { 78 "domain": "PY_ARITHM", 79 "qtype": "multiple_choice", 80 "text": "Welche Funktionen helfen beim Runden/Umwandeln?", 81 "options": [ 82 { 83 "text": "round(x)", 84 "points": 1, 85 "id": "A" 86 }, 87 { 88 "text": "int(x)", 89 "points": 1, 90 "id": "B" 91 }, 92 { 93 "text": "float.to_int(x)", 94 "points": 0, 95 "id": "C" 96 }, 97 { 98 "text": "abs(x)", 99 "points": 1, 100 "id": "D" 101 } 102 ] 103 }, 104 { 105 "domain": "PY_ARITHM", 106 "qtype": "single_choice", 107 "text": "Was liefert 0.1 + 0.2 in Python typischerweise?", 108 "options": [ 109 { 110 "text": "Einen Wert nahe 0.3 wegen Binär-Float, daher vergleicht man besser mit math.isclose.", 111 "points": 1, 112 "id": "A" 113 }, 114 { 115 "text": "Genau 0.3, immer.", 116 "points": 0, 117 "id": "B" 118 }, 119 { 120 "text": "Eine Exception.", 121 "points": 0, 122 "id": "C" 123 } 124 ] 125 }, 126 { 127 "domain": "PY_CNTRLSTRCT", 128 "qtype": "single_choice", 129 "text": "Wie wiederholt man Code, bis eine Bedingung erfüllt ist?", 130 "options": [ 131 { 132 "text": "while-Schleife", 133 "points": 1, 134 "id": "A" 135 }, 136 { 137 "text": "if-Anweisung", 138 "points": 0, 139 "id": "B" 140 }, 141 { 142 "text": "import-Anweisung", 143 "points": 0, 144 "id": "C" 145 } 146 ] 147 }, 148 { 149 "domain": "PY_CNTRLSTRCT", 150 "qtype": "multiple_choice", 151 "text": "Welche Schlüsselwörter passen zu Schleifen?", 152 "options": [ 153 { 154 "text": "break (Schleife vorzeitig beenden)", 155 "points": 1, 156 "id": "A" 157 }, 158 { 159 "text": "continue (nächste Iteration)", 160 "points": 1, 161 "id": "B" 162 }, 163 { 164 "text": "nextloop (existiert nicht)", 165 "points": 0, 166 "id": "C" 167 }, 168 { 169 "text": "else: läuft, wenn kein break passierte", 170 "points": 1, 171 "id": "D" 172 } 173 ] 174 }, 175 { 176 "domain": "PY_CNTRLSTRCT", 177 "qtype": "single_choice", 178 "text": "Wie iteriert man idiomatisch über Index und Element einer Liste?", 179 "options": [ 180 { 181 "text": "for i, x in enumerate(items):", 182 "points": 1, 183 "id": "A" 184 }, 185 { 186 "text": "for i in items: x = items[i]", 187 "points": 0, 188 "id": "B" 189 }, 190 { 191 "text": "for (i, x) in range(items):", 192 "points": 0, 193 "id": "C" 194 } 195 ] 196 }, 197 { 198 "domain": "PY_CNTRLSTRCT", 199 "qtype": "multiple_choice", 200 "text": "Welche Teile gehören zur if-Syntax?", 201 "options": [ 202 { 203 "text": "if …:", 204 "points": 1, 205 "id": "A" 206 }, 207 { 208 "text": "elif …:", 209 "points": 1, 210 "id": "B" 211 }, 212 { 213 "text": "elseif …:", 214 "points": 0, 215 "id": "C" 216 }, 217 { 218 "text": "else:", 219 "points": 1, 220 "id": "D" 221 } 222 ] 223 }, 224 { 225 "domain": "PY_CNTRLSTRCT", 226 "qtype": "single_choice", 227 "text": "Wofür steht der Walrus-Operator (:=)?", 228 "options": [ 229 { 230 "text": "Zuweisung in einem Ausdruck (z. B. while (n := input()) != '')", 231 "points": 1, 232 "id": "A" 233 }, 234 { 235 "text": "Vergleich auf Gleichheit", 236 "points": 0, 237 "id": "B" 238 }, 239 { 240 "text": "Exponentiation", 241 "points": 0, 242 "id": "C" 243 } 244 ] 245 }, 246 { 247 "domain": "PY_FILES", 248 "qtype": "single_choice", 249 "text": "Wie öffnet man eine Textdatei sicher zum Lesen in UTF-8?", 250 "options": [ 251 { 252 "text": "open(path, 'r', encoding='utf-8')", 253 "points": 1, 254 "id": "A" 255 }, 256 { 257 "text": "open(path)", 258 "points": 0, 259 "id": "B" 260 }, 261 { 262 "text": "open('utf-8', path)", 263 "points": 0, 264 "id": "C" 265 } 266 ] 267 }, 268 { 269 "domain": "PY_FILES", 270 "qtype": "multiple_choice", 271 "text": "Welche Vorteile hat der with-Block beim Datei-I/O?", 272 "options": [ 273 { 274 "text": "Automatisches Schließen der Datei.", 275 "points": 1, 276 "id": "A" 277 }, 278 { 279 "text": "Weniger Risiko für Ressourcen-Leaks.", 280 "points": 1, 281 "id": "B" 282 }, 283 { 284 "text": "Er macht das Lesen schneller per se.", 285 "points": 0, 286 "id": "C" 287 }, 288 { 289 "text": "Kompakterer Code.", 290 "points": 1, 291 "id": "D" 292 } 293 ] 294 }, 295 { 296 "domain": "PY_FILES", 297 "qtype": "single_choice", 298 "text": "Welcher Modus legt eine neue Datei an und wirft Fehler, falls sie existiert?", 299 "options": [ 300 { 301 "text": "'x'", 302 "points": 1, 303 "id": "A" 304 }, 305 { 306 "text": "'w'", 307 "points": 0, 308 "id": "B" 309 }, 310 { 311 "text": "'a'", 312 "points": 0, 313 "id": "C" 314 } 315 ] 316 }, 317 { 318 "domain": "PY_FILES", 319 "qtype": "multiple_choice", 320 "text": "Welche Bibliotheken sind für Pfade hilfreich?", 321 "options": [ 322 { 323 "text": "pathlib.Path", 324 "points": 1, 325 "id": "A" 326 }, 327 { 328 "text": "os.path", 329 "points": 1, 330 "id": "B" 331 }, 332 { 333 "text": "sys.path zum Dateischreiben", 334 "points": 0, 335 "id": "C" 336 }, 337 { 338 "text": "glob für Dateimuster", 339 "points": 1, 340 "id": "D" 341 } 342 ] 343 }, 344 { 345 "domain": "PY_FILES", 346 "qtype": "single_choice", 347 "text": "Wie liest man große Dateien speicherschonend Zeile für Zeile?", 348 "options": [ 349 { 350 "text": "for line in f:", 351 "points": 1, 352 "id": "A" 353 }, 354 { 355 "text": "lines = f.readlines()", 356 "points": 0, 357 "id": "B" 358 }, 359 { 360 "text": "text = f.read()", 361 "points": 0, 362 "id": "C" 363 } 364 ] 365 }, 366 { 367 "domain": "PY_TYPES", 368 "qtype": "single_choice", 369 "text": "Wozu dienen Typannotationen in Python?", 370 "options": [ 371 { 372 "text": "Sie unterstützen Tools/IDE/Typchecker; zur Laufzeit optional.", 373 "points": 1, 374 "id": "A" 375 }, 376 { 377 "text": "Sie erzwingen strikt die Typen zur Laufzeit.", 378 "points": 0, 379 "id": "B" 380 }, 381 { 382 "text": "Sie ersetzen Docstrings.", 383 "points": 0, 384 "id": "C" 385 } 386 ] 387 }, 388 { 389 "domain": "PY_TYPES", 390 "qtype": "multiple_choice", 391 "text": "Welche sind gültige Typannotationen (Beispiele ab Python 3.9)?", 392 "options": [ 393 { 394 "text": "list[int]", 395 "points": 1, 396 "id": "A" 397 }, 398 { 399 "text": "dict[str, int]", 400 "points": 1, 401 "id": "B" 402 }, 403 { 404 "text": "Optional[int] bzw. int | None", 405 "points": 1, 406 "id": "C" 407 }, 408 { 409 "text": "tuple<int>", 410 "points": 0, 411 "id": "D" 412 } 413 ] 414 }, 415 { 416 "domain": "PY_TYPES", 417 "qtype": "single_choice", 418 "text": "Welche Annotation beschreibt 'Funktion erhält int und gibt bool zurück'?", 419 "options": [ 420 { 421 "text": "Callable[[int], bool]", 422 "points": 1, 423 "id": "A" 424 }, 425 { 426 "text": "function(int)->bool", 427 "points": 0, 428 "id": "B" 429 }, 430 { 431 "text": "call[int->bool]", 432 "points": 0, 433 "id": "C" 434 } 435 ] 436 }, 437 { 438 "domain": "PY_TYPES", 439 "qtype": "multiple_choice", 440 "text": "Welche Stdlib-Hilfen definieren einfache Datenobjekte?", 441 "options": [ 442 { 443 "text": "dataclasses.dataclass", 444 "points": 1, 445 "id": "A" 446 }, 447 { 448 "text": "typing.TypedDict", 449 "points": 1, 450 "id": "B" 451 }, 452 { 453 "text": "collections.namedtuple", 454 "points": 1, 455 "id": "C" 456 }, 457 { 458 "text": "functools.singledispatch", 459 "points": 0, 460 "id": "D" 461 } 462 ] 463 }, 464 { 465 "domain": "PY_TYPES", 466 "qtype": "single_choice", 467 "text": "Was bedeutet `Any`?", 468 "options": [ 469 { 470 "text": "Beliebiger Typ (Typchecker schränkt kaum ein).", 471 "points": 1, 472 "id": "A" 473 }, 474 { 475 "text": "Nur Ganzzahlen erlaubt.", 476 "points": 0, 477 "id": "B" 478 }, 479 { 480 "text": "Ist identisch zu `object` in jeder Hinsicht.", 481 "points": 0, 482 "id": "C" 483 } 484 ] 485 }, 486 { 487 "domain": "PY_STREAMS", 488 "qtype": "single_choice", 489 "text": "Was gibt eine Listen-Comprehension typischerweise zurück?", 490 "options": [ 491 { 492 "text": "Eine neue Liste", 493 "points": 1, 494 "id": "A" 495 }, 496 { 497 "text": "Einen Generator", 498 "points": 0, 499 "id": "B" 500 }, 501 { 502 "text": "Ein Tupel", 503 "points": 0, 504 "id": "C" 505 } 506 ] 507 }, 508 { 509 "domain": "PY_STREAMS", 510 "qtype": "multiple_choice", 511 "text": "Welche sind Lazy-Konstrukte?", 512 "options": [ 513 { 514 "text": "(x for x in seq) (Generator-Expression)", 515 "points": 1, 516 "id": "A" 517 }, 518 { 519 "text": "map(func, seq)", 520 "points": 1, 521 "id": "B" 522 }, 523 { 524 "text": "filter(func, seq)", 525 "points": 1, 526 "id": "C" 527 }, 528 { 529 "text": "[x for x in seq]", 530 "points": 0, 531 "id": "D" 532 } 533 ] 534 }, 535 { 536 "domain": "PY_STREAMS", 537 "qtype": "single_choice", 538 "text": "Wofür steht `yield` in einer Funktion?", 539 "options": [ 540 { 541 "text": "Es macht die Funktion zu einem Generator (liefert Werte schrittweise).", 542 "points": 1, 543 "id": "A" 544 }, 545 { 546 "text": "Es beendet das Programm.", 547 "points": 0, 548 "id": "B" 549 }, 550 { 551 "text": "Es importiert ein Modul.", 552 "points": 0, 553 "id": "C" 554 } 555 ] 556 }, 557 { 558 "domain": "PY_STREAMS", 559 "qtype": "multiple_choice", 560 "text": "Welche Tools sind nützlich für Iterables?", 561 "options": [ 562 { 563 "text": "itertools (z. B. chain, islice)", 564 "points": 1, 565 "id": "A" 566 }, 567 { 568 "text": "enumerate", 569 "points": 1, 570 "id": "B" 571 }, 572 { 573 "text": "re (Regex) als Iteratorersatz", 574 "points": 0, 575 "id": "C" 576 }, 577 { 578 "text": "sum zur Konkatenation von Listen", 579 "points": 0, 580 "id": "D" 581 } 582 ] 583 }, 584 { 585 "domain": "PY_STREAMS", 586 "qtype": "single_choice", 587 "text": "Welcher Ausdruck ist am speicherschonendsten?", 588 "options": [ 589 { 590 "text": "sum(x for x in range(1_000_000))", 591 "points": 1, 592 "id": "A" 593 }, 594 { 595 "text": "sum([x for x in range(1_000_000)])", 596 "points": 0, 597 "id": "B" 598 }, 599 { 600 "text": "list(range(1_000_000))", 601 "points": 0, 602 "id": "C" 603 } 604 ] 605 }, 606 { 607 "domain": "PY_FUNCTOOLS", 608 "qtype": "single_choice", 609 "text": "Wozu dient `functools.lru_cache`?", 610 "options": [ 611 { 612 "text": "Ergebnisse teurer Funktionsaufrufe zwischenspeichern.", 613 "points": 1, 614 "id": "A" 615 }, 616 { 617 "text": "Dateien schneller lesen.", 618 "points": 0, 619 "id": "B" 620 }, 621 { 622 "text": "Asynchronität bereitstellen.", 623 "points": 0, 624 "id": "C" 625 } 626 ] 627 }, 628 { 629 "domain": "PY_FUNCTOOLS", 630 "qtype": "multiple_choice", 631 "text": "Welche Aussagen zu `partial` stimmen?", 632 "options": [ 633 { 634 "text": "`partial` bindet einige Argumente vor.", 635 "points": 1, 636 "id": "A" 637 }, 638 { 639 "text": "Die neue Funktion verhält sich wie die ursprüngliche mit den gebundenen Werten.", 640 "points": 1, 641 "id": "B" 642 }, 643 { 644 "text": "`partial` ersetzt kwargs vollständig.", 645 "points": 0, 646 "id": "C" 647 }, 648 { 649 "text": "`partial` ist ein Decorator für Klassen.", 650 "points": 0, 651 "id": "D" 652 } 653 ] 654 }, 655 { 656 "domain": "PY_FUNCTOOLS", 657 "qtype": "single_choice", 658 "text": "Was macht `@wraps` beim Schreiben eigener Decorators?", 659 "options": [ 660 { 661 "text": "Überträgt Metadaten (Name, Docstring) auf die Wrapper-Funktion.", 662 "points": 1, 663 "id": "A" 664 }, 665 { 666 "text": "Beschleunigt die Funktion automatisch.", 667 "points": 0, 668 "id": "B" 669 }, 670 { 671 "text": "Erstellt eine Klasse.", 672 "points": 0, 673 "id": "C" 674 } 675 ] 676 }, 677 { 678 "domain": "PY_FUNCTOOLS", 679 "qtype": "multiple_choice", 680 "text": "Welche Aussagen zu `singledispatch` sind korrekt?", 681 "options": [ 682 { 683 "text": "Erzeugt generische Funktionen je nach Typ des ersten Arguments.", 684 "points": 1, 685 "id": "A" 686 }, 687 { 688 "text": "Weitere Varianten werden mit @<func>.register definiert.", 689 "points": 1, 690 "id": "B" 691 }, 692 { 693 "text": "Funktioniert nicht mit Klassenmethoden/Methoden-Workarounds.", 694 "points": 0, 695 "id": "C" 696 }, 697 { 698 "text": "Ist Teil der Stdlib.", 699 "points": 1, 700 "id": "D" 701 } 702 ] 703 }, 704 { 705 "domain": "PY_FUNCTOOLS", 706 "qtype": "single_choice", 707 "text": "Wie leert man den Cache einer mit `lru_cache` dekorierten Funktion?", 708 "options": [ 709 { 710 "text": "funktion.cache_clear()", 711 "points": 1, 712 "id": "A" 713 }, 714 { 715 "text": "del funktion", 716 "points": 0, 717 "id": "B" 718 }, 719 { 720 "text": "Cache leert sich automatisch bei Programmstart nicht", 721 "points": 0, 722 "id": "C" 723 } 724 ] 725 }, 726 { 727 "domain": "PY_COLLECTIONS", 728 "qtype": "single_choice", 729 "text": "Welche Struktur eignet sich für schnelle Anhänge am Ende?", 730 "options": [ 731 { 732 "text": "list.append", 733 "points": 1, 734 "id": "A" 735 }, 736 { 737 "text": "tuple +=", 738 "points": 0, 739 "id": "B" 740 }, 741 { 742 "text": "str +=", 743 "points": 0, 744 "id": "C" 745 } 746 ] 747 }, 748 { 749 "domain": "PY_COLLECTIONS", 750 "qtype": "multiple_choice", 751 "text": "Welche Aussagen zu Mengen (set) sind korrekt?", 752 "options": [ 753 { 754 "text": "Speichern einzigartige, hashbare Elemente.", 755 "points": 1, 756 "id": "A" 757 }, 758 { 759 "text": "Unterstützen Vereinigungs-/Schnittmengen-Operationen.", 760 "points": 1, 761 "id": "B" 762 }, 763 { 764 "text": "Erhalten die Einfügereihenfolge garantiert vor 3.7.", 765 "points": 0, 766 "id": "C" 767 }, 768 { 769 "text": "Doppelte Einträge bleiben erhalten.", 770 "points": 0, 771 "id": "D" 772 } 773 ] 774 }, 775 { 776 "domain": "PY_COLLECTIONS", 777 "qtype": "single_choice", 778 "text": "Wofür ist `collections.Counter` gut?", 779 "options": [ 780 { 781 "text": "Häufigkeiten von Elementen zählen.", 782 "points": 1, 783 "id": "A" 784 }, 785 { 786 "text": "Dateien öffnen.", 787 "points": 0, 788 "id": "B" 789 }, 790 { 791 "text": "Zahlen runden.", 792 "points": 0, 793 "id": "C" 794 } 795 ] 796 }, 797 { 798 "domain": "PY_COLLECTIONS", 799 "qtype": "multiple_choice", 800 "text": "Welche Datenstrukturen sind unveränderlich (immutable)?", 801 "options": [ 802 { 803 "text": "tuple", 804 "points": 1, 805 "id": "A" 806 }, 807 { 808 "text": "frozenset", 809 "points": 1, 810 "id": "B" 811 }, 812 { 813 "text": "list", 814 "points": 0, 815 "id": "C" 816 }, 817 { 818 "text": "dict", 819 "points": 0, 820 "id": "D" 821 } 822 ] 823 }, 824 { 825 "domain": "PY_COLLECTIONS", 826 "qtype": "single_choice", 827 "text": "Was bewirkt `defaultdict(list)`?", 828 "options": [ 829 { 830 "text": "Fehlende Schlüssel bekommen automatisch eine leere Liste.", 831 "points": 1, 832 "id": "A" 833 }, 834 { 835 "text": "Fehlende Schlüssel werfen KeyError.", 836 "points": 0, 837 "id": "B" 838 }, 839 { 840 "text": "Es ist identisch zu dict.", 841 "points": 0, 842 "id": "C" 843 } 844 ] 845 }, 846 { 847 "domain": "PY_DATETIME", 848 "qtype": "single_choice", 849 "text": "Welche Bibliothek liefert Zeitzonen ohne Zusatzpakete (ab 3.9)?", 850 "options": [ 851 { 852 "text": "zoneinfo", 853 "points": 1, 854 "id": "A" 855 }, 856 { 857 "text": "pytz", 858 "points": 0, 859 "id": "B" 860 }, 861 { 862 "text": "tzlocal", 863 "points": 0, 864 "id": "C" 865 } 866 ] 867 }, 868 { 869 "domain": "PY_DATETIME", 870 "qtype": "multiple_choice", 871 "text": "Welche Aussagen zu datetime sind korrekt?", 872 "options": [ 873 { 874 "text": "Naive datetimes haben keine Zeitzone.", 875 "points": 1, 876 "id": "A" 877 }, 878 { 879 "text": "Aware datetimes tragen eine tzinfo.", 880 "points": 1, 881 "id": "B" 882 }, 883 { 884 "text": "UTC ist eine sinnvolle interne Normalform.", 885 "points": 1, 886 "id": "C" 887 }, 888 { 889 "text": "time.time() ist immer monoton.", 890 "points": 0, 891 "id": "D" 892 } 893 ] 894 }, 895 { 896 "domain": "PY_DATETIME", 897 "qtype": "single_choice", 898 "text": "Wie misst man kurze Zeitdauern zuverlässig?", 899 "options": [ 900 { 901 "text": "time.perf_counter()", 902 "points": 1, 903 "id": "A" 904 }, 905 { 906 "text": "datetime.now()", 907 "points": 0, 908 "id": "B" 909 }, 910 { 911 "text": "time.sleep()", 912 "points": 0, 913 "id": "C" 914 } 915 ] 916 }, 917 { 918 "domain": "PY_DATETIME", 919 "qtype": "multiple_choice", 920 "text": "Welche Formate sind für Logs/Datenaustausch robust?", 921 "options": [ 922 { 923 "text": "ISO-8601 (z. B. 2025-10-20T15:00:00Z)", 924 "points": 1, 925 "id": "A" 926 }, 927 { 928 "text": "Ortsabhängige Datumsstrings", 929 "points": 0, 930 "id": "B" 931 }, 932 { 933 "text": "Unix-Timestamps (Sekunden seit Epoch)", 934 "points": 1, 935 "id": "C" 936 }, 937 { 938 "text": "Freitext ohne Norm", 939 "points": 0, 940 "id": "D" 941 } 942 ] 943 }, 944 { 945 "domain": "PY_DATETIME", 946 "qtype": "single_choice", 947 "text": "Wie erstellt man 'jetzt in UTC' als aware datetime?", 948 "options": [ 949 { 950 "text": "datetime.now(timezone.utc)", 951 "points": 1, 952 "id": "A" 953 }, 954 { 955 "text": "datetime.utcnow()", 956 "points": 0, 957 "id": "B" 958 }, 959 { 960 "text": "datetime.now()", 961 "points": 0, 962 "id": "C" 963 } 964 ] 965 }, 966 { 967 "domain": "PY_NAMESPACES", 968 "qtype": "single_choice", 969 "text": "Wofür steht LEGB?", 970 "options": [ 971 { 972 "text": "Local, Enclosing, Global, Builtins (Namensauflösung)", 973 "points": 1, 974 "id": "A" 975 }, 976 { 977 "text": "Library, Env, Git, Bytecode", 978 "points": 0, 979 "id": "B" 980 }, 981 { 982 "text": "List, Eval, Generator, Bytes", 983 "points": 0, 984 "id": "C" 985 } 986 ] 987 }, 988 { 989 "domain": "PY_NAMESPACES", 990 "qtype": "multiple_choice", 991 "text": "Welche Aussagen zu `global`/`nonlocal` sind korrekt?", 992 "options": [ 993 { 994 "text": "`global` bezieht sich auf das Modul-Namespace.", 995 "points": 1, 996 "id": "A" 997 }, 998 { 999 "text": "`nonlocal` greift auf die nächst-äußere Funktionsvariable zu.", 1000 "points": 1, 1001 "id": "B" 1002 }, 1003 { 1004 "text": "`nonlocal` kann globale Variablen binden.", 1005 "points": 0, 1006 "id": "C" 1007 }, 1008 { 1009 "text": "`global` wirkt nur in Klassenmethoden.", 1010 "points": 0, 1011 "id": "D" 1012 } 1013 ] 1014 }, 1015 { 1016 "domain": "PY_NAMESPACES", 1017 "qtype": "single_choice", 1018 "text": "Wie markiert man interne Modulnamen konventionell?", 1019 "options": [ 1020 { 1021 "text": "Mit führendem Unterstrich: _name", 1022 "points": 1, 1023 "id": "A" 1024 }, 1025 { 1026 "text": "Mit Großschreibung", 1027 "points": 0, 1028 "id": "B" 1029 }, 1030 { 1031 "text": "Mit @internal", 1032 "points": 0, 1033 "id": "C" 1034 } 1035 ] 1036 }, 1037 { 1038 "domain": "PY_NAMESPACES", 1039 "qtype": "multiple_choice", 1040 "text": "Welche sind gute Praktiken, um Namenskonflikte zu vermeiden?", 1041 "options": [ 1042 { 1043 "text": "Aussagekräftige Modul-/Paketnamen", 1044 "points": 1, 1045 "id": "A" 1046 }, 1047 { 1048 "text": "Kein `from x import *`", 1049 "points": 1, 1050 "id": "B" 1051 }, 1052 { 1053 "text": "Alles in eine Datei schreiben", 1054 "points": 0, 1055 "id": "C" 1056 }, 1057 { 1058 "text": "Konstante Namen in UPPER_CASE", 1059 "points": 1, 1060 "id": "D" 1061 } 1062 ] 1063 }, 1064 { 1065 "domain": "PY_NAMESPACES", 1066 "qtype": "single_choice", 1067 "text": "Wozu dient `__all__`?", 1068 "options": [ 1069 { 1070 "text": "Steuert Exporte bei `from mod import *`.", 1071 "points": 1, 1072 "id": "A" 1073 }, 1074 { 1075 "text": "Definiert die Versionsnummer.", 1076 "points": 0, 1077 "id": "B" 1078 }, 1079 { 1080 "text": "Erzeugt automatisch Docstrings.", 1081 "points": 0, 1082 "id": "C" 1083 } 1084 ] 1085 }, 1086 { 1087 "domain": "PY_RECURSION", 1088 "qtype": "single_choice", 1089 "text": "Warum ist tiefe Rekursion in Python oft problematisch?", 1090 "options": [ 1091 { 1092 "text": "Begrenzter Call-Stack; keine Tail-Call-Optimierung.", 1093 "points": 1, 1094 "id": "A" 1095 }, 1096 { 1097 "text": "Python erlaubt Rekursion gar nicht.", 1098 "points": 0, 1099 "id": "B" 1100 }, 1101 { 1102 "text": "Listen unterstützen keine Rekursion.", 1103 "points": 0, 1104 "id": "C" 1105 } 1106 ] 1107 }, 1108 { 1109 "domain": "PY_RECURSION", 1110 "qtype": "multiple_choice", 1111 "text": "Wie kann man eine rekursive Lösung vereinfachen?", 1112 "options": [ 1113 { 1114 "text": "Iterative Variante mit explizitem Stack/Queue.", 1115 "points": 1, 1116 "id": "A" 1117 }, 1118 { 1119 "text": "Memoization bei sich wiederholenden Teilproblemen.", 1120 "points": 1, 1121 "id": "B" 1122 }, 1123 { 1124 "text": "Zufällige Abbrüche einbauen.", 1125 "points": 0, 1126 "id": "C" 1127 }, 1128 { 1129 "text": "Auf unendliche Rekursion hoffen.", 1130 "points": 0, 1131 "id": "D" 1132 } 1133 ] 1134 }, 1135 { 1136 "domain": "PY_RECURSION", 1137 "qtype": "single_choice", 1138 "text": "Welche Variante ist für Fibonacci(n) effizienter?", 1139 "options": [ 1140 { 1141 "text": "Iterativ oder mit lru_cache.", 1142 "points": 1, 1143 "id": "A" 1144 }, 1145 { 1146 "text": "Naiv rekursiv ohne Cache.", 1147 "points": 0, 1148 "id": "B" 1149 }, 1150 { 1151 "text": "Mit eval auf Zeichenketten.", 1152 "points": 0, 1153 "id": "C" 1154 } 1155 ] 1156 }, 1157 { 1158 "domain": "PY_RECURSION", 1159 "qtype": "multiple_choice", 1160 "text": "Welche Probleme treten bei Graph-Rekursion auf?", 1161 "options": [ 1162 { 1163 "text": "Zyklen → unendliche Rekursion ohne Visited-Set.", 1164 "points": 1, 1165 "id": "A" 1166 }, 1167 { 1168 "text": "Große Tiefe → RecursionError.", 1169 "points": 1, 1170 "id": "B" 1171 }, 1172 { 1173 "text": "Listen sind nicht iterierbar.", 1174 "points": 0, 1175 "id": "C" 1176 }, 1177 { 1178 "text": "Rekursion verbraucht keinen Stack.", 1179 "points": 0, 1180 "id": "D" 1181 } 1182 ] 1183 }, 1184 { 1185 "domain": "PY_RECURSION", 1186 "qtype": "single_choice", 1187 "text": "Wie traversiert man einen Baum iterativ (DFS)?", 1188 "options": [ 1189 { 1190 "text": "Mit explizitem Stack (list) und Schleife.", 1191 "points": 1, 1192 "id": "A" 1193 }, 1194 { 1195 "text": "Mit globaler Variablen.", 1196 "points": 0, 1197 "id": "B" 1198 }, 1199 { 1200 "text": "Gar nicht möglich.", 1201 "points": 0, 1202 "id": "C" 1203 } 1204 ] 1205 }, 1206 { 1207 "domain": "PY_MODULES", 1208 "qtype": "single_choice", 1209 "text": "Was macht Code auf Modul-Top-Level beim Import?", 1210 "options": [ 1211 { 1212 "text": "Er wird genau einmal ausgeführt (Initialisierung).", 1213 "points": 1, 1214 "id": "A" 1215 }, 1216 { 1217 "text": "Er wird jedes Mal neu ausgeführt.", 1218 "points": 0, 1219 "id": "B" 1220 }, 1221 { 1222 "text": "Er wird ignoriert.", 1223 "points": 0, 1224 "id": "C" 1225 } 1226 ] 1227 }, 1228 { 1229 "domain": "PY_MODULES", 1230 "qtype": "multiple_choice", 1231 "text": "Welche Aussagen zu Imports sind korrekt?", 1232 "options": [ 1233 { 1234 "text": "Module werden in sys.modules gecacht.", 1235 "points": 1, 1236 "id": "A" 1237 }, 1238 { 1239 "text": "Relative Importe funktionieren nur innerhalb von Paketen.", 1240 "points": 1, 1241 "id": "B" 1242 }, 1243 { 1244 "text": "`if __name__ == '__main__':` trennt Skript-Start vom Import.", 1245 "points": 1, 1246 "id": "C" 1247 }, 1248 { 1249 "text": "`from x import *` ist immer empfohlen.", 1250 "points": 0, 1251 "id": "D" 1252 } 1253 ] 1254 }, 1255 { 1256 "domain": "PY_MODULES", 1257 "qtype": "single_choice", 1258 "text": "Wie bindet man einen Paket-Logger korrekt?", 1259 "options": [ 1260 { 1261 "text": "logging.getLogger(__name__)", 1262 "points": 1, 1263 "id": "A" 1264 }, 1265 { 1266 "text": "print statt Logging", 1267 "points": 0, 1268 "id": "B" 1269 }, 1270 { 1271 "text": "logging.getLogger()", 1272 "points": 0, 1273 "id": "C" 1274 } 1275 ] 1276 }, 1277 { 1278 "domain": "PY_MODULES", 1279 "qtype": "multiple_choice", 1280 "text": "Welche Werkzeuge sind für Paket-Ressourcen nützlich?", 1281 "options": [ 1282 { 1283 "text": "importlib.resources", 1284 "points": 1, 1285 "id": "A" 1286 }, 1287 { 1288 "text": "pkgutil", 1289 "points": 1, 1290 "id": "B" 1291 }, 1292 { 1293 "text": "random.resources", 1294 "points": 0, 1295 "id": "C" 1296 }, 1297 { 1298 "text": "sys.path zum Lesen von Dateien", 1299 "points": 0, 1300 "id": "D" 1301 } 1302 ] 1303 }, 1304 { 1305 "domain": "PY_MODULES", 1306 "qtype": "single_choice", 1307 "text": "Wie führt man ein Modul als Skript aus?", 1308 "options": [ 1309 { 1310 "text": "python -m paket.modul", 1311 "points": 1, 1312 "id": "A" 1313 }, 1314 { 1315 "text": "python paket/modul", 1316 "points": 0, 1317 "id": "B" 1318 }, 1319 { 1320 "text": "run paket.modul", 1321 "points": 0, 1322 "id": "C" 1323 } 1324 ] 1325 }, 1326 { 1327 "domain": "PY_OOP", 1328 "qtype": "single_choice", 1329 "text": "Wofür steht `@property`?", 1330 "options": [ 1331 { 1332 "text": "Liest/erzeugt berechnete Attribute wie ein Getter.", 1333 "points": 1, 1334 "id": "A" 1335 }, 1336 { 1337 "text": "Markiert eine Klassenmethode.", 1338 "points": 0, 1339 "id": "B" 1340 }, 1341 { 1342 "text": "Erstellt automatisch __init__.", 1343 "points": 0, 1344 "id": "C" 1345 } 1346 ] 1347 }, 1348 { 1349 "domain": "PY_OOP", 1350 "qtype": "multiple_choice", 1351 "text": "Welche Aussagen zu Klassenmethoden/staticmethods stimmen?", 1352 "options": [ 1353 { 1354 "text": "@classmethod erhält die Klasse als erstes Argument (cls).", 1355 "points": 1, 1356 "id": "A" 1357 }, 1358 { 1359 "text": "@staticmethod erhält weder self noch cls.", 1360 "points": 1, 1361 "id": "B" 1362 }, 1363 { 1364 "text": "@classmethod und @staticmethod sind identisch.", 1365 "points": 0, 1366 "id": "C" 1367 }, 1368 { 1369 "text": "Beide können auf der Klasse aufgerufen werden.", 1370 "points": 1, 1371 "id": "D" 1372 } 1373 ] 1374 }, 1375 { 1376 "domain": "PY_OOP", 1377 "qtype": "single_choice", 1378 "text": "Wie vergleicht man zwei Instanzen auf Wertgleichheit korrekt?", 1379 "options": [ 1380 { 1381 "text": "__eq__ implementieren und ggf. __hash__ bei Immutables.", 1382 "points": 1, 1383 "id": "A" 1384 }, 1385 { 1386 "text": "Nur __repr__ implementieren.", 1387 "points": 0, 1388 "id": "B" 1389 }, 1390 { 1391 "text": "Nur __hash__ implementieren.", 1392 "points": 0, 1393 "id": "C" 1394 } 1395 ] 1396 }, 1397 { 1398 "domain": "PY_OOP", 1399 "qtype": "multiple_choice", 1400 "text": "Welche Methoden sind für Containerklassen nützlich?", 1401 "options": [ 1402 { 1403 "text": "__len__", 1404 "points": 1, 1405 "id": "A" 1406 }, 1407 { 1408 "text": "__iter__", 1409 "points": 1, 1410 "id": "B" 1411 }, 1412 { 1413 "text": "__getitem__", 1414 "points": 1, 1415 "id": "C" 1416 }, 1417 { 1418 "text": "__cmp__", 1419 "points": 0, 1420 "id": "D" 1421 } 1422 ] 1423 }, 1424 { 1425 "domain": "PY_OOP", 1426 "qtype": "single_choice", 1427 "text": "Welche Basisklasse nutzt man für abstrakte Klassen?", 1428 "options": [ 1429 { 1430 "text": "abc.ABC", 1431 "points": 1, 1432 "id": "A" 1433 }, 1434 { 1435 "text": "typing.Any", 1436 "points": 0, 1437 "id": "B" 1438 }, 1439 { 1440 "text": "objectonly", 1441 "points": 0, 1442 "id": "C" 1443 } 1444 ] 1445 }, 1446 { 1447 "domain": "PY_EXCEPTIONS", 1448 "qtype": "single_choice", 1449 "text": "Wie fängt man eine konkrete Exception?", 1450 "options": [ 1451 { 1452 "text": "try: ... except ValueError:", 1453 "points": 1, 1454 "id": "A" 1455 }, 1456 { 1457 "text": "try: ... catch ValueError:", 1458 "points": 0, 1459 "id": "B" 1460 }, 1461 { 1462 "text": "except(ValueError): ... ohne try", 1463 "points": 0, 1464 "id": "C" 1465 } 1466 ] 1467 }, 1468 { 1469 "domain": "PY_EXCEPTIONS", 1470 "qtype": "multiple_choice", 1471 "text": "Welche Blöcke kann ein try-Statement enthalten?", 1472 "options": [ 1473 { 1474 "text": "except", 1475 "points": 1, 1476 "id": "A" 1477 }, 1478 { 1479 "text": "else", 1480 "points": 1, 1481 "id": "B" 1482 }, 1483 { 1484 "text": "finally", 1485 "points": 1, 1486 "id": "C" 1487 }, 1488 { 1489 "text": "then", 1490 "points": 0, 1491 "id": "D" 1492 } 1493 ] 1494 }, 1495 { 1496 "domain": "PY_EXCEPTIONS", 1497 "qtype": "single_choice", 1498 "text": "Wie wirft man dieselbe Exception im except-Block erneut?", 1499 "options": [ 1500 { 1501 "text": "raise (ohne Argumente)", 1502 "points": 1, 1503 "id": "A" 1504 }, 1505 { 1506 "text": "return e", 1507 "points": 0, 1508 "id": "B" 1509 }, 1510 { 1511 "text": "throw e", 1512 "points": 0, 1513 "id": "C" 1514 } 1515 ] 1516 }, 1517 { 1518 "domain": "PY_EXCEPTIONS", 1519 "qtype": "multiple_choice", 1520 "text": "Welche sind gute Praktiken beim Fehler-Handling?", 1521 "options": [ 1522 { 1523 "text": "Nur spezifische Exceptions abfangen.", 1524 "points": 1, 1525 "id": "A" 1526 }, 1527 { 1528 "text": "Kontext mit `raise ... from e` erhalten.", 1529 "points": 1, 1530 "id": "B" 1531 }, 1532 { 1533 "text": "Bare `except:` nur in seltenen Ausnahmefällen.", 1534 "points": 1, 1535 "id": "C" 1536 }, 1537 { 1538 "text": "Alle Fehler still ignorieren.", 1539 "points": 0, 1540 "id": "D" 1541 } 1542 ] 1543 }, 1544 { 1545 "domain": "PY_EXCEPTIONS", 1546 "qtype": "single_choice", 1547 "text": "Welche Ausnahmen sind KEINE Unterklassen von Exception?", 1548 "options": [ 1549 { 1550 "text": "SystemExit/KeyboardInterrupt/GeneratorExit", 1551 "points": 1, 1552 "id": "A" 1553 }, 1554 { 1555 "text": "RuntimeError", 1556 "points": 0, 1557 "id": "B" 1558 }, 1559 { 1560 "text": "ValueError", 1561 "points": 0, 1562 "id": "C" 1563 } 1564 ] 1565 }, 1566 { 1567 "domain": "PY_CNTXTMNGR", 1568 "qtype": "single_choice", 1569 "text": "Wozu dient ein Context Manager (with)?", 1570 "options": [ 1571 { 1572 "text": "Ressourcen sicher öffnen/schließen (z. B. Dateien).", 1573 "points": 1, 1574 "id": "A" 1575 }, 1576 { 1577 "text": "Nur fürs Logging.", 1578 "points": 0, 1579 "id": "B" 1580 }, 1581 { 1582 "text": "Nur in Tests nutzbar.", 1583 "points": 0, 1584 "id": "C" 1585 } 1586 ] 1587 }, 1588 { 1589 "domain": "PY_CNTXTMNGR", 1590 "qtype": "multiple_choice", 1591 "text": "Welche bereitgestellten Kontexte sind nützlich?", 1592 "options": [ 1593 { 1594 "text": "contextlib.suppress", 1595 "points": 1, 1596 "id": "A" 1597 }, 1598 { 1599 "text": "contextlib.redirect_stdout", 1600 "points": 1, 1601 "id": "B" 1602 }, 1603 { 1604 "text": "contextlib.ExitStack", 1605 "points": 1, 1606 "id": "C" 1607 }, 1608 { 1609 "text": "contextlib.magic", 1610 "points": 0, 1611 "id": "D" 1612 } 1613 ] 1614 }, 1615 { 1616 "domain": "PY_CNTXTMNGR", 1617 "qtype": "single_choice", 1618 "text": "Welche Methoden implementiert ein eigener Context-Manager (Klasse)?", 1619 "options": [ 1620 { 1621 "text": "__enter__ und __exit__", 1622 "points": 1, 1623 "id": "A" 1624 }, 1625 { 1626 "text": "__open__ und __close__", 1627 "points": 0, 1628 "id": "B" 1629 }, 1630 { 1631 "text": "__start__ und __stop__", 1632 "points": 0, 1633 "id": "C" 1634 } 1635 ] 1636 }, 1637 { 1638 "domain": "PY_CNTXTMNGR", 1639 "qtype": "multiple_choice", 1640 "text": "Welche Aussage zu `__exit__(exc_type, exc, tb)` stimmt?", 1641 "options": [ 1642 { 1643 "text": "True zurückgeben unterdrückt die Exception.", 1644 "points": 1, 1645 "id": "A" 1646 }, 1647 { 1648 "text": "False/None propagiert die Exception weiter.", 1649 "points": 1, 1650 "id": "B" 1651 }, 1652 { 1653 "text": "Die Parameter sind immer None, auch bei Fehlern.", 1654 "points": 0, 1655 "id": "C" 1656 }, 1657 { 1658 "text": "__exit__ wird nicht aufgerufen, wenn ein Fehler auftritt.", 1659 "points": 0, 1660 "id": "D" 1661 } 1662 ] 1663 }, 1664 { 1665 "domain": "PY_CNTXTMNGR", 1666 "qtype": "single_choice", 1667 "text": "Wie kombiniert man mehrere Kontexte flexibel?", 1668 "options": [ 1669 { 1670 "text": "Mit contextlib.ExitStack()", 1671 "points": 1, 1672 "id": "A" 1673 }, 1674 { 1675 "text": "Gar nicht möglich.", 1676 "points": 0, 1677 "id": "B" 1678 }, 1679 { 1680 "text": "Mit globalen Variablen.", 1681 "points": 0, 1682 "id": "C" 1683 } 1684 ] 1685 }, 1686 { 1687 "domain": "PY_REGEXP", 1688 "qtype": "single_choice", 1689 "text": "Welche Stdlib nutzt man für reguläre Ausdrücke?", 1690 "options": [ 1691 { 1692 "text": "re", 1693 "points": 1, 1694 "id": "A" 1695 }, 1696 { 1697 "text": "regexlib", 1698 "points": 0, 1699 "id": "B" 1700 }, 1701 { 1702 "text": "rx", 1703 "points": 0, 1704 "id": "C" 1705 } 1706 ] 1707 }, 1708 { 1709 "domain": "PY_REGEXP", 1710 "qtype": "multiple_choice", 1711 "text": "Welche Konstrukte gehören zur re-Syntax?", 1712 "options": [ 1713 { 1714 "text": "Gruppen: (...)", 1715 "points": 1, 1716 "id": "A" 1717 }, 1718 { 1719 "text": "Benannte Gruppen: (?P<name>...)", 1720 "points": 1, 1721 "id": "B" 1722 }, 1723 { 1724 "text": "Quantifizierer: *, +, ?", 1725 "points": 1, 1726 "id": "C" 1727 }, 1728 { 1729 "text": "Operator := für Teilmuster", 1730 "points": 0, 1731 "id": "D" 1732 } 1733 ] 1734 }, 1735 { 1736 "domain": "PY_REGEXP", 1737 "qtype": "single_choice", 1738 "text": "Wie findet man alle nicht-überlappenden Treffer in einem Text?", 1739 "options": [ 1740 { 1741 "text": "re.findall(pattern, text)", 1742 "points": 1, 1743 "id": "A" 1744 }, 1745 { 1746 "text": "re.match überall aufrufen", 1747 "points": 0, 1748 "id": "B" 1749 }, 1750 { 1751 "text": "re.compile(pattern).group()", 1752 "points": 0, 1753 "id": "C" 1754 } 1755 ] 1756 }, 1757 { 1758 "domain": "PY_REGEXP", 1759 "qtype": "multiple_choice", 1760 "text": "Welche Flags sind korrekt zugeordnet?", 1761 "options": [ 1762 { 1763 "text": "re.IGNORECASE: Groß/Kleinschreibung ignorieren", 1764 "points": 1, 1765 "id": "A" 1766 }, 1767 { 1768 "text": "re.MULTILINE: ^ und $ auf Zeilen anwenden", 1769 "points": 1, 1770 "id": "B" 1771 }, 1772 { 1773 "text": "re.DOTALL: Punkt matcht auch Newlines", 1774 "points": 1, 1775 "id": "C" 1776 }, 1777 { 1778 "text": "re.GLOBAL: existiert in re", 1779 "points": 0, 1780 "id": "D" 1781 } 1782 ] 1783 }, 1784 { 1785 "domain": "PY_REGEXP", 1786 "qtype": "single_choice", 1787 "text": "Wie ersetzt man Texte mit einer Funktion je Match?", 1788 "options": [ 1789 { 1790 "text": "re.sub(pattern, func, text)", 1791 "points": 1, 1792 "id": "A" 1793 }, 1794 { 1795 "text": "re.replace(pattern, func, text)", 1796 "points": 0, 1797 "id": "B" 1798 }, 1799 { 1800 "text": "text.replace_re(func)", 1801 "points": 0, 1802 "id": "C" 1803 } 1804 ] 1805 }, 1806 { 1807 "domain": "PY_PARALLEL", 1808 "qtype": "single_choice", 1809 "text": "Wofür eignen sich Threads in CPython besonders?", 1810 "options": [ 1811 { 1812 "text": "I/O-gebundene Aufgaben (z. B. Netzwerk, Dateien).", 1813 "points": 1, 1814 "id": "A" 1815 }, 1816 { 1817 "text": "Schwere CPU-Berechnungen parallelisieren.", 1818 "points": 0, 1819 "id": "B" 1820 }, 1821 { 1822 "text": "Ohne Synchronisation immer korrekt.", 1823 "points": 0, 1824 "id": "C" 1825 } 1826 ] 1827 }, 1828 { 1829 "domain": "PY_PARALLEL", 1830 "qtype": "multiple_choice", 1831 "text": "Welche Bibliotheken gehören zur Stdlib für Parallelität/Konkurrenz?", 1832 "options": [ 1833 { 1834 "text": "threading", 1835 "points": 1, 1836 "id": "A" 1837 }, 1838 { 1839 "text": "multiprocessing", 1840 "points": 1, 1841 "id": "B" 1842 }, 1843 { 1844 "text": "concurrent.futures", 1845 "points": 1, 1846 "id": "C" 1847 }, 1848 { 1849 "text": "numpy.threads", 1850 "points": 0, 1851 "id": "D" 1852 } 1853 ] 1854 }, 1855 { 1856 "domain": "PY_PARALLEL", 1857 "qtype": "single_choice", 1858 "text": "Welche Klasse erstellt einen Prozess-Pool einfach?", 1859 "options": [ 1860 { 1861 "text": "concurrent.futures.ProcessPoolExecutor", 1862 "points": 1, 1863 "id": "A" 1864 }, 1865 { 1866 "text": "threading.Pool", 1867 "points": 0, 1868 "id": "B" 1869 }, 1870 { 1871 "text": "os.pool", 1872 "points": 0, 1873 "id": "C" 1874 } 1875 ] 1876 }, 1877 { 1878 "domain": "PY_PARALLEL", 1879 "qtype": "multiple_choice", 1880 "text": "Welche Aussagen zu Locks sind richtig?", 1881 "options": [ 1882 { 1883 "text": "Locks schützen kritische Abschnitte.", 1884 "points": 1, 1885 "id": "A" 1886 }, 1887 { 1888 "text": "Ohne Locks kann es zu Race Conditions kommen.", 1889 "points": 1, 1890 "id": "B" 1891 }, 1892 { 1893 "text": "Mit GIL sind Locks nie nötig.", 1894 "points": 0, 1895 "id": "C" 1896 }, 1897 { 1898 "text": "RLock erlaubt denselben Thread mehrfach zu sperren.", 1899 "points": 1, 1900 "id": "D" 1901 } 1902 ] 1903 }, 1904 { 1905 "domain": "PY_PARALLEL", 1906 "qtype": "single_choice", 1907 "text": "Was beschreibt `asyncio` korrekt?", 1908 "options": [ 1909 { 1910 "text": "Kooperative Nebenläufigkeit per Event-Loop und await.", 1911 "points": 1, 1912 "id": "A" 1913 }, 1914 { 1915 "text": "Preemptives Scheduling wie OS-Threads.", 1916 "points": 0, 1917 "id": "B" 1918 }, 1919 { 1920 "text": "Automatische Mehrkern-Parallelisierung.", 1921 "points": 0, 1922 "id": "C" 1923 } 1924 ] 1925 }, 1926 { 1927 "domain": "PY_NETWORK", 1928 "qtype": "single_choice", 1929 "text": "Welche Bibliothek ist der Low-Level-Baustein für TCP/UDP?", 1930 "options": [ 1931 { 1932 "text": "socket", 1933 "points": 1, 1934 "id": "A" 1935 }, 1936 { 1937 "text": "email", 1938 "points": 0, 1939 "id": "B" 1940 }, 1941 { 1942 "text": "http.client", 1943 "points": 0, 1944 "id": "C" 1945 } 1946 ] 1947 }, 1948 { 1949 "domain": "PY_NETWORK", 1950 "qtype": "multiple_choice", 1951 "text": "Welche Aussagen zu HTTP in der Stdlib sind korrekt?", 1952 "options": [ 1953 { 1954 "text": "urllib.request kann einfache HTTP-Requests senden.", 1955 "points": 1, 1956 "id": "A" 1957 }, 1958 { 1959 "text": "http.client ist sehr Low-Level.", 1960 "points": 1, 1961 "id": "B" 1962 }, 1963 { 1964 "text": "ssl unterstützt TLS-Konfiguration.", 1965 "points": 1, 1966 "id": "C" 1967 }, 1968 { 1969 "text": "email ist für HTTP-Requests gedacht.", 1970 "points": 0, 1971 "id": "D" 1972 } 1973 ] 1974 }, 1975 { 1976 "domain": "PY_NETWORK", 1977 "qtype": "single_choice", 1978 "text": "Wie setzt man ein Timeout für einen Socket?", 1979 "options": [ 1980 { 1981 "text": "sock.settimeout(seconds)", 1982 "points": 1, 1983 "id": "A" 1984 }, 1985 { 1986 "text": "socket.timeout=True", 1987 "points": 0, 1988 "id": "B" 1989 }, 1990 { 1991 "text": "sock.timeout(seconds)", 1992 "points": 0, 1993 "id": "C" 1994 } 1995 ] 1996 }, 1997 { 1998 "domain": "PY_NETWORK", 1999 "qtype": "multiple_choice", 2000 "text": "Welche Helfer gibt es zum Arbeiten mit URLs?", 2001 "options": [ 2002 { 2003 "text": "urllib.parse (urlsplit, urlencode, parse_qs)", 2004 "points": 1, 2005 "id": "A" 2006 }, 2007 { 2008 "text": "json.parse_url", 2009 "points": 0, 2010 "id": "B" 2011 }, 2012 { 2013 "text": "urllib.robotparser für robots.txt", 2014 "points": 1, 2015 "id": "C" 2016 }, 2017 { 2018 "text": "re.url", 2019 "points": 0, 2020 "id": "D" 2021 } 2022 ] 2023 }, 2024 { 2025 "domain": "PY_NETWORK", 2026 "qtype": "single_choice", 2027 "text": "Wie validiert man standardmäßig TLS-Zertifikate mit urllib?", 2028 "options": [ 2029 { 2030 "text": "Standard-Handler prüfen Zertifikate, wenn CA-Store korrekt ist.", 2031 "points": 1, 2032 "id": "A" 2033 }, 2034 { 2035 "text": "urllib kann TLS nie prüfen.", 2036 "points": 0, 2037 "id": "B" 2038 }, 2039 { 2040 "text": "Man muss verify=False setzen.", 2041 "points": 0, 2042 "id": "C" 2043 } 2044 ] 2045 }, 2046 { 2047 "domain": "PY_PACKAGING", 2048 "qtype": "single_choice", 2049 "text": "Welche Datei beschreibt moderne Projekt-Metadaten/Build-System?", 2050 "options": [ 2051 { 2052 "text": "pyproject.toml", 2053 "points": 1, 2054 "id": "A" 2055 }, 2056 { 2057 "text": "requirements.txt", 2058 "points": 0, 2059 "id": "B" 2060 }, 2061 { 2062 "text": "Pipfile.lock", 2063 "points": 0, 2064 "id": "C" 2065 } 2066 ] 2067 }, 2068 { 2069 "domain": "PY_PACKAGING", 2070 "qtype": "multiple_choice", 2071 "text": "Welche Felder sind typisch in pyproject.toml (PEP 621)?", 2072 "options": [ 2073 { 2074 "text": "project.name / version / dependencies", 2075 "points": 1, 2076 "id": "A" 2077 }, 2078 { 2079 "text": "project.scripts für CLI-Einträge", 2080 "points": 1, 2081 "id": "B" 2082 }, 2083 { 2084 "text": "build-system (Backend/Requires)", 2085 "points": 1, 2086 "id": "C" 2087 }, 2088 { 2089 "text": "kernel.modules", 2090 "points": 0, 2091 "id": "D" 2092 } 2093 ] 2094 }, 2095 { 2096 "domain": "PY_PACKAGING", 2097 "qtype": "single_choice", 2098 "text": "Was ist ein Wheel (.whl)?", 2099 "options": [ 2100 { 2101 "text": "Vorgebaute Distributionsdatei für schnelle Installation.", 2102 "points": 1, 2103 "id": "A" 2104 }, 2105 { 2106 "text": "Quellpaket (sdist).", 2107 "points": 0, 2108 "id": "B" 2109 }, 2110 { 2111 "text": "Virtuelle Umgebung.", 2112 "points": 0, 2113 "id": "C" 2114 } 2115 ] 2116 }, 2117 { 2118 "domain": "PY_PACKAGING", 2119 "qtype": "multiple_choice", 2120 "text": "Welche Tools helfen beim Bauen/Veröffentlichen?", 2121 "options": [ 2122 { 2123 "text": "`python -m build` (extern)", 2124 "points": 1, 2125 "id": "A" 2126 }, 2127 { 2128 "text": "`pip install .`", 2129 "points": 1, 2130 "id": "B" 2131 }, 2132 { 2133 "text": "twine für Uploads", 2134 "points": 1, 2135 "id": "C" 2136 }, 2137 { 2138 "text": "setup.py zwingend in jedem Projekt", 2139 "points": 0, 2140 "id": "D" 2141 } 2142 ] 2143 }, 2144 { 2145 "domain": "PY_PACKAGING", 2146 "qtype": "single_choice", 2147 "text": "Wo definiert man ein Konsolen-Skript ohne setup.py?", 2148 "options": [ 2149 { 2150 "text": "In pyproject.toml unter project.scripts (Backend-abhängig).", 2151 "points": 1, 2152 "id": "A" 2153 }, 2154 { 2155 "text": "In requirements.txt", 2156 "points": 0, 2157 "id": "B" 2158 }, 2159 { 2160 "text": "In README.md", 2161 "points": 0, 2162 "id": "C" 2163 } 2164 ] 2165 } 509 2166 ] 510 2167 } -
gui/detail_panel.py
re77bfb3 rfe7d338 68 68 69 69 # ─────────────────────────────────────────────────────────────────────── 70 def update_info(self, question=None, catalog_title=None,70 def refresh_details(self, question=None, catalog_title=None, 71 71 clipboard_action=None, clipboard_count=0): 72 72 """ -
gui/gui.py
re77bfb3 rfe7d338 5 5 from pathlib import Path 6 6 from datetime import datetime 7 from flexoentity import EntityState, EntityType,FlexOID7 from flexoentity import FlexOID 8 8 from builder.exam import Exam 9 from builder.question_factory import question_factory10 9 from builder.question_catalog import QuestionCatalog 11 10 from builder.media_items import NullMediaItem … … 13 12 from builder.exam_manager import ExamManager 14 13 from builder.exam_elements import SingleChoiceQuestion, MultipleChoiceQuestion, InstructionBlock 14 from .menu import AppMenu 15 from .actions_panel import ActionsPanel 16 from .select_panel import SelectPanel 15 17 from .detail_panel import DetailPanel 16 18 from .option_question_editor import OptionQuestionEditorDialog … … 26 28 self.catalog_manager = CatalogManager() 27 29 self.exam_manager = ExamManager() 28 self.title( f"flex-o-grader – [{self.catalog_manager.get_active_title()}]")30 self.title("flex-o-grader") 29 31 30 32 self.geometry("1000x600") 31 33 default_font = font.nametofont("TkDefaultFont") 32 34 default_font.configure(size=12) # increase from default (usually 9–10) 33 # Optional: change family34 # default_font.configure(family="Helvetica")35 35 36 36 # Apply same size to other common named fonts … … 43 43 self.status_var = tk.StringVar(value="No catalog loaded") 44 44 self.recent_actions_var = tk.StringVar(value="") # new right-side text 45 self.current_qtype_var = tk.StringVar(value=" Radio")45 self.current_qtype_var = tk.StringVar(value="single_choice") 46 46 self.target_exam_var = tk.StringVar(value="") 47 47 … … 51 51 self.clipboard_action = None # "copy" 52 52 self.clipboard_source_catalog = None 53 self.c reate_menu()53 self.config(menu=AppMenu(self)) 54 54 self.create_widgets() 55 55 … … 60 60 geometry = self.session.load() 61 61 self.geometry(geometry) 62 self.after(60000, lambda: self.session.save(self.geometry())) 63 self.protocol("WM_DELETE_WINDOW", self.on_quit) 64 self.refresh_all() 65 66 def refresh_all(self): 62 67 self.refresh_catalog_list() 63 68 self.refresh_exam_list() 64 69 self.refresh_question_tree() 65 70 self.refresh_status_bar() 66 67 self.after(60000, lambda: self.session.save(self.geometry())) 68 self.protocol("WM_DELETE_WINDOW", self.on_quit) 69 71 if self.question_tree.selection(): 72 self.detail_panel.refresh_details(self.require_selected_question(), 73 self.require_active_catalog()) 70 74 @property 71 75 def active_catalog(self): … … 80 84 return active 81 85 82 def create_menu(self):83 menubar = tk.Menu(self)84 filemenu = tk.Menu(menubar, tearoff=0)85 filemenu.add_command(label="Import from exam", command=self.import_exam)86 filemenu.add_separator()87 filemenu.add_command(label="Exit", command=self.quit)88 menubar.add_cascade(label="File", menu=filemenu)89 90 catalog_menu = tk.Menu(menubar, tearoff=0)91 catalog_menu.add_command(label="Create Catalog", command=self.create_catalog)92 catalog_menu.add_command(label="Open Catalog", command=self.open_catalog)93 menubar.add_cascade(label="Catalogs", menu=catalog_menu)94 95 domain_menu = tk.Menu(menubar, tearoff=0)96 domain_menu.add_command(label="Add domain", command=self.add_domain)97 menubar.add_cascade(label="Domains", menu=domain_menu)98 99 exam_menu = tk.Menu(menubar, tearoff=0)100 exam_menu.add_command(label="Create new exam", command=self.create_exam_dialog)101 exam_menu.add_command(label="Layout existing exam", command=self.layout_exam)102 exam_menu.add_command(label="Load existing exam", command=self.load_exam)103 menubar.add_cascade(label="Exams", menu=exam_menu)104 help_menu = tk.Menu(menubar, tearoff=0)105 help_menu.add_command(label="Help / FAQ", command=self.show_help)106 menubar.add_cascade(label="Help", menu=help_menu)107 self.config(menu=menubar)108 109 86 def create_widgets(self): 110 87 self.columnconfigure(0, weight=2, uniform="columns") … … 112 89 self.rowconfigure(0, weight=1) 113 90 114 frame_left = ttk.Frame(self) 115 frame_left.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) 116 frame_left.rowconfigure(1, weight=1) 117 frame_left.columnconfigure(0, weight=1) 118 frame_left.grid_propagate(True) 119 120 ttk.Label(frame_left, text="Questions").grid( 121 row=0, column=0, sticky="w", pady=(0, 5) 122 ) 123 124 # Listbox + Scrollbar frame 125 list_frame = ttk.Frame(frame_left) 126 list_frame.grid(row=1, column=0, sticky="nsew") 127 list_frame.columnconfigure(0, weight=1) 128 list_frame.rowconfigure(0, weight=1) 129 130 # --- Treeview replacing Listbox --- 131 self.question_tree = ttk.Treeview( 132 list_frame, 133 columns=("text", "state"), 134 show="headings", 135 selectmode="browse", 136 ) 137 self.question_tree.heading("text", text="Question") 138 self.question_tree.heading("state", text="State") 139 self.question_tree.column("text", anchor="w", width=500) 140 self.question_tree.column("state", anchor="center", width=100) 141 self.question_tree.grid(row=0, column=0, sticky="nsew") 142 143 # Attach scrollbars 144 vscrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=self.question_tree.yview) 145 vscrollbar.grid(row=0, column=1, sticky="ns") 146 hscrollbar = ttk.Scrollbar(list_frame, orient="horizontal", command=self.question_tree.xview) 147 hscrollbar.grid(row=1, column=0, sticky="ew") 148 self.question_tree.configure(yscrollcommand=vscrollbar.set, xscrollcommand=hscrollbar.set) 149 150 # Bind events (similar to Listbox) 151 self.question_tree.bind("<<TreeviewSelect>>", self.on_select_question) 152 self.question_tree.bind("<Double-Button-1>", lambda e: self.edit_selected_question()) 153 self.question_tree.tag_configure("cut", foreground="gray60", font=("", 10, "italic")) 154 # Create catalog switcher 155 frm_catalog = ttk.Frame(frame_left) 156 frm_catalog.grid(row=0, column=0, sticky="ew", pady=(4, 8)) 157 frm_catalog.columnconfigure(1, weight=1) # allow dropdown to expand horizontally 158 159 # ─ Label ─ 160 lbl_catalog = ttk.Label(frm_catalog, text="Catalog:") 161 lbl_catalog.grid(row=0, column=0, sticky="w", padx=(0, 6)) 162 self.catalog_var = tk.StringVar() 163 self.catalog_dropdown = ttk.Combobox( 164 frm_catalog, 165 textvariable=self.catalog_var, 166 values=self.catalog_manager.list_titles(), 167 state="readonly", 168 ) 169 self.catalog_dropdown.bind("<<ComboboxSelected>>", self.on_catalog_selected) 170 self.catalog_dropdown.grid(row=0, column=1, sticky="ew", pady=(0, 5)) 171 172 # Bottom button frame 173 frame_buttons = ttk.Frame(frame_left) 174 frame_buttons.grid(row=2, column=0, sticky="ew", pady=(5, 0)) 175 176 # Define 6 columns: Add+Type, Copy, Cut, Paste, Delete, Add-to-exam 177 for i in range(6): 178 frame_buttons.columnconfigure(i, weight=1) 179 180 frame_buttons.columnconfigure(0, weight=2) # Add + Type (wider) 181 frame_buttons.columnconfigure(5, weight=2) # Add-to-exam area (wider) 182 183 # --- Add + Type --- 184 add_frame = ttk.Frame(frame_buttons) 185 add_frame.grid(row=0, column=0, sticky="ew", padx=2) 186 add_frame.columnconfigure(0, weight=0) 187 add_frame.columnconfigure(1, weight=1) 188 189 ttk.Button(add_frame, text="Add", command=self.add_question).grid( 190 row=0, column=0, padx=(4, 6) 191 ) 192 self.qtype_dropdown = ttk.Combobox( 193 add_frame, 194 textvariable=self.current_qtype_var, 195 values=["Instruction", "Text", "Radio", "Multiple_Choice"], 196 state="readonly", 197 width=16, 198 ) 199 self.qtype_dropdown.grid(row=0, column=1, sticky="ew") 200 201 # --- Copy / Cut / Paste / Delete --- 202 ttk.Button(frame_buttons, text="Copy", 203 command=self.copy_selected_question).grid(row=0, column=1, padx=2, sticky="ew") 204 ttk.Button(frame_buttons, text="Paste", 205 command=self.paste_selected_question).grid(row=0, column=3, padx=2, sticky="ew") 206 ttk.Button(frame_buttons, text="Delete", 207 command=self.delete_selected_question).grid(row=0, column=4, padx=2, sticky="ew") 208 209 # --- Add to exam + Dropdown --- 210 exam_frame = ttk.Frame(frame_buttons) 211 exam_frame.grid(row=0, column=5, sticky="ew", padx=(12, 6)) 212 exam_frame.columnconfigure(0, weight=0) 213 exam_frame.columnconfigure(1, weight=1) 214 215 ttk.Button(exam_frame, text="Add to exam", 216 command=self.add_selected_question_to_exam).grid(row=0, column=0, padx=(0, 4)) 217 self.target_exam_dropdown = ttk.Combobox( 218 exam_frame, 219 textvariable=self.target_exam_var, 220 values=[], 221 state="readonly", 222 width=18, 223 ) 224 self.target_exam_dropdown.grid(row=0, column=1, sticky="ew") 225 self.target_exam_dropdown.bind("<<ComboboxSelected>>", self.on_exam_selected) 91 self.selection_panel = SelectPanel(self) 92 93 self.action_panel = ActionsPanel(self) 226 94 227 95 self.detail_panel = DetailPanel(self) … … 231 99 status_frame.grid(row=1, column=0, columnspan=2, sticky="ew") 232 100 233 # Columns:234 # 0 = main status (expands)235 # 1 = right-aligned recent actions (fixed)236 101 status_frame.columnconfigure(0, weight=1) 237 102 status_frame.columnconfigure(1, weight=0) … … 263 128 def export_catalog(self): 264 129 """Export the currently active catalog to a JSON file.""" 265 catalog = self.catalog_manager.get_active _catalog()130 catalog = self.catalog_manager.get_active() 266 131 if not catalog: 267 132 messagebox.showwarning("No Catalog", "No active catalog to export.") … … 294 159 295 160 catalog = QuestionCatalog.from_dict(data) 296 catalog.title = "Test" 161 title = simpledialog.askstring("New Catalog", "Enter title:", parent=self) 162 catalog.title = title 297 163 self.catalog_manager.add_catalog(catalog) 298 164 self.catalog_manager.set_active(catalog.flexo_id) 299 self.refresh_ catalog_list()165 self.refresh_all() 300 166 301 167 def refresh_catalog_list(self): … … 312 178 version = current_catalog.version 313 179 status = ( 314 f"Catalog: {title} | Version: {version} | Questions: {q_count} | Status: {current_catalog.status_text}" 180 f"Catalog: {title} | Version: {version} | Questions: {q_count} | " 181 f"Status: {current_catalog.status_text}" 315 182 ) 316 183 self.status_var.set(status) … … 336 203 catalog.title = title 337 204 catalog.author =author 338 catalog._update_fingerprint 205 catalog._update_fingerprint() 339 206 self.catalog_manager.add_catalog(catalog) 340 207 self.catalog_manager.set_active(did) … … 345 212 346 213 self.log_action(f"Created - New catalog '{title}' is now active.") 347 self.refresh_catalog_list() 348 self.refresh_question_tree() 349 self.title(f"flex-o-grader – [{self.catalog_manager.get_active_title()}]") 350 self.refresh_status_bar() 214 self.refresh_all() 351 215 352 216 def create_exam_dialog(self): … … 357 221 author=dialog.result["author"] 358 222 ) 359 self.refresh_ exam_list()223 self.refresh_all() 360 224 361 225 def on_catalog_selected(self, event=None): … … 364 228 if not title: 365 229 return 366 catalog = self.catalog_manager.set_active_by_title(title) 367 if catalog: 368 self.refresh_status_bar() 369 # Refresh question list display 370 self.refresh_question_tree() 371 230 self.catalog_manager.set_active_by_title(title) 231 232 self.refresh_all() 372 233 self.log_action(f"Catalog Switched - Active catalog: {title}") 373 234 … … 387 248 """ 388 249 Return the appropriate editor dialog instance for a given question. 389 390 250 This acts as a factory so the main GUI doesn’t need isinstance() checks. 391 251 """ 392 if question.qtype in (" radio", "multiple_choice"):252 if question.qtype in ("single_choice", "multiple_choice"): 393 253 return OptionQuestionEditorDialog(parent, question, available_domains) 394 elif question.qtype in ("instruction", "text"):254 if question.qtype in ("instruction", "text"): 395 255 # even though InstructionBlock may subclass Question, 396 256 # it doesn’t need options … … 427 287 428 288 if current_question: 429 # refresh display430 289 current_question.flexo_id = FlexOID.generate( 431 290 current_question.domain, … … 435 294 ) 436 295 active_catalog.add_questions([current_question]) 437 self.refresh_ question_tree()296 self.refresh_all() 438 297 self.log_action(f"Updated - Question {q.id} updated.") 439 298 self.log_action(f"New Question ID - Assigned ID: {current_question.id}") … … 447 306 question = self.require_selected_question() 448 307 exam.add_question(question) 449 # (f"Added {question.flexo_id} to exam {exam.title}")450 308 451 309 def delete_selected_question(self): … … 459 317 try: 460 318 if self.active_catalog.remove(q.id): 461 self.refresh_ question_tree()319 self.refresh_all() 462 320 self.log_action(f"Deleted - Question {q.id} removed.") 463 321 except ValueError as e: … … 473 331 catalog = self.catalog_manager.get_active() 474 332 catalog_title = catalog.title if catalog else None 475 self.detail_panel. update_info(question=q, catalog_title=catalog_title,333 self.detail_panel.refresh_details(question=q, catalog_title=catalog_title, 476 334 clipboard_action=self.clipboard_action, 477 335 clipboard_count=len(self.clipboard) … … 485 343 if current_question: 486 344 self.active_catalog.add_questions([current_question]) 487 self.refresh_ question_tree()345 self.refresh_all() 488 346 self.log_action(f"Updated - Question {q.id} updated.") 489 347 … … 495 353 print("Duplicate IDs:", duplicates) 496 354 497 # Create a temporary catalog355 # Create a temporary catalog 498 356 temp_id = "TEMP_" + datetime.utcnow().strftime("%H%M%S") 499 357 # FIXME: Check flexo_id creation … … 503 361 questions=exam.questions 504 362 ) 505 # self.domain_set.update(temp_catalog.domains)506 363 self.catalog_manager.add_catalog(temp_catalog) 507 364 self.catalog_manager.set_active(temp_id) 508 365 509 self.refresh_catalog_list() 510 self.refresh_question_tree() 511 self.refresh_status_bar() 366 self.refresh_all() 512 367 messagebox.showinfo( 513 368 "Import Complete", … … 524 379 dlg = ExamLayoutDialog(self, exam) 525 380 self.wait_window(dlg) 526 # self.log_action(f"Layout updated for {exam.title}")527 381 528 382 def load_exam(self): … … 534 388 return 535 389 try: 536 self. exam = self.import_exam_as_temp_catalog(path)390 self.import_exam_as_temp_catalog(path) 537 391 except Exception as e: 538 392 messagebox.showerror("Error", f"Could not load exam:\n{e}") … … 554 408 return None 555 409 556 print("QID", qid)557 print(active.questions)558 410 q = active.find(qid) 559 411 if not q: … … 586 438 # Clean up the question text (short preview) 587 439 text = q.text.strip().replace("\n", " ") 588 if len(text) > 100:589 text = text # [:97] + "..."590 440 591 441 # Try to display a readable status name … … 613 463 return 614 464 615 catalogs = self.catalog_manager. list_catalogs()465 catalogs = self.catalog_manager.catalogs() 616 466 if len(catalogs) < 2: 617 467 messagebox.showinfo(action.title(), "You need at least two catalogs.") … … 651 501 652 502 target_catalog.add_questions(transferred) 653 self.refresh_ question_tree()503 self.refresh_all() 654 504 655 505 msg = f"{action.title()}d {len(transferred)} question(s) to catalog '{target_id}'." … … 726 576 self.clipboard_source_catalog = None 727 577 728 self.refresh_ question_tree()578 self.refresh_all() 729 579 self.log_action(f"Pasted {len(transferred)} question(s) to '{target.title}'.") 730 580 … … 736 586 737 587 entry = simpledialog.askstring("Add Domain", 738 "Enter domain in 'ABBREV_Fullname' format(e.g. ELEK_Elektrotechnik):",588 "Enter domain (e.g. ELEK_Elektrotechnik):", 739 589 parent=self) 740 590 if not entry: -
gui/option_question_editor.py
re77bfb3 rfe7d338 21 21 self.question = question 22 22 self.available_domains = sorted(available_domains or []) 23 self.domain_var = tk.StringVar(value=self.question.domain if self.question else "")23 self.domain_var = tk.StringVar(value=self.question.domain_code if self.question else "") 24 24 self.state_var = tk.StringVar(value=self.question.state.name if self.question else EntityState.DRAFT.name) 25 25 … … 79 79 80 80 def on_ok(self): 81 # FIXME: Use from dict here 81 82 text = self.txt_question.get("1.0", tk.END).strip() 82 83 if not text: … … 85 86 86 87 self.question.text = text 87 self.question.domain = self.cmb_domain.get()88 self.question.domain_code = self.cmb_domain.get() 88 89 new_state = EntityState[self.cmb_status.get()] 89 90 self.question.apply_state_change(new_state)
Note:
See TracChangeset
for help on using the changeset viewer.
