תגובה: שאלה | דרך נוחה לשמור הגדרות משתמש בפיתון.
רקע
תוכנה שכתובה בפיתון הייתה זקוקה בדחיפות לדרך לשמור הגדרות והעדפות משתמש. כמובן שהייתי יכול לכתוב את כל ההגדרות לקובץ טקסט כלשהו, להמציא פורמט וליישם לו פרוטוקול. אבל כבר למדתי על בשרי בעבר לאחר שעמלתי חודשים על יישום פרוטוקולים חדשים פרי מוחי המבריק, שהיו כאלו שהקדימו אותי – שתלו קוד, ניכשו באגים, עקרו קוצים שיפרו ושיפצו, ואז הפיצו לאחרים חילם – או בקיצור – אל תמציא את הגלגל.
בעקבות ניסיוני רב השנים ידעתי שיש תוכנות רבות שמשתמשות בקובץ בעל סיומת ini על מנת לכתוב לתוכו את ההעדפות, ההגדרות וכל שאר ירקות התלויים במשתמש ספציפי. לאחר שלא ידעתי בעצמי איך משתמשים בקבצים האלו בפיתון, שאלתי שאלה בפורום, אך לאחר שהתגובה היחידה לשאלה שלי נמחקה עוד לפני שעיניי זכו לשזוף אותה, ניסיתי לחפור בעצמי ברחבי המרשתת למרות שאנגלית היא לא הצד החזק שלי, ואני וגוגל טרנסלייט לא מסתדרים במיוחד.
והנה מצאתי ספריית פיתון שיודעת להתעסק עם קבצים מהסוג הנ"ל. ולא סתם ספרייה, היא מובנת בתוך פיתון ולא צריך להתקין אותה. היות ולא מצאתי שום מדריך לשימוש בספרייה הזאת בעברית סטנדרטית, נאלצתי לכתוב אותה בעצמי, ואז גמרתי אומר לפרסם אותה כי אני מעריץ של קוד פתוח... המדריך הוא בסיסי בלבד ואינו מתיימר להקיף את כל הארגומנטים והמתודות שיש לספרייה להציע.
מבנה קובץ תצורה ini
המבנה של הקובץ הוא בסיסי ופשוט. הקובץ מחולק לפסקאות ולכל פסקה יש כותרת – הכותרת נכתבת בין סוגריים מרובעות. לאחר הכותרת ישנם זוגות של מפתחות וערכים (Keys and values) כשבין המפתח והערך יש תו מפריד (בדרך כלל "=" לפעמים ":").
[paths]
user = user/path
log = log/path
language_files = language/files/path
[settings]
language = Hebrew
background = dark
אם נשווה את זה לפיתון זה ייראה לנו כמו מילון (Dictionary) מקונן, כלומר, מילון ראשי שמכיל מפתחות שהן הכותרות של הפסקאות, ולכל מפתח כזה יש ערך שהוא מילון בעצמו. לא פלא שכך בחרו המפתחים של הספרייה להתייחס לקבצי ה-ini בבואם לכתוב קוד שיפענח אותם.
יצירת קובץ תצורה באמצעות פיתון
ראשית נייבא את הספרייה configparser בעצם, אנחנו זקוקים למודול ConfigParser בלבד, לכן נייבא רק אותו כדי להקל על העבודה. לאחר מכן נגדיר את המשתנה שיהיה מעתה "משתנה התצורה שלנו".
from configparser import ConfigParser
config = ConfigParser()
עכשיו נוכל להוסיף למשתנה הזה צמדים של מפתחות וערכים, כשהם נמצאים מתחת לכותרת. לדוגמא:
config['paths'] = {
'user': 'user/path',
'log': 'log/path',
'language_files': 'language/files/path'
}
שימו לב שאנחנו ניגשים למפתח paths במילון שהוא הכותרת ומגדירים תחתיו מילון של מפתחות וערכים.
ניתן גם להגדיר תחילה את הכותרת כמפתח שהערך שלו הוא מילון ריק, ולאחר מכן למלא את המילון במפתחות וערכים:
config['settings'] = {}
config['settings']['language'] = 'Hebrew'
config['settings']['background'] = 'dark'
שימו לב, כל הערכים שהגדרנו עד כה, אינם מופיעים בשום קובץ. את כל הערכים הכנסנו לתוך משתנה שהוא בעצם class שיודע לתרגם פיתון לקובץ תצורה תקין.
אז איך אנחנו כותבים את כל ההגדרות שלנו לקובץ, כדי שהם יישמרו גם לאחר שהסקריפט שלנו יסיים את ריצתו? לשם כך נכתבה המתודה write שאליה נעביר אובייקט קובץ כשהוא פתוח במצב כתיבה:
with open('config.ini', 'w') as file:
config.write(file)
לאחר שנריץ את הסקריפט הזה נוכל לחזות בקובץ הנוצר:
[paths]
user = user/path
log = log/path
language_files = language/files/path
[settings]
language = Hebrew
background = dark
חשוב לשים לב, לרוב פשטותו, קובץ תצורה שומר את כל הנכתב בו כטקסט פשוט, הוא לא תומך בשום פורמט אחר. כך שתוכלו לכתוב בפיתון ערכים מסוג int או bool אבל פיתון ימיר אותם לטקסט בבואו לכתוב אותם לתוך קובץ התצורה.
סקריפט:
config['different_type'] = {
'int': 50,
'float': 1.45,
'bool': False
}
קובץ תצורה:
[different_type]
int = 50
float = 1.45
bool = False
לכן כשאתם משתמשים בקבצי תצורה מסוג זה עליכם לדאוג להמיר את סוגי הערכים בחזרה לפני שאתם מבצעים עליהם מניפולציות.
הערה נוספת: המפתחות בקובץ התצורה אינן תלויי ראשיות (כלומר אין הבדל בין אותיות קטנות לגדולות) כך שגם אם תכתבו את המפתח בסקריפט באותיות גדולות, המפתחות בקובץ תמיד ייכתבו באותיות קטנות. (זה נכון רק לגבי המפתחות – הכותרות והערכים כן תלויי ראשיות).
קריאת קובץ ושינוי שלו
עבור קריאת קובץ תצורה ושינוי שלו נשתמש במתודה rade ונספק לה את הנתיב לקובץ שאותו נרצה לקרוא:
from configparser import ConfigParser
config = ConfigParser()
config.read('config.ini')
לאחר שקראנו את הקובץ הוא נמצא בתוך המשתנה שלנו, ואז נוכל לגשת למפתחות ולערכים שנמצאים בתוכו, לקרוא אותם ולשנות אותם.
הדרך הפשוטה ביותר לעשות זאת היא באמצעות כתיבת שם הכותרת והמפתח:
print(config['paths']['user']) # user/path
אבל באופן זה אם הערך לא נמצא פיתון יחזיר לנו שגיאת KeyError (מפתח לא נמצא). אנו יכולים להשתמש במתודה get שמאפשרת לקבל את הערך, ובמקרה שהערך לא נמצא היא תחזיר None או שנוכל לספק בעצמנו ערך ברירת מחדל למקרה שהערך לא יימצא:
print(config.get('paths', 'user', fallback='C:/Users/USER/'))
נוכל לשלוף את שמות כל הכותרות על ידי המתודה sections ולאחר שיש לנו את שמות הכותרות נוכל לשלוף מקטעים שלמים כמילונים:
print(config.sections()) # ['paths', 'settings']
print(dict(config['paths']))
# {'user': 'user/path', 'log': 'log/path', 'language_files': 'language/files/path'}
אם נרצה להוסיף שורה חדשה תחת כותרת קיימת, ניגש אל הכותרת, ולאחר מכן נוסיף את המפתח בתוך סוגריים מרובעות ונוסיף לתוכו את הערך. אפשר גם להוסיף מקטעים חדשים לגמרי.
# New value
config['paths']['icon'] = 'icon/path'
# New section
config['Usage data'] = {
'Days': 12,
'Hours': 8
}
אם נרצה לשנות ערך קיים, נוכל לגשת אליו על ידי הכותרת והמפתח שלו:
print(config['paths']['user']) # user/path
config['paths']['user'] = 'user/new/path'
print(config['paths']['user']) # user/new/path
אבל שימו לב, כל השינויים שנעשים אינם נשמרים אל הקובץ הם שמורים לעת עתה רק בתוך המשתנה שלנו, כשנרצה לשמור את השינויים לקובץ נקרא שוב למתודה writh ונעביר לה את הקובץ שלנו כשהוא פתוח במוד כתיבה:
with open('config.ini', 'w') as file:
config.write(file)
והנה קובץ התצורה המעודכן שלנו:
[paths]
user = user/new/path
log = log/path
language_files = language/files/path
icon = icon/path
[settings]
language = Hebrew
background = dark
[Usage data]
days = 12
hours = 8
בהצלחה.