السلاسل النصية المتقدمة

الدرس 06 غطى الأساسيات. الآن دعنا نتعمق أكثر. ماذا يعني عدم القابلية للتعديل حقاً؟ ما هي عمليات السلاسل النصية الأقل شهرة لكنها قوية؟ كيف تُخزَن الأحرف الصينية في الحواسيب؟ بعد هذا الدرس، سيكون فهمك للسلاسل النصية في مستوى جديد.


1. عدم قابلية تغيير السلاسل النصية

ذكرناها بإيجاز في الأسئلة الشائعة للدرس 06 — بمجرد إنشائها، لا يمكن تعديل السلاسل النصية. دعنا نرى ماذا يعني هذا بشكل ملموس.

PYTHON
text = "Hello"

# ❌ لا يمكن التعديل هكذا — هذا سيُسبب خطأ
# text[0] = "h"    # TypeError: 'str' object does not support item assignment

لتغيير الحرف الأول إلى حرف صغير، أنشئ سلسلة جديدة:

PYTHON
text = "Hello"
new_text = "h" + text[1:]   # أنشئ سلسلة جديدة
print(new_text)              # hello
print(text)                  # Hello — الأصلي لم يتغير

فوائد عدم القابلية للتعديل

PYTHON
# مراجع مشتركة آمنة
a = "Python"
b = a       # b و a يشيران إلى نفس السلسلة

# بغض النظر عما تفعله بـ a، b لا يتأثر
a = a.upper()
print(a)    # PYTHON
print(b)    # Python — b لا يزال يحتفظ بالقيمة الأصلية
💡 نصيحة: عدم القابلية للتعديل يجلب سلامة الخيوط و القابلية للترميز. السلاسل النصية يمكن أن تكون مفاتيح قاموس (سنغطي هذا لاحقاً)؛ القوائم لا يمكن — لأن القوائم قابلة للتعديل، وتغييرها قد يكسر البحث في القاموس.

تكلفة عدم القابلية للتعديل

كل "تعديل" يُنشئ كائناً جديداً. دمج العديد من السلاسل النصية في حلقة بطيء:

PYTHON
# ❌ غير موصى به: دمج السلاسل النصية في حلقة
result = ""
for i in range(10000):
    result += str(i) + ","
# هذا يُنشئ 10000 كائن سلسلة مؤقت

# ✅ موصى به: اجمع في قائمة، ثم استخدم join()
parts = []
for i in range(10000):
    parts.append(str(i))
result = ",".join(parts)
⚠️ ملاحظة: في البرمجة اليومية، دمج بضع عشرات من السلاسل النصية ليس مشكلة أداء أبداً. انتبه لـ دمج آلاف السلاسل النصية في حلقة. تحت بضع مئات من العمليات، += جيد تماماً.

مثال: محاكاة طرق السلاسل النصية (الصعوبة ⭐⭐)

PYTHON
# نفذ دالة "capitalize"
def my_capitalize(text):
    if not text:
        return text
    first = text[0].upper()
    rest = text[1:].lower()
    return first + rest

print(my_capitalize("hello WORLD"))      # Hello world
print(my_capitalize("python"))           # Python

# نفذ دالة "replace"
def my_replace(text, old, new):
    parts = text.split(old)     # قسّم بـ old، احصل على الأجزاء
    return new.join(parts)      # أعد الدمج بـ new

print(my_replace("one, two, one", "one", "1"))    # 1, two, 1
▶ جرّب الكود

2. طرق الاستعلام عن السلاسل النصية

إلى جانب find() و count() من الدرس 06، إليك المزيد من الطرق المفيدة.

startswith() و endswith()

PYTHON
# تحقق من بداية/نهاية السلسلة

url = "https://www.example.com"

print(url.startswith("https"))    # True — بروتوكول HTTPS
print(url.endswith(".com"))       # True — نطاق .com
print(url.endswith(".org"))       # False

# يمكن التحقق من عدة في وقت واحد
print(url.endswith((".com", ".org", ".cn")))   # True — يطابق .com

الاستخدامات العملية

PYTHON
# التحقق من امتداد الملف
filename = "report.pdf"

if filename.endswith((".pdf", ".doc", ".docx")):
    print("Document file")
elif filename.endswith((".jpg", ".png", ".gif")):
    print("Image file")
elif filename.endswith((".py", ".js", ".html")):
    print("Code file")
else:
    print("Other file")

# التحقق من بروتوكول URL
link = "ftp://files.example.com"
if link.startswith(("http://", "https://")):
    print("Web link")
elif link.startswith("ftp://"):
    print("FTP link")

المخرجات:

TEXT
Document file
FTP link

removeprefix() و removesuffix() (بايثون 3.9+)

أُضيفتا في بايثون 3.9 لإزالة البادئات أو اللواحق بشكل نظيف:

PYTHON
url = "https://www.example.com"

# أزل بادئة البروتوكول
domain = url.removeprefix("https://")
print(domain)               # www.example.com

# أزل لاحقة النطاق
site_name = url.removesuffix(".com")
print(site_name)             # https://www.example

# إذا كانت السلسلة لا تبدأ/تنتهي بالنص المحدد، أعدها كما هي
print(url.removeprefix("ftp://"))   # https://www.example.com (لم تتغير)
💡 نصيحة: قبل بايثون 3.9، إزالة بادئة تطلبت url[len("https://"):] أو url.split("://", 1)[1]. removeprefix() و removesuffix() تجعل هذا أكثر بديهية.


3. استخدام split() المتقدم

الدرس 06 غطى التقسيم الأساسي. دعنا نستكشف المتغيرات الأكثر قوة.

تحديد عدد التقسيمات

PYTHON
# حدد التقسيمات بمعامل maxsplit
text = "one,two,three,four,five"

print(text.split(",", 1))       # ['one', 'two,three,four,five']
print(text.split(",", 2))       # ['one', 'two', 'three,four,five']
print(text.split(",", 3))       # ['one', 'two', 'three', 'four,five']

rsplit(): تقسيم من اليمين إلى اليسار

PYTHON
text = "one,two,three,four,five"

print(text.rsplit(",", 1))      # ['one,two,three,four', 'five']
print(text.rsplit(",", 2))      # ['one,two,three', 'four', 'five']

splitlines(): تقسيم بأسطر

PYTHON
multiline = """First line
Second line
Third line"""

lines = multiline.splitlines()
print(lines)                    # ['First line', 'Second line', 'Third line']

# احتفظ بفواصل الأسطر
lines = multiline.splitlines(True)
print(lines)                    # ['First line\n', 'Second line\n', 'Third line']

partition() و rpartition()

partition() لا تقسم فقط بل تحتفظ أيضاً بموضع الفاصل:

PYTHON
text = "user@example.com"

# قسّم بـ @ إلى ثلاثة أجزاء: (يسار، فاصل، يمين)
parts = text.partition("@")
print(parts)                    # ('user', '@', 'example.com')

# إذا لم يُعثر على الفاصل، يُرجع (original_string, '', '')
print("hello".partition("@"))   # ('hello', '', '')

# rpartition يبحث من اليمين
path = "/home/user/documents/file.txt"
last_slash = path.rpartition("/")
print(last_slash)               # ('/home/user/documents', '/', 'file.txt')

partition() أفضل من split() لسيناريوهات "الاستخراج" — تخبرك بالضبط ما هو كل جزء من الأجزاء الثلاثة.

مثال: تحليل URL (الصعوبة ⭐⭐)

PYTHON
# حلل URL باستخدام partition
url = "https://www.example.com:8080/path/page.html?name=test&page=1"

# استخرج البروتوكول
proto, _, rest = url.partition("://")
print(f"Protocol: {proto}")        # https

# استخرج المضيف والمسار
host_part, _, path_and_query = rest.partition("/")
print(f"Host: {host_part}")    # www.example.com:8080

# استخرج المنفذ
host, _, port = host_part.partition(":")
print(f"Domain: {host}")         # www.example.com
print(f"Port: {port}")         # 8080

# استخرج المسار والاستعلام
path, _, query = path_and_query.partition("?")
print(f"Path: /{path}")        # /path/page.html
print(f"Query: {query}")        # name=test&page=1
▶ جرّب الكود

المخرجات:

TEXT
Protocol: https
Host: www.example.com:8080
Domain: www.example.com
Port: 8080
Path: /path/page.html
Query: name=test&page=1

4. المحاذاة والحشو في السلاسل النصية

توفر بايثون طرقاً لمحاذاة السلاسل النصية ضمن عرض محدد — يسار، يمين، أو وسط.

PYTHON
text = "Python"

print(text.ljust(10))           # 'Python    ' (محاذاة لليسار، حشو بالمسافات على اليمين)
print(text.rjust(10))           # '    Python' (محاذاة لليمين، حشو على اليسار)
print(text.center(10))          # '  Python  ' (توسيط، حشو على كلا الجانبين)

# يمكن تحديد حرف الحشو
print(text.ljust(10, "-"))      # 'Python----'
print(text.rjust(10, "-"))      # '----Python'
print(text.center(10, "-"))     # '--Python--'

zfill(): حشو بالأصفار

شائع الاستخدام لترقيم المعرفات:

PYTHON
print("42".zfill(5))            # 00042
print("-42".zfill(5))           # -0042 — الإشارة السالبة تأتي أولاً
print("3.14".zfill(8))          # 00003.14

# أنشئ معرفات مكونة من 3 أرقام
for i in range(1, 11):
    filename = f"photo_{str(i).zfill(3)}.jpg"
    print(filename, end="  ")

المخرجات:

TEXT
photo_001.jpg  photo_002.jpg  photo_003.jpg  photo_004.jpg  photo_005.jpg
photo_006.jpg  photo_007.jpg  photo_008.jpg  photo_009.jpg  photo_010.jpg

مثال: إنشاء تقرير محاذى (الصعوبة ⭐⭐)

PYTHON
# محاذاة جدول باستخدام rjust و ljust
items = [
    ("Apple", 5.0, 3),
    ("Banana", 3.5, 5),
    ("Milk", 12.0, 2),
]

# العناوين
header_name = "Item".ljust(8)
header_price = "Price".rjust(6)
header_qty = "Qty".rjust(4)
header_total = "Total".rjust(6)
print(f"{header_name}{header_price}{header_qty}{header_total}")
print("-" * 28)

# صفوف البيانات
for name, price, qty in items:
    total = price * qty
    name_col = name.ljust(8)
    price_col = f"{price:.1f}".rjust(6)
    qty_col = str(qty).rjust(4)
    total_col = f"{total:.1f}".rjust(6)
    print(f"{name_col}{price_col}{qty_col}{total_col}")
▶ جرّب الكود

المخرجات:

TEXT
Item     Price  Qty Total
────────────────────────────
Apple      5.0    3  15.0
Banana     3.5    5  17.5
Milk      12.0    2  24.0

5. ترميز الأحرف: ord() و chr()

كل حرف في الحاسوب يقابله رقم (نقطة ترميز). ord() تحصل على الترميز الرقمي للحرف؛ chr() تفعل العكس.

PYTHON
# عرض نقاط ترميز Unicode
print(ord("A"))          # 65
print(ord("中"))         # 20013
print(ord("❤"))          # 10084

# احصل على الحرف من نقطة الترميز
print(chr(65))           # A
print(chr(20013))        # 中
print(chr(10084))        # ❤

# أنشئ جميع الأحرف الكبيرة
for code in range(ord("A"), ord("Z") + 1):
    print(chr(code), end=" ")
# يُخرج: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

ما هو Unicode

ببساطة، Unicode هو معيار ترميز أحرف عالمي — يعين رقماً فريداً لكل حرف من كل نظام كتابة في العالم (صيني، إنجليزي، عربي، ياباني، إلخ.). بايثون 3 تخزن جميع السلاسل النصية داخلياً كـ Unicode، لذا فهي تدعم اللغات المتعددة أصلاً.

PYTHON
# سلاسل بايثون 3 تدعم Unicode أصلاً
text = "Hello, 你好, مرحبا, こんにちは"

print(text)                     # كلها تُعرض بشكل صحيح
print(len(text))                # عدد الأحرف

# يمكنك أيضاً تمثيل الأحرف بهروب \u
print("\u0048")                 # H
print("\u4e2d\u6587")          # 中文 (صينية)
⚠️ ملاحظة: في بايثون 2، كانت السلاسل النصية مقسمة إلى str (بايتات) و unicode (نص)، مما تسبب في أخطاء ترميز متكررة. بايثون 3 وحدتها — جميع السلاسل النصية هي Unicode، تعامل الصينية والإنجليزية بالتساوي. هذا أحد أفضل قرارات تصميم بايثون 3.

الترميز وفك الترميز

بينما تستخدم بايثون Unicode داخلياً، يستخدم تخزين الملفات ونقل الشبكة البايتات. لذا تحتاج إلى الترميز (encode) و فك الترميز (decode):

PYTHON
text = "Hello, 世界"

# الترميز: سلسلة ← بايتات
utf8_bytes = text.encode("utf-8")
print(utf8_bytes)               # b'\xe4\xbd\xa0\xe5\xa5\xbd...'
print(len(utf8_bytes))          # عدد البايتات في UTF-8

gbk_bytes = text.encode("gbk")
print(len(gbk_bytes))           # عدد البايتات في GBK

# فك الترميز: بايتات ← سلسلة
decoded = utf8_bytes.decode("utf-8")
print(decoded)                  # Hello, 世界

# إذا لم يتطابق الترميز/فك الترميز، يحدث خطأ
# gbk_bytes.decode("utf-8")    # UnicodeDecodeError!
💡 نصيحة: 99% من التطوير اليومي يعمل بشكل جيد مع UTF-8. UTF-8 هو ترميز الإنترنت القياسي، متوافق مع ASCII وقادر على تمثيل جميع أحرف Unicode. إذا واجهت نصاً مشوهاً عند قراءة الملفات، المشكلة تكاد تكون بالتأكيد في الترميز — حدد encoding="utf-8" عند فتح الملف وسيعمل.


6. تكميل تنسيق السلاسل النصية

إلى جانب f-strings، لدى بايثون طريقتان قديمتان للتنسيق. بينما f-strings هي المعيار الحديث، قد تصادف هذه في الكود القديم.

طريقة format() (بايثون 3.0+)

PYTHON
# حسب الموضع
print("My name is {}, I'm {} years old.".format("Alice", 18))

# حسب الاسم
print("My name is {name}, I'm {age} years old.".format(name="Bob", age=22))

# تنسيق الأرقام
print("Pi: {:.3f}".format(3.14159))

تنسيق % (عصر بايثون 2)

PYTHON
# قد ترى هذا في الكود القديم
print("My name is %s, I'm %d years old." % ("Alice", 25))
print("Pi: %.2f" % 3.14159)
💡 نصيحة: f-strings (بايثون 3.6+) هي الخيار الأفضل — الأسرع، الأكثر قابلية للقراءة، الأكثر إيجازاً. للكود الجديد، استخدم دائماً f-strings. format() لا تزال مفيدة لـ "التنسيق المؤجل" (حدد قالباً الآن، املأ القيم لاحقاً). تنسيق % فقط للتعرف عليه عندما تصادفه؛ لا تستخدمه في الكود الجديد.


حالات الاستخدام الشائعة


❓ أسئلة شائعة

س لماذا سلاسل بايثون 3 هي Unicode بينما سلاسل بايثون 2 لم تكن؟
ج عصر بايثون 2 كان يعاني من ارتباك في الترميزات (ASCII, Latin-1, GBK, Shift-JIS متعايشة). كانت معالجة السلاسل النصية منقسمة بين "بايتات" و "نص" — نظامان منفصلان، معرضان جداً للأخطاء. بايثون 3 قامت بفصل واضح: جميع السلاسل النصية هي Unicode (نص)؛ بيانات البايتات تُعالج بشكل منفصل بنوع bytes. هذا يجعل عمليات السلاسل النصية بديهية — len("中") هو دائماً 1 على أي نظام. المقايضة هي أنك تحتاج إلى ترميز/فك ترميز يدوي لملف أو إدخال/إخراج الشبكة. ⚠️ س:متى يجب استخدام split() مقابل partition()؟ ج:للتقسيم البسيط، استخدم split(). عندما تحتاج إلى الفاصل نفسه، استخدم partition(). partition() دائماً يُرجع ثلاثة عناصر (يسار، فاصل، يمين)، مفيد لتفكيك الصف: left, sep, right = text.partition(":"). split() يُرجع قائمة بطول متغير. إذا ظهر المحدد مرة واحدة فقط وتحتاج محتوى على كلا الجانبين، partition() هو الخيار الأفضل. س:ما الفرق بين ljust() و {value:10} في f-string؟ ج:ينتجان نفس النتيجة — text.ljust(10) يعادل f"{text:<10}". نسخة f-string أكثر مرونة (تجمع المحاذاة مع تنسيق الأرقام)، بينما استدعاء الطريقة أكثر بديهية. إنها مسألة تفضيل — اختر واحدة والتزم بها. رموز المحاذاة في f-strings: < يسار، > يمين، ^ وسط: f"{text:^10}". س:ما الفرق بين UTF-8 و GBK؟ لماذا يحفظ ملف بايثون الخاص بي أحياناً بأحرف مشوشة؟ ج:UTF-8 هو المعيار الدولي — 1-4 بايتات لكل حرف، عالمي. GBK معيار صيني (صينية مبسطة) — 1-2 بايتات لكل حرف، يدعم فقط الصينية والإنجليزية. إذا تم حفظ ملف بايثون الخاص بك كـ GBK ولكنه يحتوي على نص ياباني أو عربي، سيُحدث خطأ أو يعرض نصاً مشوشاً. الحل: احفظ جميع ملفات بايثون كـ UTF-8 — بايثون 3 تتعامل مع UTF-8 كافتراضي.

📖 ملخص

  • السلاسل النصية غير قابلة للتعديل: كل "تعديل" يُنشئ كائناً جديداً؛ استخدم join() بدلاً من += للدمج الكبير
  • طرق الاستعلام: startswith() / endswith() تتحقق من البداية/النهاية؛ تدعم وسيطات صف لفحوصات متعددة
  • بايثون 3.9+: removeprefix() / removesuffix() لإزالة نظيفة للبادئة/اللاحقة
  • التقسيم المتقدم: split(maxsplit=N) تحديد، rsplit() من اليمين، splitlines() بأسطر
  • partition() يحتفظ بمعلومات الفاصل، مثالي للتحليل المنظم
  • المحاذاة: ljust() / rjust() / center() / zfill()؛ أحرف حشو مخصصة مدعومة
  • ord() يحصل على نقطة ترميز Unicode للحرف؛ chr() يحولها مرة أخرى
  • سلاسل بايثون 3 تستخدم Unicode داخلياً؛ ترميز/فك ترميز باستخدام encode() / decode()؛ يُوصى بـ UTF-8

📝 تمارين

  1. أساسي (الصعوبة ⭐): بالنظر إلى files = ["report.pdf", "photo.jpg", "script.py", "notes.txt", "index.html"]، استخدم endswith() لفلترة:

    • جميع ملفات الصور (.jpg, .png, .gif)
    • جميع ملفات المستندات (.pdf, .doc, .docx, .txt)
    • جميع ملفات الكود (.py, .js, .html, .css)
  2. متوسط (الصعوبة ⭐⭐): اكتب "أداة إعادة تسمية ملفات دفعة واحدة." بالنظر إلى photos = ["IMG_1.jpg", "IMG_2.jpg", ..., "IMG_12.jpg"]، أعد تسميتها إلى photo_001.jpg, photo_002.jpg, ..., photo_012.jpg. تلميح: استخدم partition() لتقسيم اسم الملف والامتداد؛ استخدم zfill() للحشو بالأصفار.

  3. تحدي (الصعوبة ⭐⭐⭐): اكتب "محرك قوالب بسيط." بالنظر إلى سلسلة قالب template وقاموس data، استبدل عناصر نائبة {variable} بالقيم الفعلية.

    PYTHON
    template = "Dear {name}, your order {order_id} has been {status}. Estimated delivery: {delivery_time}."
    data = {
        "name": "Alice",
        "order_id": "20260623001",
        "status": "shipped",
        "delivery_time": "3 business days"
    }
    

    متطلب إضافي: إذا كان متغير القالب غير موجود في البيانات، اترك {variable} كما هو (لا تستبدل). تلميح: كرّر على أزواج المفتاح-القيمة في البيانات، واستدع replace() على القالب لكل منها.

100%