‏إظهار الرسائل ذات التسميات خواطر شخصية. إظهار كافة الرسائل
‏إظهار الرسائل ذات التسميات خواطر شخصية. إظهار كافة الرسائل

الاثنين، 1 يوليو 2024

مقدمة في تعلم البرمجة بلغة البايثون



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


السلام عليكم ورحمة الله وبركاته

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

أولا: تعريف برمجة الحاسب الآلي


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

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

ما الذي أحتاج تعلمه بداية في أي لغة برمجة لأستطيع كتابة برنامج بها؟ أو بشكل أبسط ما هيكل أي لغة برمجة؟

تأمل في هذا السؤال قليلا، وراجع فديو مثال السيارة في الفديوهات، وستجد نفسك ربما تتفق معي في أن أقل العناصر اللازمة لتعلم أي لغة برمجة هي:

  1. وسيلة لتخزين واسترجاع المعلومات (الذاكرة)
  2. وسيلة للاختيار (المقارنة بين القيم المختلفة)
  3. وسيلة للعد (الجمل التكرارية)

1- العنصر الأول :وسيلة لتخزين واسترجاع المعلومات (الذاكرة)

لو فكرت فيها قليلا ستجد أنه بدون ذاكرة يستحيل حتى على الانسان القيام بأي شيء، فالذاكرة مهمة جدا للاحتفاظ بالمعلومات والمعلومات هي الخبرة التي تراكمت لديك، وهذه الذاكرة لتكون صالحة للاستخدام فأنت تحتاج للقراءة منها (تذكر خبرة أو معلومة ... الخ) ووسيلة للكتابة اليها (اضافة خبرة أو معلومة ... الخ)

في أي لغة تعرف هذه الذاكرة بالمتغيرات variables وكل لغة لها طريقتها (التي تتشابة غالبا بينها مع فروق بسيطة جدا) في تعريف المتغيرات

في لغة البايثون نقوم بالكتابة للذاكرة (الاضافة اليها) بأسلوب مباشر وسهل جدا ومألوف لديك من دراسة الرياضيات

x = 1

هنا نرى أن الذاكرة التي اتخذنا لها اسم x قم بتخزين القيمة 1 فيها، لاحقا في البرنامج يمكن استدعاء القيمة مباشرة من الذاكرة (عملية التذكر) عن طريق استخدام الاسم الخاص بهذه الذاكرة وهو x
كمثال لطباعته على شاشة الكمبيوتر نكتب

print(x)

في هذه الحالية مترجم اللغة يقرأ هذا الأمر ويفهم منه أنك سابقا خزنت في الذاكرة معلومة وهي قيمة المتغير x وتريد قراءتها من الذاكرة وطباعة القيمة على الشاشة

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


2-العنصر الثاني: وسيلة للاختيار (المقارنة بين القيم المختلفة)


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

أي منهما أقل في السعر
أي منهما أعلى في الجودة
أي منهما أفضل في الشكل 
... الخ

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

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

جمل المقارنة مشابهة لما استخدمته في الرياضيات سابقا

لنبدأ بمثال بسيط لتوضيح الأمر

تخيل قيمتين في الذاكرة مخزنتين على النحو التالي

x = 1
y = 2

بشكل عام في أي لغة برمجة ، وبالتالي في بايثون يمكنك القيام بالمقارنات التالية

  1. هل القيمتان متساويتان؟  في بايثون نكتبها على النحو التالي x == y
  2. هل القيمتان غير متساويتان؟في بايثون نكتبها على النحو التالي x != y
  3. هل هناك قيمة أكبر من القيمة الأخرى؟ في بايثون نكتبها على النحو التالي x > y
  4. هل هناك قيمة أكبر من أو مساوية للقيمة الأخرى؟ في بايثون نكتبها على النحو التالي x >= y
  5. هل هناك قيمة أصغر من القيمة الأخرى؟ في بايثون نكتبها على النحو التالي x < y
  6. هل هناك قيمة أصغر من أو مساوية للقيمة الأخرى ؟ في بايثون نكتبها على النحو التالي x <= y
  7. هل هناك قيمة مساوية للصفر؟ x == 0 أو بشكل مختصر !x

ثم هناك جمل أخرى موجودة للقيام بأكثر من مقارنة في نفس الوقت، فمثلا حينما نقوم بالمقارنة نقارن بين أكثر من قيمة في نفس الوقت كأن نقول مثلا أيهما أقل في السعر أو أمتن في الجودة، أو مثلا أيهما أقل في السعر ولونه أخضر، لاحظ في جملة المقارنة الأولى استخدمت "أو" وفي الثانية استخدمت "و" ، والأولى تعنى اختيار الأقل في السعر وإن لم يكن أقل أقارن المتانة ، وفي الجملة الثانية لابد أن يتحقق الشرطان أي أن السعر لابد أن يكون أقل وفي نفس الوقت لابد أن يكون اللون أخضر

بتطبيق هذا على لغة البايثون سنجد لدينا الجمل التالية
 
  1. جملة "أو" لاختبار صحة "أي" مقارنة : في بايثون كمثال نكتب x == y or x == 0 (ونقرأها x تساوي y "أو" x مساوية للصفر)
  2. جملة "و" لاختبار صحة "كل" المقارنات : في بايثون كمثال نكتب x == y and x == 0 (ونقرأها x تساوي y "و" x مساوية للصفر)
  3. جملة النفي لا لعكس الاختيار : في بايثون كمثال نكتب x != 2 (أي أن x لا تساوي القيمة 2)
في بايثون كما في أي لغة برمجة تستخدم جمل المقارنة هذه مع أداة تعرف بالجمل الشرطية وأشهر هذه الجمل هي جملة لو if، وغير else

فنكتب مثلا في بايثون الجملة التالية:

if x == y:
  print("Equal")
else:
  print("Not Equal")
  
ونقرأها على النحو التالي، لو قيمة المتغير x مساوية لقيمة المتغير y قم بطباعة جملة Equal غير ذلك (بمعنى أن القيمتان غير متساويتان) قم بطباعة الجملة Not Equal


3- العنصر الثالث : وسيلة للعد (الجمل التكرارية)


تخيل نفسك تصف لشخص آخر كيفية الوصول من مكان لآخر، تخيل وليس عندك وسيلة للعد أو تكرار الفعل، ستجد نفسك تقول له شيء مشابه لـ:

امش خطوة، ثم خطوة ثم خطوة (... هكذا تكرر نفس الجملة بعدد الخطوات بالضبط الى ان تصل لـ) حتى تجد نفسك في المكان المنشود
لكن باستخدام العدد، يمكنك بكل بساطة واختصارا للكلام والوقت تقل له شيء مشابه لـ: امش 300 خطوة حتى تجد نفسك في المكان المنشود

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

في بايثون هناك جمل مثل for، وwhile تمكنك من تكرار الجمل أو الأفعال وهي ما سنعود اليه لاحقا مع تقدمنا في هذه الدروس.


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


تلخيص للدروس:

  1. شروط اسم المتغير Variable Name Rules:
    1. ألا يبدأ برقم
    2. ألا يحتوي على فراغ (اسم المتغير يكون دوما كلمة واحدة، والفراغ يكون بين أكثر من كلمة)
    3. ألا يحتوي على رموز خاصة كـ $ # % @ ! + = - (لأن كل رمز من هذه الرموز له استخدام في اللغة)
    4. ألا يكون الاسم من كلمات اللغة المحجوزة كـ     print, if, while, def, ... الخ (لأن هذا ستسبب في إرباك مفسر اللغة حيث أن هذا الاسم معرف عنده مسبقا بوظيفة محددة)
    5. يمكن للأسم أن يبدأ بحرف  من a-z أو A-Z أو _ وبعدها يمكن أن يضم الاسم حروف أو أرقام أو علامة _، كمثال الأسماء التالية كلها صحيحة: a _a _12a _A_a ... الخ
  2. نوع البيانات التي يمكن للمتغير تمثيلها: بشكل عام يقوم مفسر اللغة بتحديد نوع البيان داخل كل متغير بناءا على القيمة نفسها، وأبسط أنواع البيانات وأشهرها في لغة البايثون هي:
    1. الأرقام الصحيحة (Integer): كمثال x=1 فإن نوع البيان هنا هو رقم صحيح لأن 1 هو رقم صحيح ولا توجد فيه أي كسور عشرية
    2. الأرقام الكسرية (Float): كمثال x=1.0 فإن نوع البيان هنا هو رقم كسري حتى لو كان الرقم بعد الفاصلة العشرية هو صفر، فإن المفسر يعتبر هذا الرقم كسرا، وكمثال آخر x=1.1 يعتبر رقما كسريا وليس صحيحا
    3. سلسلة الحروف (String) : كمثال x="string" أو x='string' ولاحظ هنا أننا يمكننا استخدام علامتي التنصيص " أو ' للحروف ولكن علينا الانتباه ألا نخلط بينهم في نفس المتغير فلو بدأنا قيمة في متغير بـ " علينا أن ننهيها بنفس الحرف ذاته " حتى لا نتسبب في إرباك مفسر اللغة
  3. لو لدينا متغير كيف يمكننا أن نعرف نوعه؟يمكنك القيام بذلك عن طريق استدعاء الدالة التالية type على النحو التالي كمثال: type(x) فتقوم بطباعة نوع المتغير لك على الشاشة.
  4. أنواع المتغيرات ذاتها بشكل عام، هناك نوعان من المتغيرات موجودة داخل اللغة (وهذه سنعود اليها لاحقا بالشرح):
    1. متغيرات لا تقبل التغيير immutable
    2. متغيرات قابلة للتغيير mutable
  5. الكلمات المحجوزة في اللغة: هي الكلمات التي لا يستطيع المبرمج اعادة استخدامها في برنامجه وهي كلمات خاصة باللغة نفسها، فمثلا لا يستطيع المبرمج استخدام أي من هذه المتغيرات لتسمية متغير، وبعض هذه الكلمات هي (لاحظ أنها جميعا بالأحرف الصغيرة وليست الكبيرة): not, or, and, pass, print, raise, reurn, try, while, with, yield, exec, finally, for, from, import, if, global, in, is, lambda, assert, break, class, continue, def, del, elif, else, except 
  6. أسماء المتغيرات حساسة: اسم المتفير بالحرف الصغير ليست هي نفس اسم المتغير بالحرف الكبير فمثلا aBC=1،  AbC=2 ليسوا نفس الاسم، ويعامل المفسر كلا منهما على أنه متغير مستقل بذاته
  7. الأسطر والمحاذاة: المحاذاة هامة جدا في لغة البايثون، حيث يستخدمها مفسر اللغة لتحديد تبعية الأسطر لما قبلها، في اللغات الأخرى تستخدم مثلا الأقواس الحاضنة {} لتحديد تبعية الأسطر بداخلها لما يسبقها، أو تستخدم كلمات مثل begin, end أو الأقواس () كما في لغة ليسب، أو do, done كما في برنامج Bash ... الخ
فمثلا المثال التالي: 

if x == 1: print("X == 1")
else: print("X != 1")

يعتبر مكافيء للشكل التالي
if x == 1:
  print("X==1")
else:
  print("X != 1")
  
ويفضل استخدام الشكل السابق لأنه الأصح لو كان هناك أكثر من سطر ينبغي تنفيذه بعد جملة if أو جملة else

if x == 1:
  print("X==1")
  print("Another thing")
else:
  print("X != 1")
  print("More to come")
  
8- الجمل متعددة الأسطر
لو أردت تقسيم جملة طويلة على أكثر من سطر، يمكنك أن تستخدم الحرف \ للاشارة للمفسر الى ان الجملة مستمرة في السطر التالي عليه وهكذا 
مثلا في هذا المثال
X = A + \
B + \
C
مساو تماما للسطر التالي
X = A + B + C

9- التعليقات
التعليقات في البايثون يمكن أن تكون لسطر واحد او لأكثر من سطر، فلو أردت كتابة تعليق من سطر واحد ينبغي أن تبدأ التعليق بحرف # وكل ما سيأتي بعده حتى نهاية السطر سيهمله المفسر ولن يقوم بتنفيذه
كمثال
x = 1 # This is the value of x
أو
# This is the value of x
x=1

لو أحببت وضع تعليق على أكثر من سطر بدون الحاجة لتكرار # في بداية كل سطر، يمكنك وصع الملاحظات بين الأحرف التالية ''' '''
كمثال:
# The following is a multiline comment

'''
TThis is a multiline comment
any thing between these quotes is ignored
'''


مثال الاسبوع الأول
import webbrowser
import time
start_time = time.ctime()
start=1
target=10
while start <= target:
  webbrowser.open("https://www.google.com")
  time.sleep(60)
  start = start + 1
  
تلخيص لدرس الأسبوع الثاني:

1- المتغيرات القابلة للتعديل (Mutable) والغير قابلة للتعديل (Immutable):


في لغة البايثون يمكن تصنيف المتغيرات حسب نوع البيانات التي تحملها، فهناك بيانات رقمية Integers، وعشرية Floats، ونصية Strings، وأنواع اخرى مختلفة كـ List، ٍSet وDictionary ... الخ، وهناك تصنيف آخر مهم يتعلق بكيفية تمرير البيانات بين الدوال المختلفة ومتى يقوم المفسر بتحرير المساحة المخصصة لهم، هذا التصنيف يتعلق بقابلية نوع البيانات للتعديل أو عدم قابليته للتعديل. بداية التعديل هنا لا يعني أنك لا تستطيع تغيير القيمة التي يحملها المتغير، بل التعديل هنا يتعلق بشيء آخر، عليك أن تفهم أن كل المتغيرات بلا استثناء يمكنك تغيير القيم المخزنة داخلها، على سبيل المثال المتغير التالي

s = 'Hello'

ينتمي للمتغيرات الغير قابلة للتعديل، وهو من النوع النصي، لتغيير القيمة المخزنة بداخله يمكنك كتابة الأمر التالي

s = Hello, World'

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

s = 'Hello'
print(s)
Hello

id(s)
139845117616560

لاحظ هنا أننا طبعنا قيمة المتغير وعنوانه في الذاكرة وطبع لنا الجملة النصية Hello وتحتها طبعنا عنوان المتغير في الذاكرة (لاحظ لو قمت بطباعة العنوان على جهازك ستكون القيمة مختلفة)

الآن لنقم بتغيير قيمة المتغير

s = 'Hello, World'
print(s)
Hello, World

id(s)
139845117216048

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

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

لنجرب الآن نفس الاختبار مع متغير من النوع القابل للتعديل

l = [1, 2, 3, 4, 5]
print(l)
[1, 2, 3, 4, 5]

id(l)
139845116377024

لنقم الآن بتغيير قيمة أحد عناصر القائمة

l[0]=9
print(l)
[9, 2, 3, 4, 5]

id(l)
139845116377024

لاحظ هنا أن عنوان المتغير ظل كما هو بالسابق

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

كمثال

l1 = [1, 2, 3, 4, 5]
l2 = l1
id(l1)
139845115059456

id(l2)
139845115025408

l2 is l1
True

print(l2)
[1, 2, 3, 4, 5]

l2[0]=8
print(l1)
[8, 2, 3, 4, 5]

لاحظ هنا أن كل متغير منهم له مكان في الذاكرة مختلف، لكن عند تغيير قيمة أحدهم، تغيرت القيم في المتغير الآخر، ولاحظ استخدام جملة is للتحقق من إن كان المتغير الأول مساو للثاني أم لا وهو ما رأينا نتيجته تظهر على السطر التالي بـ True أي أن المتغيران هما نفس الشيء

بشكل عام ومختصر أنواع البيانات القابلة للتعديل mutable هي
list, dictionary, set

وأنواع البيانات الغير قابلة للتعديل immutable هي
int, float, string, boolean, tuple 

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

2- ترصيد القيم في المتغير variable assignment
بشكل عام يمكن تغيير قيم أي متغير في البايثون باستخدام الصيغ التالية

x=1  # وضع قيمة مباشرة
y=x  # تغيير قيمة متغير من متغير آخر
z=f() # تغيير القيمة من قيمة دالة استدعيناها
z=input("Type something:") # مشابه للمثال السابق، هنا نأخذ القيمة من لوحة المفاتيح ونضعها كقيمة للمتغير
a,b,c=1,2,3 #  تغيير قيم أكثر من متغير في نفس السطر


3- أنواع البيانات المختلفة

3-1 المجموعة Set 

تتميز المجموعة أنها قابلة للتعديل mutable ولا تحوي قيم مكررة، وهي مشابهة لمفهوم المجموعة الرياضي

s={1, 2, 3, 4, 5, 6, 1}

لاحظ هنا تكرار قيمة 1 في المجموعة هنا، لكن لو طبعنا قيم المجموعة سنرى التالي

print(s)
{1, 2, 3, 4, 5, 6}
سنرى هنا أنه لا يوجد تكرار للقيم

s.add(2)
print(s)
{1, 2, 3, 4, 5, 6}

سنرى أيضا هنا أنه لا يوجد تكرار للقيم

3-2 القائمة list

تتميز القائمة أنها قابلة للتعديل mutable ويمكن أن تحوي قيم مكررة أو قيم من بيانات مختلفة (أرقام صحيحة وعشرية ونصوص وحتى قائمة أو مجموعة أو قاموس أو أي نوع بيانات آخر) ويمكن الوصول للعناصر المختلفة بداخلها عن طريق ما يعرف بالفهرسة indexing والقائمة والبيان النصي string يشتركان في مفهوم الفهرسة ولكن المتغير النصي immutable

مثال لتعريف متغير من نوع القائمة

l = [1, 2, 3, "Hi", "1", {1, 2, 3}]
type(l)
list
l[0] # للوصول لأول عنصر في القائمة
1
l[-1] # للوصول لآخر عنصر في القائمة
{1, 2, 3}
l[::-1] # لعكس ترتيب القائمة
[{1, 2, 3}, '1', 'Hi', 3, 2, 1]
l[0:2] # للحصول على العناصر في الفهرس 0 و 1 
[1, 2]
l[-1] = "New value" # لتغيير قيمة آخر عنصر في القائمة
[1, 2, 3, 'Hi', '1', 'New value']

3-3 القاموس dictionary 

يتميز القاموس أنه قابل للتعديل mutable ومشابه لتعريف المجموعة بشكل كبير الا أن كل عنصر في القاموس يتكون من عنصرين: المفتاح والقيمة kay and value والمفتاح لا يمكن تكراره، ولكن القيم ممكن أن تكون مكررة
ويمكن تعريفه على النحو التالي

dic = { "A": 1, "B": 2, "C": 3 }

ويمكن فهرسته عن طريق المفتاح key وتغيير محتوياته

dic["A"]
1
dic["B"]
2
dic["C"]
3
dic["A"] = 4
print(dic)
{'A': 4, 'B': 2, 'C': 3}

4- التحويل بين أنواع البيانات
يمكن في البايثون تحويل البيانات بين أنواع المتغيرات المختلفة إذا كان يمكن تحويلها، فعلى سبيل المثال المتغير التالي

s ='123'

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

t = s + 1
TypeError: can only concatenate str (not "int") to str

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

i = int(s)
123
f = float(s)
123.0

كما يمكن تحويل القيم العشرية والصحيحة لمتغير نصي باستخدم الدالة التالية

str(f)
'123.0'
str(i)
'123'

كما يمكن تحويل المتغير النصي الى قائمة باستخدام الدالة التالية
list(s)

['1', '2', '3']

5 - التعابير والعمليات الحسابية في لغة البايثون

5-1 العمليات الحسابية في البايثون هي

الجمع + 
الطرح  - 
الضرب  *
القسمة /
ناتج القسمة %
الرفع لقوى ** 

5-2 العمليات على المتغير النصي

الأمثلة التالية توضح الفكرة

s1 = 'Hello'
s2 = 'World'
s3 = s1 + ', ' + s2
'Hello, World'
s1[0]
'H'
s1[0:2]
'He'
s1[2:] # استخرج القيم من الفهرس 2 للنهاية
'llo'
s1[::-1] # لعكس القيمة النصية
'olleH'
s1*2 # لتكرار القيمة النصية مرتين
'HelloHello'

5-3 عمليات المقارنة relational operation


المساواة  x == y
عدم المساواة x != y
أكبر من x > y
أكبر من أو تساوي x >= y 
أقل من x < y
أقل من أو تساوي x <= y 
هل المتغيران يشيران لنفس الشيء (راجع الشرح الخاص بالمتغيرات القابلة للتعديل وغير القابلة للتعديل) x is y
هل المتغيران لا يشيران لنفس الشيء  x is not y

5-4 العمليات المنطقية

و ِx and y وشرحناها من قبل وتعني أنه لتصيح العملية كلها صحيحة لابد أن يكون كلا الطرفين صحيحين
أو x or y وشرحناها من قبل وتعني أنه لتصيح العملية كلها صحيحة يكفي أن يكون أحد الطرفين صحيح
لا  not x وتعني عكس أي قيمة في x فلو كانت صحيحة تصبح خاطئة ولو كانت خاطئة تصبح صحيحة

5-5 أولويات تنفيذ العمليات

الأقواس (أي ما داخل الأقواس ينفذ أولا)
الرفع لقوى
زيادة +x وانقاص القيمة-x  ونفي القيمة ~x
الضرب ثم القسمة ثم باقي القسمة
الجمع ثم الطرح
عمليات المقارنة أقل من ، أقل من أو يساوي، أكبر من، أكبر من أو يساوي، لا يساوي، يساوي
العمليات المنطقية لا و أو

5-6 المكتبات البرمجية الجاهزة


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

المثال التالي مأخوذ من فديوهات الدرس ويوضح الفكرة بصورة جيدة


import math
signal_power = 9. # لاحظ العلامة العشرية في النهاية لإخبار المترجم أننا نريد عمليات حسابية عشرية
noise_power = 10.
ratio = signal_power / noise_power 
decibels = 10 * math.log10(ratio) # حساب الديسيبيل لنسبة الاشارة للضوضاء والديسيبيل رياضيا هو نسبة على مقياس اللوجارثم العشري
print(decibels) 

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

pip install nltk

5-7 مساحات الاسم namespaces


يستخدم هذا المفهوم لازالة اللبس لو أن هناك أكثر من دالة أو متغير بنفس الاسم، فهناك ثلاثة أنواع
1- الاسم المحلي local names وهي لأسماء المتغيرات داخل دالة معينة
2- الاسم المشاع global وهي لأسماء المتغيرات والدوال داخل ملف معين
3- الأسماء المبنية داخل اللغة builtins وهي لأسماء المتغيرات والدوال داخل مترجم البايثون نفسه كالدوال التالية abs, min, max ... الخ


تلخيص لدرس الأسبوع الثالث:


1- الجمل الشرطية conditionals

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

if x:
  print("x is True")
elif y:
  print("Y is True")
else:
  print("x and y are not True")
  
طبعا هذا الشكل مبسط جدا ولكن لتوضيح الفكرة العامة، أن جملة if تقوم باختبار قيمة المتغير x أولا لترى هل قيمته لا تساوي الصفر أم لا، فلو كانت قيمة المتغير x صفرا، فإن لغة البايثون ستنتقل لاختبار الشرط الثاني، ولكن لو كانت قيمة المتغير x غير مساوية للصفر، فإن المترجم سيقوم بتنفيذ الجملة المرتبطة بالشرط وهي هنا طباعة الجملة التالية لها مباشرة في نفس درجة المحاذاة.

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

فمثلا جملة if يمكن أن تكون على هذه الصيغ التالية وسأورد الشرح بجانب كل صيغة منها

if x == 1 or x == 2 # يقوم المفسر باختبار الشرط الأول فلو كان صحيحا لن يختبر الشرط الثاني، لو كان غير صحيح سيختبر الشرط الثاني
if x >=2 and y <=3 # سيقوم المفسر باختبار الشرطين معا لتكون الجملة كلها صحيحة
if func1() > 4 # سيقرأ المفسر القيمة الراجعة من الدالة وسيقوم باختبار هل هي أكبر من 4
if !x  # سيقوم المفسر بقراءة قيمة المتغير ثم لو كانت صفرا سيحولها لواحد أي سيكون الشرط صحيحا، ولو كانت بواحد سيحولها لصفر وسيكون الشرط غير صحيح
if !func1() # نفس الأمر السابق لكن هذه المرة مع القيمة الراجعة من الدالة

2- الاستثناءات exceptions

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

2-1- أخطاء عادية: 


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

def func1(x):
  if x > 10:
    return 1   # Error
  else:
    return 0   # Normal
    
    
عندما أقوم باستدعاء هذه الدالة، فسأقوم باخنبار القيمة الراجعة للتأكد من سلامة التنفيذ أو لو كان هناك أي خطأ

if func1(11):
  print("Function returned with an error!")

2-2- الأخطاء الاستثنائية exceptions:

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

try:
  # try some thing here
except Exception as e:
  # Error happened, handle it
else:
  # No exception happened
finally:
  # This will execute in case there was an exception or not



كمثال (ملحوظة: المثال التالي منسوخ من اجابة من برنامج الشات جي بي تي)

try:  # الصيغة العامة دوما نبدأ بهذه الكلمة
    x = int(input("Enter a number: ")) # نحاول قراءة مدخل من لوحة المفاتيح ووضعه في المتغير
    result = 10 / x # نقسم 10 بقيمة المتغير التي سبق وأن قرأناها
    print("Result:", result) # نطبع النتيجة هنا
except ValueError: # هذا الاستثناء سيحدث لو ادخلنا قيم نصية ليس فيها اي أرقام
    print("Invalid input. Please enter a valid number.")
except ZeroDivisionError: # هذا الاستثناء سيحدث لو كانت قيمة المدخل صفرا، حيث لا يجوز القسمة على صفر
    print("Cannot divide by zero.")
except Exception as e: # هذا استثناء عام لأي خطأ آخر قد يحدث أثناء التنفيذ
    print("An error occurred:", str(e))
else: # لو لم يحدث أي استثناء فإن هذا القسم سوف يقوم المفسر بتنفيذه
    print("Everything is fine! no error happend")
finally: # هذا القسم سيقوم المفسر بتنفيذه بعد أي استثناء أو في حالة عدم حدوث أي استثناء
    print("This line will always be executed")


3- الجمل التكرارية

3-1 جملة طالما while

جملة طالما تقوم بتنفيذ الجزء الخاص بها طالما كان الشرط صحيحا، أي طالما أن الشرط صحيحا، قم بتنفيذ هذه العمليات
وهي على النحو التالي:

while condition:
  # do something
  
وكمثال:

x = 0
while x < 10: # طالما أن قيمة المتغير أقل من 10
  print(x)
  x = x +1

 في هذا المثال سيطبع القيم من 0 حتى 9 

يمكن استخدام التعبيرات التالية داخل جملة طالما while أو جملة لأجل for التكرارية بشكل عام:
continue, break
جملة continue بمعنى استمر تقول للمترجم، اترك باقي الجملة التكرارية الحالية وانتقل للتكرار التالي عليها
فمثلا:

x = 0
while x < 10: # طالما أن قيمة المتغير أقل من 10
  x = x +1
  if x & 2 == 0: # لو كانت قيمة المتغير زوجية
    print(x)
  else: # القيمة فردية
    continue # اذهب للتكرار التالي
    
    
x = 0
while True: # استمر في التنفيذ دوما
  x = x +1
  if x < 10: # لو كانت قيمة المتغير أقل من 10
    print(x)
  else: # القيمة أكبر من 10
    break # اكسر التكرار واخرج منه


3-2 جملة لكل for

يختلف شكل التكرار باستخدام جملة لكل for في بايثون عن بعض اللغات الشهيرة كالسي والسي بلس بلس، إلا إنه ليس مفهوما جديدا، فهو موجود في لغات سابقة، فبدلا من وضع عداد له قيمة ونهاية وشرط للتكرار كما في لغة السي مثلا، تقوم جملة for في بايثون بزيارة كل عنصر موجود في متغير يحمل عناصر بداخله، فيمكن استخدام كلمة for مع متغير نصي (وهنا سيزور كل حرف في النص) أو قائمة list أو مجموعة set أو tuple ولكل عنصر سيكرر نفس الجملة المرتبطة به

فعلى سبيل المثال، الصيغ التالية لجملة for صحيحة ويمكن استخدامها

s='abcd'
for c in s: # تقرأ هكذا لكل حرف في المتغير النصي
  print(c) # قم بطباعته
  
 
l = [1, 2, 3]
for n in l: # تقرأ هكذا لكل عنصر في القائمة
  print(n) # قم بطباعته
    

t = (1, 2, 3)
for i in t: # تقرأ هكذا لكل عنصر في ال tuple
  print(i) # قم بطباعته
  
  
d = {"a":1, "b":2, "c":c}
for k in d.keys(): # تقرأ هكذا لكل مفتاح في القاموس
  print(k, d[k]) # اطبع المفتاح وقيمة المدخل عند هذا المفتاح
  
for i in range(0, 10): # تقرأ هكذا، لكل رقم ناتج من دالة التكرار
  print(i) # اطبع الأرقام من 0 حتى 9
  

4- استخدام واجهة البرمجة التطبيقية API 

كمثال من موقع الشات جي بي تي على استخدام API لقراءة تويتات مستخدم معين

أولا نقوم بتنصيب مكتبة tweepy عن طريق الأمر التالي

pip install tweepy


import tweepy

# Set up your Twitter API credentials
consumer_key = 'your_consumer_key'
consumer_secret = 'your_consumer_secret'
access_token = 'your_access_token'
access_token_secret = 'your_access_token_secret'

# Authenticate with Twitter
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)

# Create the API object
api = tweepy.API(auth)

# Retrieve tweets from the user's timeline
username = 'twitter_username'  # Replace with the username you want to retrieve tweets from
tweets = api.user_timeline(screen_name=username, count=10)

# Print the text of each tweet
for tweet in tweets:
    print(tweet.text)
    print('---')

مثال الاسبوع الثالث


import os
fileList = os.listdir("/home")
savedPath = os.getcwd()
os.chdir("/home/images")
for fileName in fileList:
  print(fileName)
  # os.rename(fileName, fileName.translate("0123456789"))
  os.chdir(savedPath)
  
  
  

تلخيص لدرس الأسبوع الرابع:


1- القسم الأول : الدوال Functions 

ما الدالة في لغة بايثون؟
بشكل عام سواء أكانت لغة بايثون أو أي لغة برمجة أخرى، يلعب مفهوم الدالة function دورا هاما جدا للأسباب التالية:
1- تنظيم الشفرة المصدرية source code للبرنامج، فبدلا من تكرار نفس التعليمات والجمل أكثر من مرة، يمكن وضعهم في دالة واستدعاء الدالة مباشرة، مما يجعل التعامل مع الشفرة المصدرية أسهل.
2- تجنب تكرار الوظائف وتحسين هيكلية البرنامج، فتجميع التعليمات والجمل والخطوات تحت اسم دالة مميز لها يجعل من السهل لاحقا تحسين هذه الدالة في مكان واحد أو حتى استبدالها بدالة لها نفس الاسم تقوم بنفس العمليات ولكن بشكل أكفأ.

كيفية تعريف دالة في البايثون:

نكتب الدالة في البايثون function definition على النحو التالي كمثال:

def add_numbers(first, second):
  i_sum = first + second
  print("Hello, I was called with ", first, second)
  return i_sum
  
ولاستدعاء الدالة function calling 

result = add_numbers(1, 2)
print(result)

لاحظ هنا أن اسم الدالة function name ينطبق عليه كل القيود والشروط لتسمية أسماء المتغيرات (راجع الدروس السابقة)


لاحظ هنا الفائدة المتمثلة في أن اسم الدالة add_numbers ممثلا لكل العمليات التي تتم داخل جسم الدالة function body، وفي هذا المثال نرى أن اسم الدالة متبوعا بقوس، وفيه تم تخصيص أن هذه الدالة تنتظر عند استدعائها من المستخدم قيمتين، وكل قيمة منهم لها اسم كما هو موضح، فلو قمنا باستدعاء الدالة بدون قيم كما في المثال التالي 

result = add_numers()

فإن مترجم البايثون سيطبع رسالة شبيهة بهذه

TypeError: add_numbers() missing 2 required positional arguments: 'first' and 'second

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

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

def move_step(direction, steps=1):
  print("Will move in direction: ", direction, ", with ", steps, " steps")

لاحظ أنه يمكنك استدعاء الدالة بهذه الصور المختلفة

move_step("EAST")
move_step("EAST", 10)

لكن لو استدعيت الدالة بدون اعطاء أي متغيرات على هذا النحو

move_step()

فإن مترجم البايثون  سيطبع رسالة خطأ شبيهة بهذه

TypeError: move_step() missing 1 required positional argument: 'direction'

أي أننا على الأقل علينا اعطاء متغير واحد عند استدعاء هذه الدالة

أيضا يمكنك استدعاء الدالة بقيم مباشرة كما فعلنا في الأمثلة السابقة، أو يمكنك استدعاء الدالة بقيم معرفة في متغيرات، فعلى سبيل المثال يمكنك القيام بالآتي

dir = "East"
stps = 100
move_steps(dir, stps)

لاحظ أنه في هذه الدالة move_step لم نقم بأرجاع أي قيمة من داخل الدالة، فلو قمنا بالتالي

x = move_steps("Up")
print(x)

سيطبع المترجم سطر فارغ

لو حاولنا معرفة نوع القيمة 

type(x)

سيطبع المترجم الجملة التالية

NoneType

أي أن القيمة الراجعة من الدالة غير معرفة ولا قيمة لها

أيضا لاحظ أن المتغيرات المعرفة داخل جسم الدالة، لا يمكن استدعاءها والتعامل معها خارج الدالة، فهي فقط معرفة داخل الدالة ومعزولة عن بقية البرنامج ولا تشكل أي تعارض مع أي متغيرات معرفة بنفس الاسم خارج الدالة، فعلى سبيل المثال، انظر لتعريف الدالة التالية التي تقوم بتربيع قيمة المتغير الآتي اليها من المستخدم

def square_me(num):
  result = num * num
  return result
  
الآن لنقم باستدعاء الدالة
  
square_me(10)

ولنحاول طباعة قيمة المتغير result المعرف داخل الدالة

print(result)

سنرى أن المفسر سيطبع جملة شبيهة بالتالي

NameError: name 'result' is not defined

هذا العزل مهم وضروري جدا لتجنب حدوث الأخطاء عند تكرار الأسماء المعرفة داخل الدوال

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

value = 10

def some_function():
  print(value)
  
في هذا المثال، نجد أن الدالة قادرة على قراؤة قيمة المتغير المعرف خارجها، وهو ما يعرف عموما في البرمجة بمفهوم global variables المتغيرات الشائعة، ويسهل أحيانا تمرير القيم بين الدوال المختلفة، لكن بشكل عام يفضل تجنب استخدام هذه الطريقة لما تتسبب فيه من أخطاء صعبة التصحيح، فأحيانا لا يكون واضحا أي دالة قامت بتغيير قيمة المتغير ومتى، وبسبب أن المتغير يمكن قراءته في كل الدوال،فقد يتسبب هذا في تغيير سلوك بعض الدوال وحدوث أخطاء غير مقصودة.


الآن لدينا طريقتان لتمرير المتغيرات arguments للدوال في لغة البايثون وهما:
1- الاستدعاء بتمرير القيمة call by value
ويحدث هذا عندما نضغ القيمة مباشرة في الدالة على النحو التالي

move_steps("East", 10)

أو لو كانت المتغيرات معرفة كمتغيرات من النوع الغير قابلة للتعديل (راجع الدرس المتعلق بهذا المفهوم) immutable وهي المتغيرات من النوع

int, float, string, tuple 

هذه المتغيرات يقوم المفسر بنسخ قيمها عند تمريرها للدالة، وأي تغيير يحدث في هذه المتغيرات، يظل داخل الدالة وليس له أي أثر خارجها


كمثال

def call_by_value_example(argument1, argument2):
  print('Step1: ', argument1, ' ', argument2)
  argument1 = 'New Value'
  argument2 = 100
  print('Step2: ', argument1, ' ', argument2)  
  

argument1 = 'Arg1'
argument2 = 10
call_by_value_example(argument1, argument2)
print(argument1, ' ', argument2)

سنجد أن المترجم سيطبع الجمل التالية

Step1:  Arg1   10 # في هذا السطر نرى القيمة الداخلة للدالة
Step2:  New Value   100 # نرى القيم التي قمنا بتغييرها داخل الدالة
Arg1   10 # لكن لاحظ هنا أن القيم الأصلية لازالت كما هي لم تتغير


2- الاستدعاء بتمرير المرجع call by reference
ويعني أننا عندما نقوم باستدعاء الدالة، فإن المتغير الممرر للدالة يتم مشاركته بين من قام بالاستدعاء والدالة المستدعاة وأي تعديل في قيمة هذا المتغير ستكون منظورة لمن قام بالاستدعاء، وبشكل عام المتغيرات القابلة للتعديل mutable يقوم مترجم البايثون بتمرير مرجع لها ولا يقوم بنسخ قيمها، وهذا المفهوم هام جدا لتسريع البرنامج بدلا من نسخ قيم كثيرة جدا واستهلاك وقت المعالج والذاكرة في نسخ القيم بين الدوال، فإن الاستدعاء بالمرجع يمثل تسريع للاستدعاء مما يجعل اداء البرنامج أفضل بشكل عام

بشكل عام كل المتغيرات من النوع القابل للتعديل mutable كمثال

list, set, dictionary

يقوم مترجم البايثون بتمرير مرجع reference لها

كمثال

def call_by_reference_example(argument1, argument2):
  print('Step1: ', argument1, ' ', argument2)
  argument1.append(5)
  argument2.append("World")
  print('Step2: ', argument1, ' ', argument2)  
  

argument1 = [1, 2, 3, 4]
argument2 = ["Hello"]
call_by_reference_example(argument1, argument2)
print(argument1, ' ', argument2)

سنجد أن المترجم سيطبع الجمل التالية

Step1:  [1, 2, 3, 4]   ['Hello']# في هذا السطر نرى القيمة الداخلة للدالة
Step2:  [1, 2, 3, 4, 5]   ['Hello', 'World']  # نرى القيم التي قمنا بتغييرها داخل الدالة
[1, 2, 3, 4, 5]   ['Hello', 'World'] # هنا نرى أن التغييرات ظهر على القيم خارج الدالة أيضا



2- القسم الثاني : البرمجة الشيئية Object Oriented Programming


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

في البداية تعرفنا على ان البرمجة بشكل عام تتكون من العناصر الثلاثة التالية:

1- وسيلة لتخزين واسترجاع المعلومات (الذاكرة) وهو ما تعرفنا عليه بمفهوم المتغيرات variables وأصنافها المختلفة أرقام وحروف ونصوص وقوائم وقواميس ... الخ (numbers, strings, tuple, list, dictionary , ... etc) وطريقة التعامل معها فهناك منها ما يمكن تغييره mutable, وما لا يمكن تغييره immutable، وقواعد تسمية المتغيرات، وكيفية التحويل ما بينها، ... الخ
2- وسيلة للاختيار (المقارنة بين القيم المختلفة) وهي وسائل المقارنة المختلفة أكبر من، أصغر من، يساوي، لا يساوي ... الخ ومعها الجمل الشرطية لو if وغير else وجمل المقارنة المنطقية أو و ولا or, and, not ... الخ
3- وسيلة للعد (الجمل التكرارية) وهي جمل لأجل for وطالما while وشاهدنا بعض الاستخدامات لها ... الخ

اذا لما الحاجة لتعلم مفهوم الدوال اذا؟ وما أهميته اذا كان بامكاني أن أبرمج أي شيء باستخدام هذه العناصر الثلاث السابقة؟

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

لكن ...


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

لذا ...

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


اذا ما علاقة مفهوم الدوال functions بمفهوم البرمجة الشيئية OOP اذا؟ 


قبل الاجابة على هذا السؤال، دعني أسألك أولا ما العلاقة التي تجمع بين جميع الكائنات الحية على ظهر كوكب الأرض بشكل عام؟ واصبر معي وتأمل قليلا قبل الاجابة على السؤال، وسترى كيف سنصل من هذه النتيجة لفهم البرمجة الشيئية OOP في بايثون


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


فعامل الكمبيوتر آلان كاي Alan Kay مخترع مفهوم البرمجة الشيئية OOPK، اقتبس هذا المفهوم من علم الأحياء كما يقول هنا في الاقتباس التالي:

"في وقت ما بعد نوفمبر من عام 1966 في كلية الهندسة بجامعة يوتا، عندما تأثرت ببرنامج Sketchpad (لإيفان سذرلاند)، ولغة البرمجة سيمولا  (Simula)، وتصميم شبكة أربانت (ARPAnet) وتصميم حاسوب Burroughs B5000 وخلفيتي الأكاديمية في علم الأحياء والرياضيات، فكرت في تصميم هيكلية للبرمجة. ربما في عام 1967 عندما سألني أحدهم عما أفعله، فقلت: "برمجة كائنية التوجه" (OOP).

كان المفهوم الأصلي يتكون من الأجزاء التالية:

 - فكرت في الكائن (Object) على أنه كالخلايا البيولوجية و/أو كأجهزة الحاسوب المنفردة المتصلة بالشبكة ، قادرة فقط على الاتصال فيما بينها بواسطة الرسائل (وهنا يتضح أن مفهوم الاتصال بين الكائنات عن طريق الرسائل ظهر مبكرًا في البداية، رغم أنه استغرق وقتا لوصوله لشكل مفيد في لغة البرمجة)." انتهى الاقتباس
 
 
 
هنا نرى أن الكائن Object ليس مجرد متغير أو مكان في الذاكرة فقط، بل أكثر من ذلك، ليصبح هذا المتغير كائنا Object، أضافت له لغة البرمجة اضافة الى مكانه في الذاكرة عدة عوامل جعلته كائنا وهي:
1- مجموعة من الدوال methods/functions أصبحت مربوطة به وجزءا منه، وهذه الدوال لا يمكن التعامل معها واستدعائها الا عن طريق الكائن نفسه.
2- مجموعة من الخصائص أو المتغيرات attributes مرتبطة به، معرفة فقط داخل هذا الكائن


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


بشكل عام الكائن Object ومفهوم البرمجة الشيئية OOP هي مجرد أداة لتنظيم البرنامج، وترتيب هيكله بحيث يصبح من السهل عليك اعادة استخدام هذه الكائنات في أماكن أخرى، ألا يذكرك هذا بمفهوم الدالة function؟ ألم تكن الدالة وسيلة لترتيب شفرة البرنامج بشكل يسهل استخدامه؟ الأمر نفسه هو ما نريده في البرمجة الشيئية OOP هي مجرد وسيلة لتنظيم وترتيب واعادة استخدام الكائن


بشكل عام البرمجة الشيئية تتميز بالآتي:

1- تجميع الدوال والبيانات المرتبطة بها في كائن واحد Abstraction

كمثال لو لدي كائن Object يمثل سيارة، فإن داخل هذا الكائن ستجد الدوال والبيانات المتعلقة بنوع السيارة والمصنع، وسعرها، ورقم اللوحة، وعدد المقاعد وكذا دوال لتصنيع كائن السيارة Car Object وتغيير البيانات كدالة لتغيير اسم المالك وتاريخ تجديد الرخصة ووو ... الخ، بدلا من وضع كل هذه البيانات والدوال في ملفات متفرقة، نضعها داخل كائن واحد ولنسمه مثلا سيارة Car ثم نبدأ في التعامل مع كائن واحد بدلا من التعامل مع ملفات أو دوال أو متغيرات عديدة في أكثر من مكان

2- وراثة الامكانيات بين الكائنات Inheritance

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

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



الآن بعد هذه المقدمة الطويلة، كيف يمكن لنا تعريف كائنات في لغة البايثون؟

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


كل أنواع البيانات التي مرت عليك في لغة البايثون الى الآن، كلها بلا استثناء هي كائنات ولها دوال وخصائص مرتبطة بها!


لا تصدقني؟! اذا حاول أن تجرب الأمر التالي في لغة البايثون

i=10

وماذا في ذلك؟ مجرد تعريف لمتغير من النوع الرقمي الصحيح، أين الكائن هنا؟!

جرب الأمر التالي

dir(i)
ستجد أن المترجم قد قام بطبع ما يشبه التالي

['__abs__',
 '__add__',
 '__and__',
 '__bool__',
 '__ceil__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__divmod__',
 '__doc__',
 '__eq__',
 '__float__',
 '__floor__',
 '__floordiv__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__index__',
 '__init__',
 '__init_subclass__',
 '__int__',
 '__invert__',
 '__le__',
 '__lshift__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__neg__',
 '__new__',
 '__or__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__rand__',
 '__rdivmod__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rfloordiv__',
 '__rlshift__',
 '__rmod__',
 '__rmul__',
 '__ror__',
 '__round__',
 '__rpow__',
 '__rrshift__',
 '__rshift__',
 '__rsub__',
 '__rtruediv__',
 '__rxor__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__sub__',
 '__subclasshook__',
 '__truediv__',
 '__trunc__',
 '__xor__',
 'as_integer_ratio',
 'bit_count',
 'bit_length',
 'conjugate',
 'denominator',
 'from_bytes',
 'imag',
 'numerator',
 'real',
 'to_bytes']


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


اقتنعت؟! حسنا لنصل الآن للنقطة التالية وهي كيفية تعريف كائن جديد في لغة البايثون


نقوم بذلك عن طريق كلمة جديدة في لغة البايثون وهي كلمة النوع class والشكل العام لاستخدامها يكون كالتالي كمثال لكائن لبرمجة سيارة فورد توروس

class FordTaurusCar:
    def __init__(self): # هذه الدالة تستخدم عند انشاء الكائن أول مرة
        self.Make = "Ford"
        self.Year = "1999"
        self.Model = "Taurus"
    def info(self):
        print("Car is: ", self.Make, ", of Year: ", self.Year, ", of Model: ", self.Model)

        

car = FordTaurusCar() # في هذا السطر سنقوم باستدعاء دالة __init__
car.info()

لو أحببت أن تمرر قيم للكائن عن انشائه، يمكنك القيام بذلك بعد تعديل تعريف الكائن على النحو التالي

class Car:
    def __init__(self, make, year, model): # هذه الدالة تستخدم عند انشاء الكائن أول مرة
        self.Make = make
        self.Year = year
        self.Model = model
    def info(self):
        print("Car is: ", self.Make, ", of Year: ", self.Year, ", of Model: ", self.Model)



car = Car("Toyota", 1999, "HiAce") # في هذا السطر سنقوم باستدعاء دالة __init__
car.info()


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


مثال الاسبوع الرابع: منسوخ من هنا:


import turtle
import math


def p_line(t, n, length, angle):
    """Draws n line segments."""
    for i in range(n):
        t.fd(length)
        t.lt(angle)


def polygon(t, n, length):
    """Draws a polygon with n sides."""
    angle = 360 / n
    p_line(t, n, length, angle)


def arc(t, r, angle):
    """Draws an arc with the given radius and angle."""
    arc_length = 2 * math.pi * r * abs(angle) / 360
    n = int(arc_length / 4) + 1
    step_length = arc_length / n
    step_angle = float(angle) / n

    # Before starting reduces, making a slight left turn.
    t.lt(step_angle / 2)
    p_line(t, n, step_length, step_angle)
    t.rt(step_angle / 2)


def petal(t, r, angle):
    """Draws a petal using two arcs."""
    for i in range(2):
        arc(t, r, angle)
        t.lt(180-angle)

def flower(t, n, r, angle, p):
    """Draws a flower with n petals."""
    for i in range(n):
        petal(t, r, angle)
        t.lt(p/n)

def leaf(t, r, angle, p):
    """Draws a leaf and fill it."""
    t.begin_fill() # Begin the fill process.
    t.down()
    flower(t, 1, 40, 80, 180)
    t.end_fill()


def draw():
    window = turtle.Screen()  # creat a screen
    window.bgcolor("blue")

    lucy = turtle.Turtle()
    lucy.shape("turtle")
    lucy.color("red")
    lucy.width(5)
    lucy.speed(0)

    # Drawing flower
    flower(lucy, 10, 40, 100, 360)

    # Drawing pedicel
    lucy.color("brown")
    lucy.rt(90)
    lucy.fd(200)

    # Drawing leaf
    lucy.rt(270)
    lucy.color("green")
    leaf(lucy, 40, 80, 180)
    lucy.ht()

if __name__ == "__main__":
    draw()
    turtle.mainloop()

الجمعة، 15 أبريل 2022

لمَ صمدت لغة البايثون في معركة الذكاء الصناعي؟

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

1-  بدايات شبه مبكرة 


في عام 1960 نشر جون مكارثي الجزء الأول من ورقة بحثية بعنوان "Recursive Functions of Symbolic Expressions and Their Computation by Machine" بالعربية تترجم ل "دوال ارتدادية للعبارات الرياضية وحسابهم بالآلة"، وفيها أوضح كيفية بناء لغة برمجة كاملة طبقا لمفاهيم آلان تورنج (أي أنها قادرة على حساب أي خوارزمية، وبالإنجليزية تسمى Turing complete) باستخدام مجموعة بسيطة ومحدودة من العمليات وتعريف ارتدادي للدوال (أي أن الدالة في تعريفها تقوم باستدعاء نفسها)، وسميت هذه اللغة ليسب (بالانجليزية LISP وهي اختصار لـ LISt Processor) وتعني معالج القائمة ولهذه التسمية سبب، فهي تعتبر كلا من الدوال والبيانات نفس الشيء، ففي ليسب تُعامل البيانات معاملة الدوال، فكلا منهم عبارة عن قائمة، والقائمة عبارة عن قوسين بينهما محتويات القائمة، وبذا يكون أبسط برامجها هو القائمة الفارغة (). 

احتوت اللغة على وسيلة لتعريف دالة، ومجموعة من الدوال البسيطة للتعامل مع القائمة كمثال دالة للحصول على أول عنصر في القائمة (CAR)، ودالة أخرى للحصول على بقية العناصر (CDR)، ولاتخاذ قرار بين نتيجتين يمكن القيام بذلك عن طريق دالة تقوم بالمقارنة وإعادة قيمة إحداهم أو استدعاء دالة أخرى (وهو ما يعادل استخدام إذا أو If الشرطية في لغات البرمجة الأخرى) ويقوم التعريف الارتدادي (recursive) للدوال بدور عملية التكرار في لغات البرمجة الأخرى، وهكذا نرى أن كل شيء في هذه اللغة يتمحور حول مفهوم الدوال واستدعاءها. وباستخدام هذه التعريفات الأولية البسيطة بيَّن مكارثي في ورقته البحثية أنه يمكن استخدام هذه اللغة البسيطة لتنفيذ أي خوارزمية. 

لم تكن ليسب مجرد فكرة في تلك الورقة البحثية، بل كانت لغة مستخدمة بالفعل في مختبرات إم. آي. تي  للذكاء الاصطناعي، فقد صممها مكارثي عام 1958، قبل كتابة الورقة البحثية بعامين، ولم تكن أول لغة تُستخدم للعمل في مجال الذكاء الصناعي فقد سبقتها لغة أخرى باسم "لغة معالجة المعلومات" IPL (بالانجليزية Information Processing Language)، وسبقت ليسب في استخدامها للقوائم والدوال الارتدادية (recusrive functions). 

اعتمدت ليسب وملهمتها من قبلها IPL على تصور معين ساد لفترة أبحاث الذكاء الصناعي، وهذا التصور وصل إليه كلا من العالمين ألان نيويل وهربرت سيمون (وهم أيضا من صمموا لغة IPL)، ويقوم هذا التصور على فكرة أن الذكاء في جوهره هو عمليات يقوم بها العقل على ما يرمز للأشياء (سمي هذا النظام باسم physical symbol system أي "نظام الرمز المادي" والمادي/أو الفزيائي تشير هنا لأن الرمز المستخدم يشير لشيء واقعي له خصائص يمكن قياسها والتعامل معها)، ولتتعامل لغة IPL مع هذه الرموز، استخدموا القائمة للتخزين والاسترجاع، وكذا الدوال الارتدادية (recursive functions)، ومن ثم جاءت ليسب من بعدها ولأنها قائمة على نفس المبدأ، استخدمت القائمة وتعريف الدوال الارتدادية وزادت عليهم خصائص أخرى عديدة.

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

وبسبب بساطة تصميمها، كان من السهل كتابة مُفَسِر (Interpreter) لها في أي لغة أخرى، فكر في الأمر لدقيقة، كل ما تحتاجه ليكون لديك لغة ليسب هو وسيلة لإنشاء قائمة (List) تضع فيها العناصر وتسترجعها مرة أخرى، ووسيلة لتعريف دوال يمكنك تطبيقها على تلك العناصر، نظريا الأمر أسهل بكثير من كتابة مُفَسِر لمعظم اللغات الأخرى والذي يتطلب في الغالب تحليل النص الموجود ومضاهاته ببنية (syntax) مُعَرَّفة مُسبقا والتأكد من توافقها مع قواعد (grammar) اللغة الموضوعة مُسبقا، لكن في ليسب لا تحتاج لكل هذا، فالأوامر والبيانات هما نفس الشيء، وهيكل البيانات الوحيد الذي ستحتاج إليه هو القائمة وضف على هذا وسيلة لتعريف الدوال ارتدايا ووسيلة لتطبيقها على البيانات بصورة ارتدادية وستكون لديك لغة ليسب جاهزة للاستخدام، وهناك بالفعل العديد من هذه المحاولات ومنها ما هو مكتوب في لغة البايثون، بل هناك تنافس على أقل عدد السطور المستخدمة لتصميم مُفَسِر للغة ليسب.

ولكن قبل الاسترسال في الحديث، يحسن بنا أن نجيب عن سؤال مهم جدا، ألا وهو: لمَ احتاج الذكاء الصناعي للغة برمجة؟

وللإجابة على هذا السؤال البسيط، يحسن بنا أن نلقي نظرة سريعة على تاريخ فكرة آلة الكمبيوتر بشكل عام، ففيها تكمن الإجابة، أو أجزاء مهمة منها.

شعار لغة ليسب
شعار لغة ليسب



2 -  التاريخ يكرر نفسه


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

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

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

مع بدايات القرن العشرين والاستخدام الواسع للكهرباء في العديد من الآلات المختلفة، بدأت تظهر بعض المحاولات هنا وهناك لتحسين آلية عمل الآلات الحاسبة لجعلها أكثر مرونة وقادرة على القيام بعمليات مختلفة، ورغم أهمية بعض هذه المحاولات إلا أنها لم تشكل طفرة كبيرة في طريقة عمل هذه الآلات، رغم وجود الأساس النظري الذي يمكن أن تُبنى عليه آلة قابلة للبرمجة، ففي عام 1679 توصل لايبنتز لفكرة التمثيل الثنائي للأرقام، وفي عام 1847 نشر جورج بول أساسيات الجبر البولياني في كتابه "التحليل الرياضي للمنطق" وهو ما استخدمه لاحقا في كتاب "بحث قوانين التفكير" وبواسطته حصلنا لأول مرة على شكل رياضي للعمليات المنطقية، وفي عام 1936 نشر آلان تورينج ورقته البحثية التي وضح فيها رياضيا أنه يمكن القيام بحساب أي رقم عن طريق آلة نظرية (سميت آلة تورينج) (وهي نفس النتيجة التي توصل إليها ألونزوا تشرش في حساب اللامدا (Lambda calculus)) طالما يمكننا التعبير عن هذا الحساب بخطوات محددة (خوارزمية الحساب)، وهي نتائج مهدت بشكل كبير لظهور الحاسب الآلي كما نعرفه اليوم، فأهميتها تنبع من أنه يمكن التعبير عن هذه الحسابات المختلفة على شكل خطوات منطقية، وهذه النتائج يمكن بعد ذلك تغذيتها لدوال لتقوم بحسابات أخرى لتخرج نتائج أو لتستدعي دوال أخرى للقيام بها (وحتى تستدعي نفسها مرة أخرى للاستمرار في الحساب)، باختصار بدت ملامح طريق من خلاله يمكن السير عليه أو وضع الآلة عليه لتسير بمفردها لأول مرة مُسلحة بنظام عام للتعامل مع البيانات، هذا النظام سيطلق عليه لاحقا البرمجة، ففي عام 1941 اخترع كونراد سوزه أول كمبيوتر رقمي (يستخدم الأرقام الثنائية) وقابل للبرمجة بشكل عام (أي كامل طبقا لنظرية تورينج) وبعده توالت التحسينات والتطويرات المختلفة التي جعلت من تنفيذ عملية حسابية يستغرق ساعات لدقائق ثم ثوان وأجزاء منها لاحقا، لكن بشكل عام فُتِحَ الطريق للتطوير، فتحته النتائج النظرية التي بينت إمكانية حدوثه، وفتحه أكثر وأكثر الاكتشافات الكهربائية والالكترونية اللاحقة التي اختصرت الزمن والمساحة لأبعاد يصعب على العقل البشري قديما تخيل وجودها أو إمكانية قياسها.

بظهور الآلة القابلة للبرمجة، ظهرت الحاجة الملحة لوسيلة برمجة تمكن الإنسان من تغيير سلوك الآلة حسب احتياجاته المختلفة، فالآن وبعد أن كانت تتم الحسابات بشكل يدوي وحتى باستخدام الآلة، أمكن لأول مرة تغذية الألة بالتعليمات وتركها لتقوم بما ينبغي عليها القيام به للوصول للناتج، فظهرت وسائل شتى للتواصل مع الآلة شكلت بدايات "لغات البرمجة"، وتطورت الآلة لتشمل وسيلة تخزين دائمة يمكن من خلالها الاحتفاظ بالمعلومات (وأيضا تعليمات التشغيل) ووسيلة تخزين مؤقتة تستخدم أثناء التشغيل لتسريع العمليات المختلفة المفترض أن تقوم بها، واحتوت الآلة على عناصر تشبه لحد ما من اخترعوها، فالآلة الأن بها وحدة لتنفيذ العمليات الحسابية والمنطقية والتعليمات المختلفة (المعالج المركزي) تشبه لحد ما المخ البشري، وبها وحدة تخزين تشبه الذاكرة البشرية، وتستطيع أن تستقبل تعليمات لتنفيذها (كالبشر) وأن تخبر عن نتائج هذه التعليمات (أيضا كالبشر)، وبدا أن هناك شيء جديد يلوح في الأفق لم يكن ممكنا من قبل، وبدأت التساؤلات تظهر، هل يمكن للآلة أن تفكر، وهل الانسان آلة تمت برمجتها مسبقا؟ وإن كان الانسان استطاع تصميم آلة تشبهه لحد ما، فما الذي ينقص هذا الاختراع الجديد حتى يستطيع التفكير؟


Pilot ACE
Pilot ACE




3 -  ربيع الذكاء الصناعي وشتاءه


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

كان من بين الأبحاث المبكرة في هذا المجال، بحث نشره وارين ماكلا ووالتر بتس في عام 1943 بعنوان "A LOGICAL CALCULUS OF THE IDEAS IMMANENT IN NERVOUS ACTIVITY" (حساب منطقي للأفكار  الموجودة في نشاط عصبي) وفيها استخدموا نموذج مبسط يعتمد على أن النشاط العصبي إما يكون موجودا بشكل كامل أو غير موجود، لذا فيمكن نمذجة هذا النشاط العصبي بشكل رياضي منطقي (أو تحديدا بحساب القضايا، بالإنجليزية propositional logic)، وعليه وصفوا في هذه الورقة شبكات من الخلايا العصبية وكل خلية لها مشابك عصبية (synapse) وتتصل ببعضها البعض عن طريقها، ولتصل الخلية العصبية لمرحلة الإثارة فإن عددا معينا من تلك المشابك يجب أن يُثَار أولا، ووصفوا أشكالا مختلفة للتشبيك بين تلك الخلايا العصبية وكيف يمكن وصف سلوكها عن طريق العلاقات المنطقية بين مشابكها العصبية، واقترحوا أيضا قابلية بعض الشبكات للتعلم (وهنا التعلم في هذا السياق يشير إلى استجابة المشبك العصبي لمستوى تحفيزي لم يكن يستجيب له سابقا) وذلك اذا توافرت ظروف معينة في تصميم الشبكة العصبية (اقترحوا أن الليفة العصبية لو أُثيرت وتزامن ذلك مع إطلاق العصبون لشحنة، فإن الليفة ستكون قادرة على إثارة العصبون التالي لها وبالتالي ستشكل مشبك عصبي جديد). في عام 1949 عرض عالم النفس دونالد هيب في كتابه (Organization of Behavior) نظرية عن زيادة كفاءة المشابك العصبية، إذا أثر تحفيز مستمر أو متكرر في تركيب ونمو خليتين متجاورتين بمرور الوقت، فإن نوع من التحفيز والاستجابة المتبادلة بينهما سينشأ (والتعلم هنا يشير لنشوء مسار مشبكي جديد لم يكن موجودا في السابق وجاء كاستجابة لنشاط سابق). وبناءا على هذه النظريات قام مارفن منسكي ودين إدموندز ببناء أول آلة حاسبة بنظام الشبكات العصبية (Stochastic neural analog reinforcement calculator) في عام 1950، وفي نفس العام نشر تورينج بحث بعنوان "COMPUTING MACHINERY AND INTELLIGENCE" (آلية الحساب والذكاء) وفيها عرض اختباره الشهير لقياس ذكاء الآلة. وفي عام 1956 ظهر أول برنامج ذكاء صناعي حاسوبي باسم "Logic Theorist" صممه ألان نويل وهربرت سيمون وكليف شو، ولتصميم هذا البرنامج، قام مؤلفوه بإختراع لغة برمجة جديدة عرفت باسم IPL (لغة معالجة المعلومات Information Processing Language) واحتوت على مفاهيم ستؤثر لاحقا على تصميم اللغات التي ستليها كلغة ليسب البرمجية، واستطاع هذا البرنامج البرهنة على عدد من القضايا المنطقية (38 من أصل 52) من كتاب أسس الرياضيات لراسل ووايتهيد. لاحقا في عام 1958 صمم مكارثي لغة ليسب واستخدمها في الأبحاث والتجارب التي أجروها في مختبر إم. آي. تي للذكاء الصناعي.

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

اصطدمت هذه الأبحاث والمحاولات ببعض العقبات، فالمحاولات التي تمت مثلا أثناء الحرب الباردة لترجمة اللغة الروسية فشلت بسبب أنها لم تأخذ سياق الحديث في الاعتبار ولم يكفي مجرد استبدال كلمة بأخرى، بل اكتشف الباحثون أنه لابد من وجود سياق معرفي للترجمة وإلا فلن تحقق النتائج المرجوة منها. أيضا بسبب اعتماد برامج الذكاء الصناعي المبكرة على استراتيجية تجريب الكثير من التباديل للوصول لحل المشكلة، ولم تكن مشكلة في البداية فالمشكلات التي كانت تحاول تلك البرامج حلها كانت محدودة العدد (التعرف على على عدد معين من الأشكال ... الخ)، وظل الاعتقاد سائدا أنه للتعرف على أعداد أكبر أو لرفع كفاءة البرنامج يكفي زيادة موارد الآلة، ولكن سرعان ما ظهر خطأ هذه الفرضية في برامج حاولت التعامل مع أعدد أكبر مما صممت له في البداية، صعوبة أخرى أشار اليها منسكي وبابير في كتابهم بيرسيبترون (Perceptron) عام 1969، عندما أوضحا استحالة تدريب نوع بسيط من البيرسيبترون (له مُدْخَلان فقط) لتمييز اختلاف قيم كل مُدْخَل، وربما تسبب هذا لاحقا في انخفاض ثم توقف تمويل الأبحاث في الشبكات العصبية لفترة، والتي ستزدهر لاحقا في فترة الثمانيات من القرن العشرين بظهور خوارزميات التعلم بالانتشار الخلفي (Backpropagation).

ظهر لنظم تمثيل المعرفة استخدامات تجارية واسعة النطاق تسببت في حدوث إقبال كبير عليها، فظهر ما يسمى بالأنظمة الخبيرة (expert systems) وغطت مجالات حيوية كالطب حيث ساعدت في عمليات التشخيص، ومجال فهم اللغات البشرية، ومجال البيع والشراء في أنظمة التعامل مع العملاء، وبسبب انتشارها الواسع ظهرت لغات برمجة مخصصة لتلك الأنظمة كلغة البرولوج (Prolog) وعائلة لغات البلانار (PLANNER). للتعرف على حجم انتشار وتأثير الأنظمة الخبيرة يكفيك أن تعرف أنه في عام 1988 وفر نظام R1 الخبير ما يقارب 40 مليون دولارا لشركة ديجيتال DEC، وبحلول عام 1988 أصبح لدى مجموعة الذكاء الصناعي لشركة ديجيتال 40 نظاما خبيرا قيد الاستعمال وآخرون في طريقهم للاستعمال، وكان لشركة دبونت للكيماويات 100 نظام خبير و500 قيد التطوير وفروا على الشركة ما يقارب 10 مليون دولار سنويا، تقريبا كان لدى كل شركة كبرى في الولايات المتحدة مجموعتها الخاصة لأبحاث الذكاء الصناعي وكانت إما تستخدم نظاما خبيرا أو تعمل على تطوير إحداها، وباختصار شديد نمى سوق الذكاء الصناعي من بضع ملايين من الدولارات في عام 1980 لبلايين منها في عام 1988، من ضمنها كانت هناك شركات لتطوير نظم خبيرة وروبوتات وأنظمة روية وعتاد وبرامج مخصص لهذه الأغراض.

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

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

لكن أين موقع لغة البايثون من كل هذه التفاصيل؟ 

من بحث ماكلا وبتس عن الشبكات العصبية عام 1943.
من بحث ماكلا وبتس عن الشبكات العصبية عام 1943.




* ملحوظة: استفدت بشكل كبير من الفصل الأول من كتاب "Artificial Intelligence A Modern Approach" لمؤلفيه Stuart J. Russell و Peter Norvig، الطبعة الثالثة وبخاصة الجزء المعنون "THE HISTORY OF ARTICIAL INTELLIGENCE" بدءا من ص 16.

4- ليس كل صغير ضعيف


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

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

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

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

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

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

في أوائل التسعينات من القرن العشرين أصدر جيدو فان روسم أول نسخة من لغة البايثون، والتي اعتمدت مبادئ شبيهة بتلك التي اعتمدها نظام التشغيل يونكس، فالبساطة في الاستخدام كان أحد أهم دعائم هذه اللغة، كما احتوت على دعم للبرمجة كائنية التوجه (Object-oriented programming) وكذا دعمت البرمجة الوظيفية (Functional programming) بخصائص مقتبسة من لغة الليسب، كما احتوت أيضا على وسيلة لتوسعة اللغة بإضافة مكتبات خارجية لها وتوسعة اللغة عن طريق الإضافات التي يمكن كتابتها بلغة أخرى كالسي مثلا.

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

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

دينيس ريتشي وكين تومبسون (مطورو نظام اليونكس ولغة السي)
دينيس ريتشي وكين تومبسون (مطورو نظام اليونكس ولغة السي)