كيفية استبدال سلسلة في بايثون

استبدال السلاسل النصية في بايثون مهارة أساسية. يمكنك استخدام دالة .replace() لإجراء عمليات استبدال مباشرة، بينما تتيح دالة re.sub() مطابقة واستبدالًا أكثر تطورًا للأنماط. تساعدك كلتا الأداتين على تنظيف بيانات النص وتنقيتها.

في هذا الدرس، ستعمل على نص محادثة لإزالة أو استبدال المعلومات الحساسة والكلمات غير المرغوب فيها برموز تعبيرية. لتحقيق ذلك، ستستخدم كلاً من توابع الاستبدال المباشر وتعبيرات عادية أكثر تقدمًا.

ستلعب دور مطور في شركة تقدم الدعم الفني عبر محادثة نصية فردية. مهمتك هي إنشاء نص برمجي لتنقية المحادثة، وإزالة أي بيانات شخصية، واستبدال أي كلمات بذيئة برموز تعبيرية.

لن يتم إعطاؤك سوى نص محادثة واحد قصير:

[support_tom] 2025-01-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2025-01-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
[support_tom] 2025-01-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2025-01-24T10:04:03+00:00 : Blast! You're right!

على الرغم من أن هذا النص قصير، إلا أنه نموذجي لنوع المحادثات التي يجريها الوكلاء دائمًا. يحتوي على مُعرّفات المستخدم، وطوابع زمنية ISO، ورسائل.

في هذه الحالة، قدّم العميل جوندو شكوى، وسياسة الشركة هي تنقيح وتبسيط النص، ثم إحالته إلى تقييم مستقل. تنقيح الرسالة هو مهمتك!

أول شيء عليك فعله هو الاهتمام بأي كلمات بذيئة.

كيفية إزالة أو استبدال سلسلة نصية أو سلسلة فرعية في بايثون

الطريقة الأساسية لاستبدال سلسلة في بايثون هي استخدام تابع السلسلة .replace() :

>>> "Fake Python".replace("Fake", "Arabic")
'Arabic Python'

كما ترى، يمكنك ربط .replace() بأي سلسلة نصية، وتزويد هذه الدالة بمعاملين. المعامل الأول هو السلسلة التي تريد استبدالها، والثاني هو الاستبدال.

الآن حان الوقت لتطبيق هذه المعرفة على النص:

>>> transcript = """\
... [support_tom] 2025-01-24T10:02:23+00:00 : What can I help you with?
... [johndoe] 2025-01-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
... [support_tom] 2025-01-24T10:03:30+00:00 : Are you sure it's not your caps lock?
... [johndoe] 2025-01-24T10:04:03+00:00 : Blast! You're right!"""

>>> transcript.replace("BLASTED", "😤")
[support_tom] 2025-01-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2025-01-24T10:03:15+00:00 : I CAN'T CONNECT TO MY 😤 ACCOUNT
[support_tom] 2025-01-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2025-01-24T10:04:03+00:00 : Blast! You're right!

تحميل النص كسلسلة نصية بين علامتي اقتباس، ثم استخدام دالة .replace() على إحدى كلمات السب والشتم، يعمل بشكل جيد. ولكن هناك كلمة سب أخرى لا تُستبدل، لأن بايثون يشترط أن تتطابق السلسلة تمامًا:

>>> "Fake Python".replace("fake", "arabic")
'Fake Python'

كما ترى، حتى لو لم يتطابق شكل أحد الأحرف، فسيمنع ذلك أي استبدالات. هذا يعني أنه إذا كنت تستخدم دالة .replace()، فستحتاج إلى استدعائها عدة مرات مع المتغيرات. في هذه الحالة، يمكنك ببساطة ربط استدعاء آخر لـ .replace():

>>> transcript.replace("BLASTED", "😤").replace("Blast", "😤")
[support_tom] 2025-01-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2025-01-24T10:03:15+00:00 : I CAN'T CONNECT TO MY 😤 ACCOUNT
[support_tom] 2025-01-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2025-01-24T10:04:03+00:00 : 😤! You're right!

نجاح! لكنك ربما تعتقد أن هذه ليست الطريقة الأمثل لشيء مثل مُنقِّح النسخ العام. يُفضَّل استخدام طريقة ما لإنشاء قائمة بالبدائل، بدلًا من كتابة .replace() في كل مرة.

إعداد قواعد الاستبدال المتعددة

هناك بعض الاستبدالات الإضافية التي تحتاج إلى إجرائها على النص لجعله في تنسيق مقبول للمراجعة المستقلة:

  • تقصير أو إزالة الطوابع الزمنية
  • استبدل أسماء المستخدمين بالوكيل والعميل

الآن وقد أصبح لديك المزيد من السلاسل النصية التي تحتاج إلى استبدال، سيصبح التسلسل باستخدام دالة .replace() مكررًا. إحدى الأفكار هي الاحتفاظ بقائمة من النُسَخ، مع عنصرين في كل نُسَخة. يتوافق العنصران مع الوسيطات التي تحتاج إلى تمريرها إلى دالة .replace() – السلسلة النصية المراد استبدالها وسلسلة الاستبدال:

# transcript_multiple_replace.py

REPLACEMENTS = [
    ("BLASTED", "😤"),
    ("Blast", "😤"),
    ("2025-01-24T", ""),
    ("+00:00", ""),
    ("[support_tom]", "Agent "),
    ("[johndoe]", "Client"),
]

transcript = """
[support_tom] 2025-01-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2025-01-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
[support_tom] 2025-01-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2025-01-24T10:04:03+00:00 : Blast! You're right!
"""

for old, new in REPLACEMENTS:
    transcript = transcript.replace(old, new)

print(transcript)

في هذا الإصدار من نص تنظيف النصوص، أنشأتَ قائمةً من النُسخ البديلة، مما يُتيح لك إضافةَ البدائل بسرعة. يمكنكَ حتى إنشاء هذه القائمة من النُسخ من ملف CSV خارجي إذا كان لديكَ عددٌ كبيرٌ من النُسخ البديلة.

بعد ذلك، كرر عملية استبدال قائمة العناصر. في كل تكرار، تستدعي دالة .replace() على السلسلة، مع ملء الوسيطات بالمتغيرات القديمة والجديدة التي تم فك ضغطها من كل عنصر بديل.

بهذا، حققتَ تحسنًا كبيرًا في سهولة قراءة النص بشكل عام. كما أصبح من الأسهل إضافة بدائل عند الحاجة. يؤدي تشغيل هذا البرنامج النصي إلى الحصول على نص أكثر وضوحًا:

$ python transcript_multiple_replace.py
Agent  10:02:23 : What can I help you with?
Client 10:03:15 : I CAN'T CONNECT TO MY 😤 ACCOUNT
Agent  10:03:30 : Are you sure it's not your caps lock?
Client 10:04:03 : 😤! You're right!

هذا نصّ واضح. ربما هذا كل ما تحتاجه. لكن إذا لم يكن أداؤك الآلي الداخلي راضيًا، فربما يعود ذلك إلى وجود بعض الأمور التي قد تزعجك:

  • لن ينجح استبدال الكلمات البذيئة إذا كان هناك اختلاف آخر باستخدام -ing أو استخدام أحرف كبيرة مختلفة، مثل BLAst.
  • لا يعمل إزالة التاريخ من الطابع الزمني حاليًا إلا في 07 جوان 2025.
  • إن إزالة الطابع الزمني الكامل يتطلب إعداد أزواج بديلة لكل وقت ممكن – وهو ليس شيئًا ترغب في القيام به كثيرًا.
  • إن إضافة المسافة بعد Agent من أجل محاذاة الأعمدة الخاصة بك يعمل بشكل جيد، ولكنه ليس عامًا جدًا.

إذا كانت هذه هي مخاوفك، فقد ترغب في تحويل انتباهك إلى التعبيرات العادية.

استخدام re.sub() لإنشاء قواعد معقدة

عندما تبحث عن إجراء أي عملية استبدال أكثر تعقيدًا قليلًا أو تحتاج إلى بعض الأحرف البدل، فستحتاج عادةً إلى تحويل انتباهك نحو التعبيرات العادية، والمعروفة أيضًا باسم regex.

التعبيرات النمطية (Regex) هي لغة برمجة مصغرة تتكون من أحرف تُعرّف نمطًا. تُستخدم هذه الأنماط، أو التعبيرات النمطية، عادةً للبحث عن سلاسل نصية في عمليات البحث والاستبدال. تدعم العديد من لغات البرمجة التعبيرات النمطية، وهي مستخدمة على نطاق واسع. بل إن التعبيرات النمطية ستمنحك قدرات خارقة.

في Python، يعني الاستفادة من regex استخدام دالة ()sub الخاصة بوحدة re وبناء أنماط regex الخاصة بك:

# transcript_regex.py

import re

REGEX_REPLACEMENTS = [
    (r"blast\w*", "😤"),
    (r" [-T:+\d]{25}", ""),
    (r"\[support\w*\]", "Agent "),
    (r"\[johndoe\]", "Client"),
]

transcript = """
[support_tom] 2025-01-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2025-01-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
[support_tom] 2025-01-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2025-01-24T10:04:03+00:00 : Blast! You're right!
"""

for old, new in REGEX_REPLACEMENTS:
    transcript = re.sub(old, new, transcript, flags=re.IGNORECASE)

print(transcript)

بينما يمكنك دمج دالة ()sub مع دالة .replace()، إلا أن هذا المثال يستخدم ()sub فقط، لذا يمكنك رؤية كيفية استخدامها. ستلاحظ أنه يمكنك استبدال جميع أشكال كلمة البذاءة باستخدام مجموعة استبدال واحدة فقط الآن. وبالمثل، ستستخدم تعبيرًا عاديًا واحدًا فقط للختم الزمني الكامل:

$ python transcript_regex.py
Agent  : What can I help you with?
Client : I CAN'T CONNECT TO MY 😤 ACCOUNT
Agent  : Are you sure it's not your caps lock?
Client : 😤! You're right!

الآن، تم تطهير نصك بالكامل، وإزالة كل التشويش! كيف حدث ذلك؟ هذا هو سحر التعبيرات العادية.

نمط التعبيرات العادية الأول، “blast\w*“، يستخدم الحرف الخاص \w، الذي يُطابق الأحرف الأبجدية الرقمية والشرطات السفلية. إضافة المُحدِّد الكمي * بعده مباشرةً يُطابق صفرًا أو أكثر من أحرف \w.

من الجوانب المهمة الأخرى للنمط الأول هو أن علامة re.IGNORECASE تجعله نمطًا غير حساس لحالة الأحرف. لذا، الآن، سيتم مطابقة واستبدال أي سلسلة فرعية تحتوي على كلمة blast، بغض النظر عن حجمها.

يستخدم نمط التعبيرات العادية الثاني مجموعات الأحرف والكميات لاستبدال ختم الوقت. غالبًا ما تستخدم مجموعات الأحرف والكميات معًا. على سبيل المثال، نمط التعبيرات العادية [abc] يطابق حرفًا واحدًا من a أو b أو c. وضع * بعده مباشرةً يطابق صفرًا أو أكثر من أحرف a أو b أو c.

هناك المزيد من المحددات الكمية. إذا استخدمتَ [abc]{10}، فسيطابق عشرة أحرف بالضبط من a أو b أو c بأي ترتيب وأي تركيبة. لاحظ أيضًا أن تكرار الأحرف أمرٌ زائد، لذا فإن [aa] يُعادل [a].

بالنسبة للطابع الزمني، استخدم مجموعة أحرف موسعة [-T:+\d] لمطابقة جميع الأحرف المحتملة التي قد تجدها في الطابع الزمني. عند استخدام هذا مع المُحدِّد الكمي {25}، سيطابق هذا أي طابع زمني محتمل، على الأقل حتى عام 10,000.

يتيح لك نمط تعبيرات الختم الزمني اختيار أي تاريخ ممكن بتنسيق الختم الزمني. ونظرًا لعدم أهمية الأوقات للمراجع المستقل لهذه النصوص، يمكنك استبدالها بسلسلة فارغة. من الممكن كتابة تعبيرات regex أكثر تطورًا تحافظ على معلومات الوقت مع حذف التاريخ.

يُستخدم نمط التعبيرات العادية الثالث لتحديد أي سلسلة نصية تبدأ بالكلمة المفتاحية “support”. لاحظ أنه يجب عليك تجاوز (\) القوس المربع ([) وإلا فسيتم تفسير الكلمة المفتاحية كمجموعة أحرف.

أخيرًا، يقوم نمط التعبيرات العادية الأخير بتحديد سلسلة اسم مستخدم العميل واستبدالها بـ “Client”.

باستخدام التعابير العادية، يمكنك تقليل عدد الاستبدالات التي يتعين عليك كتابتها بشكل كبير. مع ذلك، قد تحتاج إلى ابتكار العديد من الأنماط. ولأن التعابير العادية ليست أسهل لغات البرمجة قراءةً، فإن وجود العديد من الأنماط قد يصبح صعبًا بسرعة.

لحسن الحظ، هناك خدعة أنيقة مع re.sub() تسمح لك بالحصول على قدر أكبر من التحكم في كيفية عمل الاستبدال، كما تقدم بنية أكثر قابلية للصيانة.

استخدم Callback مع ()re.sub لمزيد من التحكم

إحدى الحيل التي يخفيها بايثون و()sub هي إمكانية تمرير دالة استدعاء بدلاً من سلسلة الاستبدال. هذا يمنحك تحكمًا كاملاً في كيفية المطابقة والاستبدال.

للبدء في بناء هذا الإصدار من البرنامج النصي لتطهير النص، ستستخدم نمط تعبير عادي أساسي لمعرفة كيفية عمل استخدام معاودة الاتصال مع ()sub:

# transcript_regex_callback.py

import re

transcript = """
[support_tom] 2025-01-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2025-01-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
[support_tom] 2025-01-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2025-01-24T10:04:03+00:00 : Blast! You're right!
"""

def sanitize_message(match):
    print(match)

re.sub(r"[-T:+\d]{25}", sanitize_message, transcript)

سيطابق نمط التعبيرات العادية الذي تستخدمه الطوابع الزمنية، وبدلاً من توفير سلسلة نصية بديلة، ستُمرّر مرجعًا إلى دالة ()sanitize_message. الآن، عندما تعثر ()sub على تطابق، ستستدعي ()sanitize_message مع كائن التطابق كمُعامل.

نظرًا لأن ()sanitize_message تطبع فقط الكائن الذي تتلقاه كحجة، فعند تشغيل هذا، سترى كائنات المطابقة تُطبع في وحدة التحكم:

<re.Match object; span=(15, 40), match='2025-01-24T10:02:23+00:00'>
<re.Match object; span=(79, 104), match='2025-01-24T10:03:15+00:00'>
<re.Match object; span=(159, 184), match='2025-01-24T10:03:30+00:00'>
<re.Match object; span=(235, 260), match='2025-01-24T10:04:03+00:00'>

كائن المطابقة هو أحد مكونات وحدة re. تُرجع الدالة ()re.match الأكثر بساطة كائن مطابقة. أما ()sub فلا تُرجع أي كائنات مطابقة، بل تستخدمها في الخلفية.

بما أنك تحصل على كائن المطابقة هذا في دالة الاستدعاء، يمكنك استخدام أيٍّ من المعلومات الموجودة فيه لبناء سلسلة الاستبدال. بعد إنشائه، تُرجع السلسلة الجديدة، وستستبدل الدالة ()sub السلسلة المُعادة بالمطابقة.

تطبيق Callback على البرنامج النصي

في البرنامج النصي لتطهير النص الخاص بك، ستستخدم طريقة .groups() لكائن المطابقة لإرجاع محتويات مجموعتي الالتقاط، ومن ثم يمكنك تطهير كل جزء في وظيفته الخاصة أو تجاهله:

# transcript_regex_callback.py

import re

ENTRY_PATTERN = (
    r"\[(.+)\] "  # User string, discarding square brackets
    r"[-T:+\d]{25} "  # Time stamp
    r": "  # Separator
    r"(.+)"  # Message
)
BAD_WORDS = ["blast", "dash", "beezlebub"]
CLIENTS = ["johndoe", "janedoe"]

def censor_bad_words(message):
    for word in BAD_WORDS:
        message = re.sub(rf"{word}\w*", "😤", message, flags=re.IGNORECASE)
    return message

def censor_users(user):
    if user.startswith("support"):
        return "Agent"
    elif user in CLIENTS:
        return "Client"
    else:
        raise ValueError(f"unknown client: '{user}'")

def sanitize_message(match):
    user, message = match.groups()
    return f"{censor_users(user):<6} : {censor_bad_words(message)}"

transcript = """
[support_tom] 2025-01-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2025-01-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
[support_tom] 2025-01-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2025-01-24T10:04:03+00:00 : Blast! You're right!
"""

print(re.sub(ENTRY_PATTERN, sanitize_message, transcript))

بدلاً من امتلاك الكثير من regexes المختلفة ، يمكنك الحصول على regex من المستوى العلوي يمكن أن يتطابق مع الخط بأكمله ، وتقسيمه إلى مجموعات التقاط مع قوسين (()). ليس لمجموعات الالتقاط أي تأثير على عملية المطابقة الفعلية ، لكنها تؤثر على كائن المطابقة الذي ينتج عن المطابقة:

  • \ [(.+) ] يطابق أي تسلسل من الأحرف ملفوفة بين قوسين مربعة. تختار مجموعة الالتقاط سلسلة اسم المستخدم ، على سبيل المثال Johndoe.
  • [-T:+\ d] {25} يطابق الطابع الزمني ، الذي استكشفته في القسم الأخير. نظرًا لأنك لن تستخدم الطوابع الزمنية في النص النهائي ، فإنه لا يتم التقاطه بأقواس.
  • : تطابق النقطتين الحرفي. يتم استخدام النقطتين كفاصل بين بيانات تعريف الرسالة والرسالة نفسها.
  • (.+) يطابق أي تسلسل للأحرف حتى نهاية الخط ، والتي ستكون الرسالة.

سيكون محتوى مجموعات الالتقاط متاحًا كعناصر منفصلة في كائن المطابقة عن طريق استدعاء التابع .groups()، والتي تقوم بإرجاع مجموعة من السلاسل المطابقة.

المجموعتان هما سلسلة المستخدم والرسالة. تُرجعهما دالة .groups() كمجموعة من السلاسل. في دالة ()sanitize_message، تستخدم أولًا فك الضغط لتعيين السلسلتين إلى متغيرات:

def sanitize_message(match):
    user, message = match.groups()
    return f"{censor_users(user):<6} : {censor_bad_words(message)}"

لاحظ كيف تسمح هذه البنية التحتية بتعبيرات عادية واسعة النطاق وشاملة في المستوى الأعلى، ثم تسمح لك باستكمالها بتعبيرات عادية أكثر دقة داخل معاودة الاتصال البديلة.

تستخدم دالة ()sanitize_message دالتين لتنقية أسماء المستخدمين والكلمات غير المناسبة. كما تستخدم سلاسل f لتسوية الرسائل. لاحظ كيف تستخدم دالة ()censor_bad_words تعبيرًا عاديًا مُنشأً ديناميكيًا، بينما تعتمد دالة ()censor_users على معالجة سلاسل نصية أكثر بساطة.

يبدو هذا الآن نموذجًا أوليًا جيدًا لنص برمجي مُنقَّح! النتيجة نظيفة تمامًا:

$ python transcript_regex_callback.py
Agent  : What can I help you with?
Client : I CAN'T CONNECT TO MY 😤 ACCOUNT
Agent  : Are you sure it's not your caps lock?
Client : 😤! You're right!

رائع! استخدام sub() مع دالة الاستدعاء يمنحك مرونة أكبر لدمج ومطابقة أساليب مختلفة وبناء تعبيرات نمطية ديناميكيًا. كما يمنحك هذا الهيكل مساحة أكبر للتطور عندما يُغير رؤسائك أو عملاؤك متطلباتهم عليك حتمًا!

في هذا البرنامج التعليمي، تعلمتَ كيفية استبدال السلاسل النصية في بايثون. وخلال هذه العملية، انتقلتَ من استخدام دالة .replace() الأساسية في بايثون إلى استخدام الاستدعاءات مع ()re.sub للتحكم المطلق. كما استكشفتَ بعض أنماط التعبيرات العادية وفككتها إلى بنية أفضل لإدارة نص برمجي بديل.

بفضل هذه المعرفة، نجحتَ في تنظيف نص المحادثة، وهو الآن جاهز للمراجعة المستقلة. ليس هذا فحسب، بل إن نصك المُنقّي للنصوص لديه مساحة كافية للتطوير.


اكتشاف المزيد من بايثون العربي

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

Scroll to Top

اكتشاف المزيد من بايثون العربي

اشترك الآن للاستمرار في القراءة والحصول على حق الوصول إلى الأرشيف الكامل.

Continue reading