פרק קודם:
פייתון 11 - קידוד וקבציםמה נלמד
- מהו מידע בינארי
- בסיסי ספירה
- מה זה byte וכמה משתנים שונים יש לנו
- איך לעבוד עם קבצים בינאריים
- The walrus operator
- הגדרת קובץ בינארי משלנו
- תכנון קוד
מידע בינארי
כמו משתנה בוליאני שמגדיר True
או False
.
הבסיס למידע בינארי היא יחידה קטנה שנקראת ביט - Bit
.
כל ביט מכיל 0 או 1.
קבצים בינאריים
קבצי טקסט מכילים מידע שאנשים יכולים לקרוא - קבצים בינאריים אלו קבצים שמכילים מידע שרק המחשב יודע לקרוא.
או להגדיר בצורה יותר טובה - בשפה שקוד יודע לקרוא.
בסיס ספירה
כשאנחנו סופרים מספרים אנחנו משתמשים בבסיס ספירה מבלי לדעת שאנחנו עושים זאת.
במתמטיקה שמלמדים בתיכון משתמשים בבסיס ספירה 10.
זה מגדיר את כמות הספרות שאנחנו משתמשים.
1 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 |
בעזרת הספרות האלו אנחנו יכולים לבנות קומבינציות ספרות שנותנים לנו מספרים:
- 5
- 99
- 125
- 952034234723
וכמובן שזה אניסופי.
המשמעות המתמטית של מיקום הספרות במספרים
נדמיין שאנחנו סופרים:
…7, 8, 9…
אנחנו יודעים שמגיע 10 כי אחרי 9 מקדמים את הספרה שמאלה למספר העשרות.
במתמטיקה המיקום של הספרה מיוצג בעזרת חזקה:
1 | 10^1 * 1 + 0 |
אם למשל לוקחים 99 אז זה:
1 | 10^1 * 9 + 10^0 * 1 + 9 |
מה זה המספר 985?
כתבו את זה מתמטית.
1 | (10^2 * 9) + (10^1 * 8) + 10^0 * 1 + 5 |
החוקיות פה היא לקחת את הבסיס שאנחנו נמצאים בו - בסיס 10 במקרה הזה ולבצע פעולת חזקה על המיקום של הספרה.
מיקום | מספר החזקה |
---|---|
יחידות | 0 |
עשרות | 1 |
מאות | 2 |
אלפים | 3 |
עשרות אלפים | 4 |
… | n |
שיטת הספירה הבינארי - בסיס 2
לעומת בסיס 10 שיש לנו 10 ספרות להשתמש על מנת לספור, בבסיס 2 יש לנו רק 2 ספרות:
0 ו 1.
סופרים בבסיס בינארי באותו אופן שכותבים ספרות רגילות רק עם 0 ו1.
בסיס 10 | בסיס 2 |
---|---|
0 | 0 |
1 | 1 |
2 | 10 |
3 | 11 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
8 | 1000 |
בדיחה קומית:
יש לנו 10 אנשים בעולם, כאלו שמבינים בינארית - וכאלו שלא!
תרגיל
ספרו בבינארית עד 50!
בסיס 10 | בסיס 2 |
---|---|
1 | 1 |
2 | 10 |
3 | 11 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
8 | 1000 |
9 | 1001 |
10 | 1010 |
11 | 1011 |
12 | 1100 |
13 | 1101 |
14 | 1110 |
15 | 1111 |
16 | 10000 |
17 | 10001 |
18 | 10010 |
19 | 10011 |
20 | 10100 |
21 | 10101 |
22 | 10110 |
23 | 10111 |
24 | 11000 |
25 | 11001 |
26 | 11010 |
27 | 11011 |
28 | 11100 |
29 | 11101 |
30 | 11110 |
31 | 11111 |
32 | 100000 |
33 | 100001 |
34 | 100010 |
35 | 100011 |
36 | 100100 |
37 | 100101 |
38 | 100110 |
39 | 100111 |
40 | 101000 |
41 | 101001 |
42 | 101010 |
43 | 101011 |
44 | 101100 |
45 | 101101 |
46 | 101110 |
47 | 101111 |
48 | 110000 |
49 | 110001 |
49 | 110010 |
תאמת קצת רימיתי כי לא באמת ספרתי לבד - כתבתי קוד בפייתון שעושה את זה!
הקוד הוא פשוט:
1 | def ToBinary(n): |
הפונקציה bin מחזירה את הייצוג הבינארי של מספר.
אך המספר חוזר עם התחילית 0b.
לכן אני מחליף את כולם בתווים ריקים.
על ביטים, בייטים ועוד
המחשב מגדיר את גודל המספרים שהוא יכול לייצג כי בסופו של דבר הוא צריך לייצג את זה באופן חשמלי.
היחידה הכי קטנה במחשב 0 או 1 נקראת ביט-Bit
.
בייט-Byte
מורכב מ-8 ביטים.
מילה-Word
מורכבת מ-2 בייטים.
מילה כפולה-DWord
מורכבת מ-2 מילים.
מילה מרובעת-QWord
מורכבת משתי מילים כפולות או 4 מילים.
מהו הגודל הפיזי של מילה?
גודל היחידות - בייט,מילה, מילה כפולה ומילה מרובעת תלויה במימוש של מערכת ההפעלה.
כיום רוב המחשבים הביתיים מגדירים מילה כ-64 ביטים(או 8 בייטים).
איך מייקרוסופט השאירו את הגדלים שכפי שתיארתי למעלה על מנת לא ליצור בלבול.
בייצוג בינארי מספר מוגדר ע”י שני מצבים בלבד ולכן בייצוג מתמטי כל ספרה מיוצגת בכפל 2 בחזקת המיקום שלו במספר.
למשל 101:
1 | (1 * 2^2) + (0 * 2^1) + (1 * 2^0) = 101 |
בסיס 10 | בסיס 2 |
---|---|
1 | 0000 0001 |
2 | 0000 0010 |
4 | 0000 0100 |
8 | 0000 1000 |
16 | 0001 0000 |
32 | 0010 0000 |
64 | 0100 0000 |
128 | 1000 0000 |
255 | 1111 1111 |
עד ל-255, המספר המקסימלי של בייט.
Hexadecimal - האקסאדצימאלי - בסיס ספירה 16
כל 4 ביטים נקראים ניבל-Nibble
.
יש סיבה למה רושמים ביטים בקבוצות של 4.
מאוד נוח לייצג 4 ביטים בבסיס 16 - האקסאדצימאלי.
האקסה - 6
דצימאלי - 10
אם בסיס 10 זה 10 תווים, בסיס 2 זה 2 תווים, לכן בסיס 16 הוא 16 תווים!
התווים שמשתמשים בהם:
1 | 0 1 2 3 4 5 6 7 8 9 A B C D E F |
בסיס 10 | בסיס 2 | בסיס 16 |
---|---|---|
0 | 0000 0000 | 0 |
1 | 0000 0001 | 1 |
2 | 0000 0010 | 2 |
3 | 0000 0011 | 3 |
4 | 0000 0100 | 4 |
5 | 0000 0101 | 5 |
6 | 0000 0110 | 6 |
7 | 0000 0111 | 7 |
8 | 0000 1000 | 8 |
9 | 0000 1001 | 9 |
10 | 0000 1010 | a |
11 | 0000 1011 | b |
12 | 0000 1100 | c |
13 | 0000 1101 | d |
14 | 0000 1110 | e |
15 | 0000 1111 | f |
16 | 0001 0000 | 10 |
17 | 0001 0001 | 11 |
למה כל כך נח להשתמש ב-הקסאדצמיאלי כדי לייצג מידע בינארי?
בייטים גודלם 8 ביטים.
מהסיבה הזו מאוד נוח לייצג בייט אחד עם 2 ספרות הקסאדצימאליות.
לאדם יהיה הרבה יותר גם לקרוא אותיות ומספרים מאשר מספרים ארוכים שכוללים 0 ו-1.
למשל המספר 44,979:
בבינארי זה יהיה:
1 | 1010 1111 1011 0011 |
והקסאדצימאלי זה יהיה:
1 | AFB3 |
הרבה יותר קומפקטי ונוח.
דוגמא נוספת זה ייצוג של צבעים, לצבעים יש קוד הקסאדצימאלי שנוח יותר להעתיק אותו ולכתוב.
תרגילים לספירה
1.
אם תפסתם את קונספט הספירה עוד ממקודם אז תנסו לספור לבד מ-17 עד 50!
- השלימו את המספרים:
בסיס 10 בסיס 16 37 99 112 960
כמו בתרגיל הבינארי קצת רימיתי והשתמשתי בפונקציה - hex
.
1 | def ToBinary(n): |
בסיס 10 | בסיס 16 |
---|---|
37 | 25 |
99 | 63 |
112 | 70 |
960 | 3c0 |
מחשבון מתכנתים
קודם כל - למחשבון של ווינדוס יש מוד למתכנתים שעוזר לכם להתמודד עם כל הספירה הזו:
דוגמא לשימוש במחשבון:
תלמדו להשתמש במחשבון הזה - בתור מישהו שעובד הרבה עם ביטים ובייטים אני משתמש בו הרבה פעמים.
Unsigned - Signed
הביטויים מתייחסים למספרים שליליים וחיוביים.unsigned
מתייחס למספרים חיוביים בלבד - 0 ומעלה.signed
מייתחס למספרים שליליים, חיוביים ו0.
אז איך בעצם אנחנו מייצגים מספר שהוא שלילי?
בצורה מאוד פשוטה - משתמשים בביט האחרון או הכי שמאלי!
1 זה מינוס, 0 זה חיובי.
בסיס 10 | בסיס 2 |
---|---|
5 | 0000 0101 |
-6 | 1000 0110 |
10 | 0000 1010 |
-14 | 1000 1110 |
שיטת המשלים ל-2
יש ייצוג אחר למספרים שליליים שעוזרים לנו גם לבצע פעולות מתמטיות בצורה פשוטה יותר.
אני לא ארחיב על זה יותר מדי כי זה לא חובה לדעת.
לעוד מידע על זה ניתן לקרוא פה:
משלים ל-2
Endianity - מה מגיע קודם
הביטוי מתייחס לסידור הבייטים.
נניח יש לנו מספר גדול:
1 | 322945 = 0000 0100 1110 1101 1000 0001 |
או שנוכל לכתוב אותו ככה:
1 | 322945 = 0001 1000 1101 1110 0100 0000 |
מה השתנה כאן?
הסדר התהפך!
יש לנו שני מושגים שונים:
- Little Endian
- Big Endian
Little Endian מתייחס לסידור שהביט הכי קטן הוא קודם.
Big Endian מייתחס לסידור שהביט הכי גדול הוא קודם.
ז”א בדוגמא שכתבנו:
Big Endian:
1 | 322945 = 0000 0100 1110 1101 1000 0001 |
Little Endian:
1 | 322945 = 0001 1000 1101 1110 0100 0000 |
לביט הכי קטן והכי גדול יש מושגים משלהם:
Most Significant Bit - MSB
Least Significant Bit - LSB
תרגיל
תכתבו את המספרים הבאים בבינארית באינדיאניטי קטן וגדול:
בסיס 10 | גדול | קטן |
---|---|---|
5200 | ||
4600 | ||
1288 | ||
16912 | ||
35333 |
בסיס 10 | גדול | קטן |
---|---|---|
5200 | 0001 0100 0101 0000 | 0000 0101 0100 0001 |
4600 | 0001 0001 1111 1000 | 1000 1111 0001 0001 |
1288 | 0101 0000 1000 | 1000 0000 0101 |
16912 | 0100 0010 0001 0000 | 0000 0001 0010 0100 |
35333 | 1000 1010 0000 0101 | 0101 0000 1010 1000 |
איך זה משפיע על הקוד שלנו?
אתם כותבים פרוטוקול בינארי, הקוד שלכם מקבל 4 בייטים, מבלי לדעת את הסדר הנכון של הבייטים האלו איך תוכלו לדעת מהו המספר האמיתי?
בצורה אינטואיטיבית נחשוב שדווקא - Big Endian
הוא הלוגי יותר.
כי השכל שלנו רגיל לקרוא מימין לשמאל מספרים.
(שהימין הוא הקטן ביותר).
אך יש מערכות - או יותר נכון היו - שעובדים בעזרת Little endian
.
הסיבות די מורכבות אך אחת הטענות היא שמבחינת זיכרון קל יותר לטעון מספרים שהם Little endian
.
אני לא אכנס פה לפרטים כי צריך לדעת Assembly
.
קוד פייתון
אחרי שסקרנו בהרחבה,
אנחנו מוכנים לעבוד בקבצים בינאריים בפייתון!
בפרק הקודם הגדרנו פרוטוקול בינארי שנרחיב אותו בפרק הזה.
להלן הקובץ שלנו, חילקנו אותו לשורות כדי שיהיה נוח להסתכל בו:
1 | arr = [0, 25, 19, 1, 2500, 2, 2, 50, 199, 3, 9, 349] |
אנחנו מגדירים כל שורה במערך באופן סדרתי כי ככה קבצים עובדים - הם כתובים כמקשה אחת.
על := במקשה אחת
שימו לב לשורה 7 בקוד הקודם.
ה-while
השני נראה מעט מוזר.
הפרמטר :=
מאתחל משתנה במקשה אחת ביחד עם ה-while
.
אם כמות הבייטים תהיה שווה ל-0
תנאי הלולאה לא יתקיים.
זה כמו לכתוב את הקוד בצורה:
1 | with open("binaryFile", "rb") as file: |
רואים כמה זה חוסך לנו?
האופרטור הזה נקרא גם The Walrus Operator
.
פונקציות פייתון לקריאת קבצים בינאריים
על מנת לקרוא או לכתוב קובץ בינארי צריך להוסיף -b
.
1 | writeBinaryFile = open('file.bin','wb') |
יש לנו שני פונקציות ש-int
נותן לנו כדי לעבוד עם בייטים:
1 | to_bytes(num_of_bytes_to_generate, endianity) |
endianity
יכול להיות 'big'
או 'little'
כפי שלמדנו למעלה.
num_of_bytes_to_generate
מחליט כמה בתים לייצר עבור המספר.
אם נביא לו את הערך 1 אך נבקש ממנו לייצר 4 בייטים זה יצור לנו:
1 | num = 1 |
bytes
- זהו משתנה בתים מיוחד.
בפייתון הסוג המשתנה המיוחד שמטפל ברשימה של בייטים הוא - bytearray
.
השימוש בו הוא נורא פשוט, הוא כמו רשימה רגילה שאתם כבר מכירים:
1 | bytes = bytearray([0,0,0,1,2,255]) |
שימו לב: לא ניתן לשים בבייטים מספר שגדול יותר מ255
.
הרשימה bytearray
לא ניתן לשנות.
אם רוצים סוג שניתן לשנות ניתן ליצור רשימה עם bytes
, אך אני לא ממליץ על זה אלה אם כן אין ברירה.
לכן אנחנו נעבוד עם bytearray
.
הדגמה:
מהו פרוטוקול
פרוטוקול הוא הגדרת חוקים לצורה שבה אנחנו מעבירים מידע או מייצגים מידע.
למשל אם ניקח סדרת מספרים רנדומלית:[1,2,3,4,5]
הם לא יגידו לנו כלום, האם הם אינדקסים למערך אחר? האם זה כמות של מוצרים? האם זה הגדרה של סדרה?
רק פרוטוקול שמוגדר היטב יוכל לומר לנו מהו המידע.
זוכרים שאמרנו שמידע בינארי הוא לא עקבי אלה אם כן אנחנו יודעים בוודאות מה הוא מכיל?
עבור המטרה הזו צריך להגדיר פרוטוקול שיגדיר מה כל מספר מייצג.
פרוטוקול רשימת מוצרים
- כל מספר יהיה בגודל 4 בתים(בייטים) ללא מספרים שליליים.
- כל 3 מספרים מייצגים מספר - משמע 12 בייטים
מיקום משמעות מיקום בייט 0 מזהה 0 1 כמות במלאי 4 2 מחיר ליחידה בודדה 8
בתמונה רואים שיש 24 בייטים סך הכל:
מוצר ראשון - בייט 0 עד 11
מוצר שני - בייט 12 עד 23
- גודל תקני של קובץ יוגדר באופן הבא:
1 | BytesInNum = 4 |
- כפילויות:
הפרוטוקול ייאפשר כפילויות בין מוצרים - ז”א מזהה מוצר יכול להיות זהה למזהה מוצר אחר.
הגדרת API
כדי להיות חכמים ולהגדיר מראש כיצד אנחנו נקרא ונכתוב בפרוטוקול לכן נגדיר פונקציות.
הפונקציות האלו נקראות - Application Programming Interface
.
כמו שיש לנו ממשק משתמש שניתן ללחוץ על כפתורים כדי לבצע פעולות,
יש לנו ממשק מתכנת - פונקציות וכלים אחרים שנותנים למתכנת גישה לפעולות.
הצעד הראשון זה להגדיר עם איזה מידע אנחנו עובדים - מה ה-Input
ומה ה-Output
.
הצעד השני זה להגדיר שמות ברורים
.
והצעד השלישי זה לכתוב את התכנון כיצד להשתמש בפונקציות
.
נתחיל מהמידע שאנחנו עובדים איתו - איזה פרמטרים בעצם נרצה.
תחשבו מעט לפני שאתם פותחים את התשובה.
הפרמטרים שאנחנו רוצים לעבוד איתם הם בייטים ומערך של מערכים.
כל מערך יכיל שלושה מספרים וזה הערך האפליקטיבי שלנו.
ובייטים הם הערכים הבינאריים שלנו.
1 | products = [[0,2,3], [1,20,50],[0,953,5]] |
אם נשמור את זה בקובץ אז למה לא לעבוד עם נתיבי קבצים?
מכיוון שהפרוטוקול שלנו מגדיר צורת מידע ולא צורת תקשורת.
אנחנו רוצים שיהיה ניתן להשתמש בהם בכל מקום - קבצים, לשלוח באינטרנט, לבצע דחיסה וכדו’…
לכן נעבוד מול בייטים ולא שמות קבצים.
הצעד השני אמרנו הוא להגדיר את הפונקציות, חשבו על שמות מתאימים:
הכי פשוט זה לתת שם עם פועל.
למשל:
Read
Write
Create
Modify
Crumble
Zip
ניתן את השמות הבאים:
ValidateProducts
ReadProducts
WriteProducts
וכעת נתכנן את השימוש בפונקציות.
בשלב הזה אנחנו מגדירים את החתימה של הפונקציה - מה היא מקבלת, מה היא מחזירה.
נוכל לזרז תהליכים ופשוט לכתוב בפייתון:
1 | def ValidateProducts(bytes): |
שימו לב שלא מימשנו עדיין כלום - בשלושה שלבים אנחנו הגדרנו מה המידע שלנו, מהם השמות של הפונקציות וכיצד הם נראים.
עכשיו לחלק הפחות כיף - המימוש.
מימוש
נתכנת את המימוש בעזרת מתודולוגיית פיתוח שנקראת - Test Driven Development
.
גישות תכנון ותכנות
יש בתעשייה כל מיני תפקידים בתכנות - חלקם משפיעים יותר על התכנון וחלקם פחות.
מה שבטוח זה שלמתכנתים בדרך כלל יש הרבה השפעה וחופש על כיצד הם מתכננים את העבודה שלהם וכותבים את הקוד.
הסיבה העיקרית לזה שזה התפקיד של מתכנתים בסופו של יום זה לדעת כיצד לתכנן, לכתוב ולהוציא קוד לאור.
מתכנתים טובים שואפים להכיר כמה שיותר בתכנון וכתיבה של קוד
בגישת ה-Test Driven Development אנחנו כותבים קודם כל קוד שבודק את הקוד שאנחנו הולכים לכתוב.
למשל, נתון לנו הקוד הבא:
1 | def Add(a, b): |
שימו לב לפונקציה שאנחנו בודקים - לא מימשנו אותה!
בגישת התכנות הזו - אנחנו קודם מממשים את הקוד שבודק את הקוד שלנו ואחר כך אנחנו מממשים את הקוד שלנו.
תרגול
1.
תריצו את הקוד מבלי לממש את פונקציית החיבור.
2.
ממשו את פונקציית החיבור ותריצו את הקוד.
בפעם הראשונה:
1 | Fail! a:1 b:2 result: 0 |
אחרי המימוש:
1 | Pass! a:1 b:2 result: 3 |
הטסטים לפרוטוקול הבינארי:
1 | def ReadProducts(bytes): |
אם תריצו את הקוד הזה אז אתם תראו שהכל נכשל.
כעת ניתן לממש את הפונקציות כדי שייעבדו.
מימוש הפרוטוקול
ממשו את הפרוטוקול.
1 | def ValidateProducts(bytes): |
ראינו שפרוטוקול בינארי עד כמה שהוא מורכב יכול להיות פשוט כאשר אנחנו מפרקים אותו לגורמים.
למדנו מה זה ספירה בינארית, כיצה לפרש מספרים בעזרת הקסאדצימאל ולכתוב קוד שקורא וכותב מידע בינארי.
בפרק הבא נעבור על פורמט פופולרי - JSON
!