א.4 — מחרוזות — עבודה עם טקסט
שם שחקן, הודעה וקלט מהמשתמש הם טקסט. בפייתון שומרים טקסט במחרוזת (str): רצף של תווים בתוך מירכאות. גם רווח הוא תו. אפשר לקרוא תו בודד, לקחת חלק מהטקסט וליצור ממנו טקסט חדש.
בפרק הקודם פגשנו מחרוזות בעיקר כטקסט שמדפיסים. אבל במשחק ובכל תוכנית אחרת צריך גם לעבד טקסט: לבדוק אם השחקן הקליד שם ריק, להפוך את השם לאותיות גדולות כדי להציגו בלוח התוצאות, או לחלץ את השם הפרטי מתוך שם מלא. לשם כך צריך לדעת איך מחרוזת בנויה מבפנים, ואילו פעולות אפשר לבצע עליה.
בפרק זה נתחיל מהמבנה: כל תו במחרוזת יושב במקום ממוספר, ודרך המספר הזה ניגשים אליו. נמשיך לחיתוך של חלק מהטקסט, נראה שמחרוזת אינה משתנה אלא רק יוצרים ממנה מחרוזות חדשות, ונסיים באוסף של פעולות מוכנות ובדרך שבה המחשב מייצג כל תו כמספר. הרעיון של גישה לפי מקום ושל חיתוך יחזור בהמשך הספר גם ברשימות ובמערכים.
מחברות האוניברסיטה הפתוחה — 1-יסודות השפה
כתיבת מחרוזת — גרשיים יחידים או כפולים
אפשר להקיף מחרוזת בגרשיים כפולים ("Hello") או בגרשיים יחידים ('Hello'). שתי הצורות יוצרות מחרוזת מאותו טיפוס, str, ועם אותו תוכן. פותחים וסוגרים באותו סוג של גרשיים; הסימנים שעוטפים את המחרוזת אינם חלק מהטקסט שלה.
נוח לבחור בסוג אחד כדי לכלול את הסוג האחר בתוך הטקסט:
print("Hello")
print('Hello')
print("Dana's turn")
print('She said "Hi"')
פלט
Hello
Hello
Dana's turn
She said "Hi"
בדוגמה השלישית הגרש בתוך Dana's הוא חלק מהטקסט, ובדוגמה הרביעית המירכאות סביב Hi הן חלק מהטקסט. בניגוד ל־C#, גם 'A' היא מחרוזת בפייתון; אין טיפוס char נפרד. כתיבת מחרוזות בתיעוד Python.
תווים ואינדקסים
לכל תו במחרוזת יש אינדקס — מספר המציין את מיקומו ברצף. המספור מתחיל ב־0: לתו הראשון אינדקס 0, לשני אינדקס 1, וכן הלאה. האינדקס הוא המיקום; ערך התו הוא התוכן שנמצא במיקום הזה.
לדוגמה, במחרוזת "Gilad":
| אינדקס | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| ערך התו | G | i | l | a | d |
באינדקס 2 נמצא התו "l": המספר 2 מציין היכן לחפש, והתו "l" הוא מה שמקבלים. ניגשים לתו באמצעות סוגריים מרובעים: text[index]. בפייתון התוצאה היא מחרוזת באורך תו אחד.
אינדקס שלילי סופר מהסוף: -1 מציין את התו האחרון, -2 את התו שלפניו. בדוגמה הבאה גם הרווח שבין השמות תופס מקום — האינדקס שלו הוא 5, ולכן M נמצאת באינדקס 6.
name = "Gilad Markman"
print(name[0], name[6], name[-1])
פלט
G M n
הפונקציה len — אורך המחרוזת
הפונקציה len(text) מחזירה את מספר התווים במחרוזת, כולל רווחים. זהו כלי מרכזי בעבודה עם מחרוזות: בעזרתו יודעים כמה תווים יש בהן ומהו האינדקס האחרון.
האורך והאינדקס האחרון אינם אותו מספר. מכיוון שהמספור מתחיל באפס, במחרוזת שאינה ריקה האינדקסים הלא־שליליים הם מ־0 עד len(text) - 1. את התו האחרון מקבלים באמצעות text[len(text) - 1] או בקיצור text[-1].
name = "Gilad Markman"
print("Length:", len(name))
print("Last index:", len(name) - 1)
print("Last character:", name[len(name) - 1])
print("Last character:", name[-1])
פלט
Length: 13
Last index: 12
Last character: n
Last character: n
במחרוזת יש 13 תווים, אבל האינדקס האחרון הוא 12.
מה קורה כשאינדקס אינו קיים?
גישה לתו מחוץ לגבולות המחרוזת גורמת לשגיאה מסוג IndexError. למשל, len(name) הוא 13, אך אין במחרוזת תו באינדקס 13:
name = "Gilad Markman"
print(name[len(name)])
פלט
IndexError: string index out of range
מוצגת כאן השורה האחרונה של הודעת השגיאה. התוכנית נעצרת בשורה הזאת ולא מדפיסה תו. גם אינדקס שלילי רחוק מדי, כגון name[-14], אינו קיים במחרוזת באורך 13.
מחרוזת ריקה: len("") מחזירה 0. אין בה תו ראשון או אחרון, ולכן גם גישה באינדקס 0 או -1 תגרום לשגיאה.
חיתוך מחרוזת
חיתוך מחרוזת (slicing) יוצר מחרוזת חדשה המכילה חלק מהתווים של המחרוזת המקורית. המחרוזת המקורית אינה משתנה. למשל, מתוך השם המלא "Gilad Markman" אפשר לקבל בנפרד את השם הפרטי "Gilad" ואת שם המשפחה "Markman", ולשמור כל אחד במשתנה חדש.
בכתיבה text[start:stop] מציינים שני אינדקסים המופרדים בנקודתיים: היכן להתחיל והיכן לעצור. ההתחלה נכללת והסיום אינו נכלל. למשל, name[0:5] בוחר את התווים באינדקסים 0, 1, 2, 3 ו־4, ומחזיר את השם הפרטי:
name = "Gilad Markman"
print("First name:", name[0:5])
פלט
First name: Gilad
משמיטים את ההתחלה או את הסיום
אפשר להשאיר את הנקודתיים ולהשמיט אחד מהמספרים או את שניהם. בחיתוך הרגיל, כשהצעד אינו מצוין:
- אין מספר לפני הנקודתיים — מתחילים בתחילת המחרוזת.
name[:5]שקול ל־name[0:5]. - אין מספר אחרי הנקודתיים — ממשיכים עד סוף המחרוזת, כולל התו האחרון.
name[6:]שקול ל־name[6:len(name)]. - אין מספר משני הצדדים — בוחרים את כל המחרוזת.
name[:]מחזיר את כל הטקסט.
הנקודתיים הן שמסמנות חיתוך: name[5] מבקש תו אחד באינדקס 5, ואילו name[:5] מבקש את כל התווים מתחילת המחרוזת ועד לפני אינדקס 5.
first_name = name[:5]
last_name = name[6:]
print("Original:", name)
print("First name:", first_name)
print("Last name:", last_name)
print("Full slice:", name[:])
print(last_name + " " + first_name)
פלט
Original: Gilad Markman
First name: Gilad
Last name: Markman
Full slice: Gilad Markman
Markman Gilad
הפלט מראה ש־name עדיין מכילה את השם המלא, ואילו first_name ו־last_name מכילות את תוצאות החיתוך.
חיתוך עם צעד — step
הכתיבה המלאה היא text[start:stop:step]. הפרמטר step קובע בכמה מקומות מתקדמים בין תו שנבחר לתו הבא. ברירת המחדל היא 1, כלומר תווים רצופים; צעד 2 בוחר כל תו שני.
בדוגמה הבאה מתחילים באינדקס 1, מתקדמים בכל פעם בשני מקומות ועוצרים לפני אינדקס 8. לכן נבחרים האינדקסים 1, 3, 5 ו־7:
text = "0123456789"
print("Step 2:", text[1:8:2])
print("Every second character:", text[::2])
print("Reversed:", text[::-1])
פלט
Step 2: 1357
Every second character: 02468
Reversed: 9876543210
ב־text[::2] משמיטים את ההתחלה והסיום, ולכן בוחרים כל תו שני לאורך כל המחרוזת, החל מאינדקס 0. ב־text[::-1] הצעד הוא -1: עוברים מהסוף להתחלה ומקבלים מחרוזת הפוכה. בכל שלוש ההדפסות נוצרת תוצאת חיתוך חדשה; text נשארת ללא שינוי.
מחרוזת אינה משתנה במקום
ראינו שאפשר לקרוא תו לפי אינדקס. האם אפשר גם לכתוב לאותו מקום, למשל להחליף את האות הראשונה בשם? התשובה היא לא: לא ניתן להחליף תו בתוך מחרוזת קיימת באמצעות אינדקס. למשל, ההשמה הבאה נכשלת:
name = "Gilad"
name[0] = "X"
פלט
TypeError: 'str' object does not support item assignment
מוצגת השורה האחרונה של הודעת השגיאה. כדי לקבל טקסט שונה יוצרים מחרוזת חדשה. מותר לשייך אותה לאותו משתנה באמצעות השמה; פעולה זו משנה לאיזו מחרוזת המשתנה מתייחס, ולא את התווים במחרוזת המקורית. הסבר בתיעוד Python.
פונקציות של מחרוזות
לפייתון יש אוסף של פעולות מוכנות לעבודה עם מחרוזות: המרה לאותיות גדולות או קטנות, ניקוי רווחים מהקצוות, חיפוש קטע טקסט, ספירה והחלפה. במקום לכתוב את הפעולות האלה בעצמנו, פשוט קוראים להן. פעולות אלה נקראות מתודות (Methods): קוראים להן באמצעות נקודה אחרי המחרוזת, למשל text.upper(). הן מחזירות ערך ואינן משנות את המקור. לעומתן, len(text) היא פונקציה שמקבלת את המחרוזת בסוגריים.
בדוגמאות בטבלה מניחים ש־text = "Hello".
| מה הפעולה עושה? | דוגמת שימוש |
|---|---|
| מחזירה מחרוזת שבה האותיות הוחלפו לאותיות גדולות | text.upper() |
| מחזירה מחרוזת שבה האותיות הוחלפו לאותיות קטנות | text.lower() |
| מסירה רווחים ותווי רווח מהקצוות | " Hi ".strip() |
מחזירה מחרוזת שבה כל רצף "th" הוחלף ב־"J"; אפשר להחליף גם כמה תווים יחד | "th-th".replace("th", "J") |
| אינדקס ההופעה הראשונה; −1 אם לא נמצאה | text.find("l") |
| כמו find; שגיאת ValueError אם לא נמצאה | text.index("l") |
| סופרת כמה פעמים הקטע מופיע במחרוזת | text.count("l") |
| האם מתחילה בקטע הנתון? | text.startswith("He") |
| האם מסתיימת בקטע הנתון? | text.endswith("lo") |
| האם כל התווים ספרות? | "3456".isdigit() |
| האם כל התווים אותיות? | "Gilad".isalpha() |
| האם כל התווים אותיות או ספרות? | "Player1".isalnum() |
החיפוש רגיש לאותיות גדולות. בדיקות התווים מחזירות True או False; במחרוזת ריקה שלושתן מחזירות False. isdigit() אינה בדיקה למספר שלילי או עשרוני. strip() ללא ארגומנטים אינה מסירה רווחים מתוך הטקסט. פרטי הפעולות בתיעוד Python.
כדי לשמור תוצאה, משייכים אותה למשתנה:
text = "Hello"
upper_text = text.upper()
print("Original:", text)
print("Uppercase:", upper_text)
print("Missing text:", text.find("Python"))
פלט
Original: Hello
Uppercase: HELLO
Missing text: -1
אם נשתמש כאן ב־text.index("Python"), תתקבל השגיאה ValueError: substring not found במקום הערך -1.
האם קטע טקסט נמצא במחרוזת? — in
האופרטור in בודק אם קטע טקסט מופיע בתוך מחרוזת. כותבים משמאלו את הקטע שמחפשים ומימינו את המחרוזת שבה מחפשים: "ell" in text. התוצאה היא ערך בוליאני: True אם הקטע נמצא, ו־False אם לא. כותבים in באותיות קטנות, ללא נקודה וללא סוגריים של מתודה.
text = "Hello"
print("Contains ell:", "ell" in text)
print("Contains Python:", "Python" in text)
print("Contains hello:", "hello" in text)
פלט
Contains ell: True
Contains Python: False
Contains hello: False
הרצף "ell" מופיע בתוך "Hello", ואילו "Python" אינו מופיע בה. הבדיקה רגישה לאותיות גדולות וקטנות, ולכן גם "hello" לא נמצא. בניגוד ל־find, האופרטור in מחזיר תשובה של אמת או שקר, ולא אינדקס.
לרשימת כל פעולות המחרוזת ודוגמאות נוספות ב־W3Schools.
תווים מיוחדים
על ירידת שורה (\n) וטאב (\t) למדנו בסעיף „שולטים בהדפסה” בפרק א.3. כדי לכלול לוכסן הפוך אחד בטקסט, כותבים שניים (\\) בתוך המחרוזת:
print("Path:", "C:\\temp")
פלט
Path: C:\temp
לכל תו יש מספר — ASCII ו־Unicode
כדי לייצג טקסט במחשב, נותנים לתווים מספרים לפי תקן מוסכם. ASCII (אסקי) הוא תקן ותיק המגדיר 128 קודים, מ־0 עד 127: אותיות באנגלית, ספרות, סימני פיסוק ותווי בקרה כמו ירידת שורה. למשל, הקוד של A הוא 65, של a הוא 97 ושל הספרה 0 הוא 48.
ASCII אינו כולל אותיות עבריות. מחרוזות בפייתון מבוססות על Unicode, תקן רחב הכולל גם עברית, שפות נוספות וסמלים. לכל תו בו יש מספר הנקרא נקודת קוד. קודי ASCII נשמרים באותם מספרים גם ב־Unicode. הסבר על ייצוג תווים בתיעוד Python.
מספר התו אינו האינדקס שלו במחרוזת: A יכולה להופיע במקום הראשון או במקום אחר, אך מספרה נשאר 65.
המרה בין תו למספר — ord ו־chr
ord(character)מקבלת מחרוזת המכילה תו אחד ומחזירה את מספרו ב־Unicode.chr(number)עושה את הפעולה ההפוכה: מקבלת מספר קוד ומחזירה את התו המתאים, כמחרוזת באורך אחד.
print("Code of A:", ord("A"))
print("Code of a:", ord("a"))
print("Code of digit 0:", ord("0"))
print("Character 65:", chr(65))
print("Character 97:", chr(97))
print("Code of Alef:", ord("א"))
print("Character 1488:", chr(1488))
פלט
Code of A: 65
Code of a: 97
Code of digit 0: 48
Character 65: A
Character 97: a
Code of Alef: 1488
Character 1488: א
אות גדולה ואות קטנה מקבלות מספרים שונים. גם התו "0" שונה מהמספר 0: ord("0") מחזירה את קוד התו, 48. הדוגמה של א ממחישה שהפונקציות פועלות גם מעבר לתווי ASCII. תיעוד ord · תיעוד chr.