20. Συγχώνευση εμφωλευμένων λεξικών — DedSec Project
Βαθιά συγχώνευση ρυθμίσεων — προτεραιότητες, αντίγραφα και ασύμβατοι τύποι Συμβόλαιο πριν από την υλοποίηση Όταν ο χρήστης αλλάζει ρυθμίσεις, θέλουμε οι συγκεκριμένες επιλογές του να…
Βαθιά συγχώνευση ρυθμίσεων — προτεραιότητες, αντίγραφα και ασύμβατοι τύποι
Συμβόλαιο πριν από την υλοποίηση
Όταν ο χρήστης αλλάζει ρυθμίσεις, θέλουμε οι συγκεκριμένες επιλογές του να αντικαθιστούν τις προεπιλεγμένες χωρίς να χάνονται άσχετα πεδία. Η υπάρχουσα συνάρτηση merge συνδυάζει αναδρομικά μόνο δύο λεξικά. Λίστες, απλές τιμές και None αντικαθίστανται εξ ολοκλήρου, χωρίς ένωση ή αγνόηση. Η deepcopy δημιουργεί ξεχωριστά εμφωλευμένα αντίγραφα ώστε αλλαγές στο αποτέλεσμα να μη μεταβάλλουν τις αρχικές ρυθμίσεις. Αν ένα από τα εξωτερικά ορίσματα δεν είναι λεξικό, επιστρέφεται TypeError.
Αρχεία και ανεξάρτητες δοκιμές
Εκτέλεσε python3 20_deep_merge.py για να δεις το αρχικό παράδειγμα. Αποθήκευσε το παρακάτω ως test_merge.py στον ίδιο κατάλογο. Ελέγχει ότι η γλώσσα αλλάζει σε el ενώ το theme παραμένει dark, η λίστα αντικαθίσταται και οι αρχικές τιμές διατηρούνται.
from runpy import run_path
merge = run_path("20_deep_merge.py")["merge"]
defaults = {"ui": {"lang": "en", "theme": "dark"}, "tabs": ["a"]}
result = merge(defaults, {
"ui": {"lang": "el"}, "tabs": ["b"], "new": None
})
assert result == {
"ui": {"lang": "el", "theme": "dark"},
"tabs": ["b"], "new": None
}
result["ui"]["theme"] = "light"
assert defaults["ui"]["theme"] == "dark"
assert merge({"a": {"b": 1}}, {"a": 5})["a"] == 5
try:
merge([], {})
except TypeError:
pass
else:
raise AssertionError("non-dictionary source accepted")
print("PASS merge: isolation, replacement, recursion, type guard")
Γιατί το ρηχό αντίγραφο είναι επικίνδυνο
Η dict(defaults) αντιγράφει μόνο το εξωτερικό λεξικό. Το εσωτερικό ui μπορεί ακόμη να είναι το ίδιο αντικείμενο στις προεπιλογές και στο αποτέλεσμα. Μία αλλαγή θέματος στο αποτέλεσμα τότε αλλοιώνει τις αρχικές ρυθμίσεις χωρίς προειδοποίηση. Η copy.deepcopy αντιγράφει και τα εμφωλευμένα μεταβλητά αντικείμενα. Επίσης αντιγράφει τιμές από τα overrides, ώστε η αλλαγή μίας λίστας στο αποτέλεσμα να μη μεταβάλλει την πηγή. Αυτό έχει σημασία σε μενού ρυθμίσεων όπου οι δοκιμαστικές αλλαγές πρέπει να μπορούν να ακυρωθούν.
Ρητοί κανόνες για τις συγκρούσεις
Η λίστα ['a'] αντικαθίσταται από ['b'], δεν γίνεται ['a','b']. Ένα εμφωλευμένο λεξικό μπορεί να αντικατασταθεί από ακέραιο, ενώ το None αντιμετωπίζεται ως πραγματική τιμή και όχι ως διαγραφή πεδίου. Αυτά είναι συνειδητές επιλογές της εφαρμογής. Πριν επιτρέψεις εισαγωγή ρυθμίσεων από τρίτους πρέπει να αποφασίσεις ποια κλειδιά είναι αποδεκτά, πώς ελέγχονται οι τύποι και πώς αντιμετωπίζονται μυστικές τιμές.
Ασκήσεις με αναπαραγωγή σφαλμάτων
- Αντικατάστησε προσωρινά την deepcopy με ρηχά αντίγραφα και εντόπισε ποιο assert αποτυγχάνει όταν αλλάζει εμφωλευμένη τιμή.
- Πρόσθεσε λεξικό με λίστα, τροποποίησε τη λίστα μόνο στο αποτέλεσμα και έλεγξε ότι καμία είσοδος δεν άλλαξε.
- Γράψε εναλλακτική πολιτική που απορρίπτει ασύμβατους τύπους αντί να τους αντικαθιστά και τεκμηρίωσε τα τεστ.
- Μελέτησε λεξικά που παραπέμπουν στον εαυτό τους και υπερβολικό βάθος. Η παρούσα αναδρομή δεν έχει ρητό όριο, άρα δεν είναι ασφαλής για απεριόριστα μη έμπιστα δεδομένα.
Παραδοτέα: PASS, άθικτες αρχικές ρυθμίσεις, σαφής πολιτική για λίστες/None και ένα αναπαράξιμο σφάλμα κοινής αναφοράς.