الاثنين، 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()