البرمجة

المتغيرات وأنواع البيانات

المتغير اسم تعطيه لقيمة لتناديها لاحقا، ونوع تلك القيمة يحدد ما يمكن فعله بها: الأعداد تُجمع، والسلاسل تُلصق، وجمع أحدهما بالآخر يرفع خطأ. وأصعب علل أنواع البيانات ليست التي ترفع خطأ، بل التي تمر بصمت وتطبع رقما خاطئا.

  • الدرس 5 من 14
  • مبتدئ
  • مجاني، دون تسجيل

الأنواع الخمسة التي تدور حول كل قيمة

النوع يحدد ما يمكن فعله بالقيمة، لا مقدار أهميتها.

عدد، صحيح وعشريسلسلة، أي نص
قيمةلكل قيمة نوعبولياني، صح أو خطأ
قاموس، بمفتاحقائمة، بالترتيب

هذه الخمسة ليست كل الأنواع. فلبايثون غيرها، وفي البرامج الأكبر تصنع أنواعك بنفسك؛ لكن الشهور الأولى تمر كلها تقريبا بهذه.

آخر مراجعة: تُراجع الحقائق وأسماء الأدوات في هذا الدرس مقابل مصادرها في هذا التاريخ.

ما المتغير بالضبط وكيف نصنعه؟

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

city = "Tehran"
visits = 1200

والآن أينما كتبت city وضع بايثون تلك السلسلة مكانها. وعلامة المساواة هنا بلا معنى رياضي؛ فهي لا تقول إن الاثنين متساويان، بل تقول ألصق هذا الاسم بتلك القيمة. ولهذا يكون هذا السطر معقولا تماما في الشفرة وبلا معنى في الرياضيات:

visits = visits + 1

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

وثلاث قواعد للتسمية تسهّل العمل من اليوم الأول. ابدأ الاسم بحرف ولا تضع فيه فراغا؛ واستعمل الشرطة السفلية بدله، مثل total_price. ولا تختصر الاسم لتكتب أقل، فبعد ثلاثة أسابيع يصير tp لغزا عليك أنت. واكتب الأسماء بالإنجليزية ولو كان البرنامج عربيا، لأن بقية الشفرة التي ستقرؤها مكتوبة هكذا.

الأنواع الخمسة التي يُنجز بها العمل اليومي

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

price = 120000
rate = 1.09
name = "Sara"
is_paid = True
items = ["pen", "book"]
user = {"name": "Sara", "city": "Tehran"}

والعدد الصحيح والعشري نوعان منفصلان، وهذا الفصل ينفعك لاحقا. والسلسلة نص ويكون دائما بين علامتي اقتباس. والبولياني له قيمتان فقط، True وFalse، وهو قلب كل شرط. والقائمة تحفظ عدة قيم بالترتيب وتصل إليها برقم. والقاموس يحفظ القيم بمفتاح لا برقم، فتقول «المدينة» بدل «الثالث».

print(items[0])
print(user["city"])

والعدّ يبدأ من الصفر، فـitems[0] هو العضو الأول. وهذا مما يزعجك مرات في الشهر الأول ثم يصير عاديا.

وإن لم تعرف ما في يدك فاسأل بايثون نفسه. دالة type تجيب، وهي أداتنا الرئيسة في هذا الدرس كله:

print(type(price))
print(type(name))

لماذا يرفع العمر زائد واحد خطأ؟

هذا أول لقاء حقيقي لأي أحد مع أنواع البيانات، ويبدأ غالبا بـinput. شغّل هذا:

age = "17"
print(type(age))
print(age + 1)

يطبع السطر الثاني <class 'str'> ويُسقط الثالث البرنامج. وهذه الرسالة بنصها، كما جاءتنا حين شغّلناه:

TypeError: can only concatenate str (not "int") to str

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

age = "17"
print(int(age) + 1)

والمخرج 18. فـint يحوّل السلسلة إلى عدد صحيح، وfloat إلى عدد عشري، وstr يعمل بالعكس. ثلاث دوال، وبها يُنجَز تسعون بالمئة من عمل التحويل.

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

وتحذير عن التحويل: int("abc") يرفع خطأ، وهذا هو السلوك الصحيح. وإن رأيت من أسكت ذلك الخطأ بـtry فارغة، فالبرنامج لم يعد يسقط لكنه صار ينتج بيانات خاطئة بصمت، وهذا أسوأ.

من نص كتبه المستخدم إلى عدد يمكن الحساب به

الخطوة الثالثة يكتبها المبرمج؛ وإن تركها سقط البرنامج في الرابعة.

  1. 1

    يصل المدخل

    سلسلة دائما، حتى لو كتب المستخدم عددا.

  2. 2

    قرر: ماذا يُفترض أن يكون هذا؟

    الرمز البريدي يبقى سلسلة والعمر يصير عددا. وأنت وحدك تعرف أيهما.

  3. 3

    حوّل بـint أو float

    كلمة واحدة، وهي التي يسقطها أكثر المبتدئين.

  4. 4

    الآن يمكن الحساب

    من هنا فصاعدا تعني علامة الجمع الجمع لا اللصق.

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

لماذا لا يعطي جمع عددين عشريين ما تتوقعه؟

شغّل هذا وانظر إلى المخرج:

print(0.1 + 0.2)

يطبع 0.30000000000000004. وهذه ليست علة في بايثون؛ فجافاسكريبت وPHP وكل لغة أخرى تقريبا تفعل الشيء نفسه. والسبب أن الحاسوب يحفظ الأعشار في الأساس الثنائي، وبعض الكسور لا تنتهي في الأساس الثنائي، تماما كما لا ينتهي الثلث في الأساس العشري. ووثائق بايثون نفسها تفرد لهذا صفحة كاملة.

وللعرض الحل بسيط:

print(round(0.1 + 0.2, 2))

فيطبع 0.3. لكن هذا يصلح العرض فقط، ولا يكفي للمال.

وتعلم قاعدة المال هنا، فأغلى العلل التي يكتبها مبرمج جديد من هذه العائلة: لا تحفظ المال في عدد عشري. أمامك طريقان صحيحان. الأول أن تحفظ كل شيء بأصغر وحدة كعدد صحيح. والثاني وحدة decimal، وهي موجودة لهذا بالذات.

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

قائمة أم قاموس؛ أيهما أين؟

القاعدة في جملة: إن كانت الأشياء من جنس واحد وترتيبها مهم فقائمة؛ وإن كان لكل منها اسم وتناديه باسمه فقاموس. والشكل أدناه يضع ذلك في عمودين، وليس أحد العمودين أفضل من الآخر.

لكن في القوائم فخا يفاجئ الناس أكثر من أي شيء آخر في هذا الدرس. أتذكر أننا قلنا إن المتغير لصاقة لا صندوق؟ هذا ما يترتب عليه:

a = [1, 2]
b = a
b.append(3)
print(a)

المخرج [1, 2, 3] لا [1, 2]. أضفنا شيئا إلى b فتغير a أيضا، لأن الاسمين ملصقان على قائمة واحدة ولم تُصنع قائمة ثانية أصلا. وإن أردت نسخة منفصلة حقا فعليك أن تقولها:

c = a.copy()
c.append(4)
print(a, c)

فيطبع [1, 2, 3] [1, 2, 3, 4]. الآن صارا شيئين منفصلين.

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

القائمة والقاموس، عملان مختلفان

ليس أحدهما أفضل من الآخر؛ والسؤال هل لأشيائك أسماء أم أرقام.

قائمة

  • أشياء من جنس واحد: أسماء وأسعار وملفات
  • الترتيب له معنى ويُحفَظ
  • تناديها برقم، والعدّ يبدأ من الصفر
  • مبنية لـ«خذها واحدا واحدا»

قاموس

  • أشياء مختلفة لكل منها اسم
  • الترتيب لا يهم؛ المفتاح يهم
  • تناديها بمفتاح، مثل المدينة أو السعر
  • مبني لـ«خصائص شيء واحد»

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

حين يكون النوع خطأ ولا يظهر أي خطأ

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

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

والثاني أكثر إفادة. قالب هذا الموقع يحوّل الأعمدة الرقمية في الجداول إلى أشرطة، ولذلك عليه أن يقرأ رقما مكتوبا في النص. وكانت القاعدة الأولى أن الفاصلة تفصل الآلاف. وعلى الصفحة التركية صارت «2,6 ميغابايت» ستة وعشرين، و«80,6 بالمئة» ثمانمئة وستة، ورُسم شريط أطول ثماني مرات مما قالته الصفحة نفسها. والسبب افتراضنا: فالتركية تجعل النقطة للآلاف والفاصلة للأعشار، عكس الفارسية والإنجليزية تماما.

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

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

المسار السريع مع الذكاء الاصطناعي

المكان الذي تكلّف فيه أنواع البيانات وقتا حقيقيا هو الملف الحقيقي: جدول مُصدَّر، أو ملف CSV، أو جواب عائد من خدمة. والمسار السريع ليس أن ينظف النموذج بياناتك، لأنه لا ينظف ما لا يراه. المسار السريع أن تأخذ منه خريطة أنواع، ثم قائمة القيم التي تكسر تلك الخريطة، وأن تجرب تلك القائمة بنفسك على الملف كله. ويكفي لهذا نموذج سريع ورخيص؛ واختيارنا الحالي في <a class="text-link" href="/ar/ai/">قسم الذكاء الاصطناعي</a>.

  1. الصق خمسة صفوف حقيقية من بياناتك لا وصفا لها. فالوصف ما تظنه في الملف، والصف ما فيه فعلا.
  2. اطلب لكل عمود النوع والتحويل بالضبط مع السبب. وحيث يضعف السبب يسكن عادة افتراض خاطئ.
  3. اطلب قائمة القيم التي تكسر كل تحويل: فارغ، وأرقام غير لاتينية، وفاصلة عشرية، وصفر في البداية، وعدد سالب، ومسافة زائدة.
  4. شغّل التحويل على الملف كله وعُدّ الإخفاقات. فالنظر إلى الصفوف الخمسة الأولى هو ما فعلناه مع الجدول التركي، وخرج خطأ بثمانية أضعاف.

وصفة جاهزة للنسخ

هذه خمسة صفوف حقيقية من ملفي، كما هي:

{خمسة صفوف حقيقية مع العناوين}

هذه البيانات ستُستعمل من أجل {ما تريد فعله بها}.

أعطني ثلاثة أشياء وهذه الثلاثة فقط:

١. جدول أنواع: لكل عمود النوع المقترح ودالة التحويل بالضبط وسبب في جملة.
   وقل صراحة أي الأعمدة يجب أن يبقى نصا ولماذا.
٢. لكل عمود، القيم التي تكسر تحويله. وضمّن على الأقل: خلية فارغة، وأرقاما
   غير لاتينية، وفاصلة تُستعمل فاصلة عشرية، وصفرا في البداية، وعددا سالبا،
   ومسافات زائدة في الأول والآخر.
٣. شفرة تحويل ترفع خطأ وتطبع الصف عند الإخفاق. ولا تبتلع الخطأ بـtry فارغة
   ولا تضع قيمة افتراضية مكان البيانات الخربة.

ولا تفترض أن الفاصلة تفصل الآلاف. وإن لم تكن واثقا فاسأل.

قبل أن تثق بالناتج: رأى النموذج خمسة صفوف لا خمسين ألفا. والأعطاب تسكن في الصفوف التي لم تلصقها بالضبط، فنتيجة هذا العمل خريطة لا ضمانة. وأمران لا تفعلهما أبدا: لا تشغّل شفرة التنظيف على الملف الأصلي بل على نسخة، وقارن عدد الصفوف قبل وبعد؛ وإن كانت الشفرة التي أخذتها تبتلع الأخطاء وتضع قيمة افتراضية فغيّر ذلك. فالعلة التي ترفع خطأ تكلّف ساعة، والعلة التي تصنع رقما خاطئا بصمت تكلّف شهورا.

الذكاء الاصطناعي في هذا العمل

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

أدوات تساعد فعلا

  • Claude جيد في «ماذا يعني هذا الخطأ» وفي بناء قائمة القيم التي تكسر تحويلا. وإيران ليست في قائمة الدول المدعومة لدى أنثروبيك، وقرأنا ذلك في صفحة أنثروبيك نفسها.
  • Gemini يفهم الفارسية جيدا، فحين تكون بياناتك فارسية وفيها أرقام فارسية يسهل شرح المشكلة له. وصفحة غوغل نفسها تقول إن تطبيق جيميناي على الويب يعمل في أكثر من ٢٣٠ دولة وإقليما، وإيران ليست في تلك القائمة.
  • ChatGPT الخيار الأشيع ومقبول لهذا العمل. وليست لدينا بعد صفحة له في قسم الذكاء الاصطناعي، فلا ندعي هنا شيئا عن وصوله أو سعره.

اين ينقلب ضدك

في أجوبة النماذج عادة تصنع بالضبط ما يحذر منه هذا الدرس: تضع التحويل داخل try وتعيد قيمة افتراضية عند الإخفاق، صفرا غالبا. فيتوقف البرنامج عن رفع الأخطاء وتُسرّ أنت، لكن منذ تلك اللحظة صار كل صف خرب في ملفك صفرا وجلس في المجموع النهائي. وإن حملت قاعدة واحدة من هذا القسم فلتكن هذه: اكتب في طلبك أنه عند الإخفاق يجب أن يرفع خطأ ويطبع الصف. والخطر الثاني هو الافتراض المحلي: فما لم تقل غير ذلك يأخذ النموذج القاعدة الإنجليزية عادة ويقرأ الفاصلة فاصل آلاف، وهو الافتراض نفسه الذي أنتج خطأ بثمانية أضعاف في جدولنا التركي. فاذكر لغة بياناتك وصيغتها الرقمية صراحة. والخطر الثالث هو الوصول: فإيران ليست في أي من قائمتي الدول المدعومة لدى أنثروبيك، وليست في قائمة غوغل لدول جيميناي؛ قرأنا الاثنتين في صفحتي الشركتين ولا نقترح طرق التفاف.

المصادر: Python docs: built-in types Anthropic: supported countries Google: where Gemini Apps are available

حدود هذه النصيحة

يفحص بايثون النوع في زمن التشغيل، أي أن شيئا لا يخبرك قبل التشغيل أنك أخطأت في النوع. واللغات ذات الأنواع الساكنة تلتقط ذلك قبله، وهذا فرق حقيقي لا مكان له في هذا الدرس. وثلاثة أمور غائبة عمدا أيضا: الأصناف والأنواع التي تصنعها بنفسك، وتفاصيل وحدة decimal، وأنواع قواعد البيانات ولها قواعدها. ونقطة تُنسى: معرفة الأنواع لا تمنع البيانات الخاطئة. والذي يمنعها وحده هو تشغيل البرنامج على بيانات حقيقية لا على ثلاثة صفوف نموذجية.

من عملنا نحن

أول مرة تحولت فيها الأعمدة الرقمية في جداول هذا الموقع إلى أشرطة، وقع الاختبار على جدول مؤشرات الويب الحيوية، ونتيجته درس اليوم. ففي عمود واحد اجتمعت «٢.٥ ثانية» و«٢٠٠ ميلي ثانية» و«٠.١». وكانت الشفرة قد قرأت الأعداد صحيحة، لكن الأشرطة قالت إن INP ثمانون ضعف LCP، وهي جملة لم تقلها تلك الصفحة قط. ولم تكن المشكلة في النوع بل في الوحدة: فـ٢.٥ و٢٠٠ عددان، وهما غير قابلين للمقارنة ما لم تتحد وحدتهما. والقاعدة التي وضعناها بعدها ما زالت قائمة: لا يأخذ العمود أشرطة إلا حين يكون النص المتبقي في كل خلاياه، أي وحدتها، متطابقا تماما. فالعدد بلا وحدته ليس قيمة كاملة بعد.

اسئلة متابعة حقيقية

كيف أعرف ما بداخل متغير؟

بـprint(type(x)) ترى نوعه، وبـprint(x) ترى الشيء نفسه. أضف هذين السطرين حيث تشك ثم احذفهما؛ فهذه أبسط أدوات التنقيح في بايثون وأكثرها استعمالا. وإن كان الجواب <class 'str'> وأنت تتوقع عددا، فقد وجدت المشكلة في اللحظة نفسها.

بايثون يستنتج النوع بنفسه، فلماذا أهتم؟

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

أنتعلم تلميحات الأنواع من البداية؟

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