القائمة الرئيسية

الصفحات

قواعد البيانات و تعدد المهام في بايثون

قواعد البيانات في بايثون

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

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


معلومة تقنية

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



ملاحظة

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

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

تنصيب الحزمة mysql-connector في بايثون

للتعامل مع قواعد بيانات MySQL يجب تنصيب الحزمة mysql-connector المخصصة للتعامل معها.
لذلك إفتح موجه الأوامر في PyCharm و أكتب الأمر التالي.

pip install mysql-connector
	

بعد تنصيب الحزمة mysql-connector بنجاح تصبح قادر على تضمينها و استخدام الدوال الموجودة فيها.

الإتصال بقواعد بيانات MySQL في بايثون

أول شيء يجب أن تفعله للإتصال بقاعدة البيانات هو تضمين الموديول mysql.connector من الحزمة mysql-connector كالتالي.

import mysql.connector
	

الآن أصبح بإمكانك استخدام الدوال connect(), cursor(), execute() و غيرهم من الدوال الجاهزة في هذا الموديول للتعامل مع قواعد البيانات.



الدالة connect()

هذه الدالة تستخدم لإنشاء كائن نوعه MySQLConnection يسمح لنا بالإتصال بقاعدة البيانات. و هي مبنية كالتالي.

mysql.connector.connect(host='', user='', passwd='', database='')
	

  • الباراميتر host نمرر لها المكان الموجود عليه خادم قاعدة البيانات.

  • الباراميتر user نمرر له إسم المستخدم الذي سنتعامل من خلاله مع قاعدة البيانات.

  • الباراميتر passwd نمرر له كلمة مرور المستخدم الذي سنتعامل من خلاله مع قاعدة البيانات.

  • الباراميتر database نمرر له إسم قاعدة البيانات التي ننوي التعامل معها.



ملاحظة

المعلومات الإفتراضي للإتصال بقواعد بيانات MySQL في حال لم تقم أنت بتغييرها هي التالية:

  • إسم المستخدم الإفتراضي هو 'root'

  • المستخدم الإفتراضي ليس له كلمة مرور, أي كلمة مروره هي ''

  • في حال كانت قاعدة البيانات موجودة على حاسوبك, فإن قيمة الباراميتر host ستكون 'localhost'

في كل الأمثلة التي سنقوم فيها بالإتصال بقواعد البيانات سنستخدم هذه المعلومات لإنشاء الإتصال.



في المثال التالي قمنا بتضمين الحزمة mysql-connector, ثم قمنا باستدعاء الدالة connect() لإنشاء كائن يسمح لنا بالإتصال بخادم قواعد البيانات.
بعدها قمنا بعرض هذا الكائن للتأكد فقط منه قد تم إنشاء هذا الكائن في الذاكرة.

مثال

Test.py
# connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		host='localhost',
		user='root',
		passwd=''
		)

		# كما هو للتأكد فقط من أن معلومات الإتصال بخادم قواعد البيانات صحيحة لإنها إن لم تكن كذلك سيظهر خطأ db هنا قمنا بطباعة الكائن
		print(db)
	  

سنحصل على نتيجة تشبه النتيجة التالية عند تشغيل الملف Test.

<mysql.connector.connection.MySQLConnection object at 0x012A18F0>


الدالة cursor()

هذه الدالة تستدعى من الكائن الذي ترجعه الدالة connect(), و هي ترجع كائن نوعه MySQLCursor.
كائن الـ MySQLCursor يحتوي على دوال جاهزة يمكن استخدامها للأسباب التالية:

  • لإرسال إستعلامات ( Queries ) إلى خادم قاعدة البيانات.

  • للحصول على النتائج ( Results ) التي يرجعها خادم قاعدة البيانات بعد أن يتم تنفيذ الإستعلام.

لا تقلق, ستتعلم طريقة استخدامها لاحقاً من الأمثلة.



الدالة execute(operation)

هذه الدالة تستدعى من الكائن الذي ترجعه الدالة cursor(), من أجل إرسال إستعلامات إلى خادم قاعدة البيانات.
مكان البارميتر operation نمرر نص يمثل الإستعلام الذي نريد إرساله إلى خادم قاعدة البيانات.

لا تقلق, ستتعلم طريقة استخدامها لاحقاً من الأمثلة.

أمثلة شاملة حول التعامل مع قواعد البيانات في بايثون

ستتعلم من الأمثلة التالية طريقة إنشاء قاعدة بيانات بسيطة إسمها ( company ), ثم سننشئ جدول لتخزين بيانات الموظفين إسمه ( employee ).
و سنفترض أن كل موظف له إسم ( name ), يملك رقم هاتف ( phone ), و عنده رقم تعرفة ( id ) خاص فيه.

الأمثلة موضوعة بشكل مترابط و دقيق بحيث تعلمك طريقة إنشاء قاعدة البيانات, إنشاء جداول فيها, تعديل حقول الجدول, تعبئة الجدوال بالبيانات, تعديل بيانات الجدول إلخ..


 طريقة إنشاء قاعدة بيانات جديدة في بايثون

المثال التالي يعلمك طريقة إنشاء قاعدة بيانات جديدة إسمها company.

مثال

Test.py
                    # connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect() هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost'
		)

		# cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# و تمرير نص الإستعلام الذي يقضي بإنشاء قاعدة بيانات لها حتى يتم تنفيذه execute() هنا قمنا باستدعاء الدالة
		cursor.execute('CREATE DATABASE company')

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

إذا لم يظهر لك أي خطأ بعد تشغيل الملف Test فهذا يعني أنه تم إنشاء قاعدة البيانات company بنجاح.

 طريقة إنشاء جدول في قاعدة البيانات في بايثون

المثال التالي يعلمك طريقة إنشاء جدول إسمه employee في قاعدة البيانات company التي أنشأناها في المثال الأول.

مثال

Test.py
                    # connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# 'company' حتى ترجع كائن يسمح لنا بالإتصال بقاعدة البيانات connect() هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# يسمح لنا بالتعامل مع قاعدة البيانات cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# في قاعدة البيانات 'employee' و تمرير نص الإستعلام الذي يقضي بإنشاء جدول إسمه execute() هنا قمنا باستدعاء الدالة
		cursor.execute('CREATE TABLE employee (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255))')

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

إذا لم يظهر لك أي خطأ بعد تشغيل الملف Test فهذا يعني أنه تم إنشاء الجدول employee بنجاح في قاعدة البيانات company.

 طريقة إضافة عامود جديد في الجدول في بايثون

المثال التالي يعلمك طريقة إضافة عامود جديد إسمه phone في الجدول employee الذي قمنا بإنشاءه في المثال الثاني.

مثال

Test.py
                    # connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# 'company' حتى ترجع كائن يسمح لنا بالإتصال بقاعدة البيانات connect() هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# يسمح لنا بالتعامل مع قاعدة البيانات cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# 'employee' في الجدول 'phone' و تمرير نص الإستعلام الذي يقضي بإضافة عامود جديد إسمه execute() هنا قمنا باستدعاء الدالة
		cursor.execute("ALTER TABLE employee ADD COLUMN phone VARCHAR(30)")

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

إذا لم يظهر لك أي خطأ بعد تشغيل الملف Test فهذا يعني أنه تم إضافة عامود إسمه phone بنجاح في الجدول employee.

 طريقة إضافة سطر جديد في الجدول في بايثون

المثال التالي يعلمك طريقة إضافة سطر ( أي سجل ) في الجدول employee الذي قمنا بإنشاءه في الأمثلة السابقة.
المعلومات التي سنضيفها على سطر في الجدول, سنقوم بتجهيزها بشكل مرتب في tuple.


معلومة تقنية

لحفظ أي معلومات قمت بإضافتها, تعديلها أو مسحها من أي جدول في قاعدة البيانات, يجب أن تستدعي الدالة commit() لحفظ التغيرات.


مثال

Test.py
                    # mysql.connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect() هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# يسمح لنا بالتعامل مع قاعدة البيانات cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# 'employee' وضعنا فيه نص الإستعلام الذي يسمح بإضافة سطر جديد في الجدول sql المتغير
		sql = 'INSERT INTO employee(name, phone) values (%s, %s)'

		# يمثل القيم التي ستوضع بالترتيب في الجدول tuple عبارة عن val الكائن
		val = ('Ahmad', '96101200155')

		# sql و تمرير نص الإستعلام المخزن في المتغير execute() هنا قمنا باستدعاء الدالة
		# val و من ثم القيم التي سيتم دمجها مع نص الإستعلام و التي قمنا بتخزينها في الكائن
		cursor.execute(sql, val)

		# لحفظ التغيرات التي تم إجراءها في قاعدة البيانات commit() هنا قمنا باستدعاء الدالة
		db.commit()

		# هنا قمنا بطباعة عدد الأسطر التي تم إضافتها في الجدول بسبب الإستعلام الذي تم إرساله سابقاً
		print(cursor.rowcount, 'record(s) inserted')

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

إذا لم يظهر لك أي خطأ بعد تشغيل الملف Test فهذا يعني أنه تم إضافة سطر جديد في الجدول employee.

أيضاً سيتم طباعة الجملة التالية في الكونسول.

1 record(s) inserted


معلومة تقنية

قبل إرسال الإستعلام إلى قاعدة البيانات, قامت الدالة execute() بدمج نص الإستعلام و القيم التي مررناها لها كالتالي.

                  INSERT INTO employee(name, phone) values ('Ahmad', '9610200155')
	

 طريقة إضافة عدة أسطر في الجدول في بايثون

المثال التالي يعلمك طريقة إضافة عدة أسطر ( سجلات ) في الجدول employee الذي قمنا بإنشاءه في الأمثلة السابقة دفعة واحدة.

لتمرير مجموعة أسطر للدالة executemany(), سنقوم بإنشاء list كل عنصر فيه عبارة عن tuple.
و هكذا سنقوم بتجهيز المعلومات التي سيتم تخزينها بشكل مرتب في.


معلومة تقنية

لحفظ أي معلومات قمت بإضافتها, تعديلها أو مسحها من أي جدول في قاعدة البيانات, يجب أن تستدعي الدالة commit() لحفظ التغيرات.


مثال

Test.py
                    # mysql.connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect() هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# يسمح لنا بالتعامل مع قاعدة البيانات cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# 'employee' وضعنا فيه نص الإستعلام الذي يسمح بإضافة سطر جديد في الجدول sql المتغير
		sql = 'INSERT INTO employee(name, phone) values (%s, %s)'

		# يمثل الأسطر و القيم التي ستوضع بالترتيب في الجدول tuple عبارة عن val الكائن
		val = [
		('Rami', '96170188201'),
		('Sara', '96103200155'),
		('Rola', '96176554235'),
		('John', '96178665711'),
		('Nada', '96171004321')
		]

		# sql و تمرير نص الإستعلام المخزن في المتغير execute() هنا قمنا باستدعاء الدالة
		# val و من ثم القيم التي سيتم دمجها مع نص الإستعلام و التي قمنا بتخزينها في الكائن
		cursor.executemany(sql, val)

		# لحفظ التغيرات التي تم إجراءها في قاعدة البيانات commit() هنا قمنا باستدعاء الدالة
		db.commit()

		# هنا قمنا بطباعة عدد الأسطر التي تم إضافتها في الجدول بسبب الإستعلام الذي تم إرساله سابقاً
		print(cursor.rowcount, 'record(s) inserted')

		# الذي تم وضعه بشكل تلقائي لآخر عنصر قمنا بإضافته ( id ) هنا قمنا بطباعة رقم التعرفة
		print('Last row id is', cursor.lastrowid)

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

إذا لم يظهر لك أي خطأ بعد تشغيل الملف Test فهذا يعني أنه تم إضافة 5 أسطر جديدة في الجدول employee.

أيضاً سيتم طباعة الجملة التالية في الكونسول.

5 record(s) inserted     --> هذا السطر يعني أنه تم إضافة 5 أسطر جديدة في الجدول
Last row id is 6       --> رقم تعرفة آخر موظف أضفناه يساوي 6 لأنه السطر السادس في الجدول


معلومة تقنية

قبل إرسال الإستعلام إلى قاعدة البيانات, قامت الدالة executemany() بدمج نص الإستعلام و القيم التي مررناها لها كالتالي.

                  INSERT INTO employee(name, phone) values ('Rami', '96170188201');
	  INSERT INTO employee(name, phone) values ('Sara', '96103200155');
	  INSERT INTO employee(name, phone) values ('Rola', '96176554235');
	  INSERT INTO employee(name, phone) values ('John', '96178665711');
	  INSERT INTO employee(name, phone) values ('Nada', '96171004321');
	

 طريقة جلب كل البيانات المخزنة في جدول في بايثون

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

المثال الأول

Test.py
                    # connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# 'employee' و تمرير نص الإستعلام الذي يقضي بجلب كل معلومات الموظفين المخزنة في الجدول execute() هنا قمنا باستدعاء الدالة
		cursor.execute('SELECT * FROM employee')

		# حتى ترجع كل المعلومات التي أرجعها الإستعلام fetchall() هنا قمنا باستدعاء الدالة
		# result بعدها قمنا بتخزين كل المعلومات التي تم إرجاعها في الكائن
		# يمثل سطر في الجدول tuple كل عنصر فيه عبارة عن list عبارة عن result الكائن
		result = cursor.fetchall()

		# أي ترجع سطر جديد من الجدول في كل دورة .result موجود في الكائن tuple في كل دورة ترجع for هنا قمنا بإنشاء حلقة
		# phone و name ,id :يتألف من ثلاث عناصر tuple السطر الذي سيتم إرجاعه, طبعناه كما هو.. لاحظ أنه عبارة عن
		for row in result:
		print(row)

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

ستحصل على النتيجة التالية عند تشغيل الملف Test إذا قمت بتخزين نفس البيانات التي قمنا بإدخالها في الأمثلة السابقة.

(1, 'Ahmad', '96101200155')
(2, 'Rami', '96170188201')
(3, 'Sara', '96103200155')
(4, 'Rola', '96176554235')
(5, 'John', '96178665711')
(6, 'Nada', '96171004321')


المثال التالي يعلمك طريقة تحديد الأعمدة التي تريد عرضها بناء على رقم الـ Index الخاص بكل عامود.
فعلياً, بما أن كل سطر سنجلبه من الجدول سيكون عبارة عن tuple تتألف من ثلاث عناصر, سنقوم بعرض هذه العناصر بالشكل الذي نريده.

المثال الثاني

Test.py
                    # connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# 'employee' و تمرير نص الإستعلام الذي يقضي بجلب كل معلومات الموظفين المخزنة في الجدول execute() هنا قمنا باستدعاء الدالة
		cursor.execute('SELECT * FROM employee')

		# حتى ترجع كل المعلومات التي أرجعها الإستعلام fetchall() هنا قمنا باستدعاء الدالة
		# result بعدها قمنا بتخزين كل المعلومات التي تم إرجاعها في الكائن
		# يمثل سطر في الجدول tuple كل عنصر فيه عبارة عن list عبارة عن result الكائن
		result = cursor.fetchall()

		# و من ثم تعرض القيم الثلاثة الموجودة فيها بشكل مرتب result موجود في الكائن tuple في كل دورة ترجع for هنا قمنا بإنشاء حلقة
		for row in result:
		print('Id:',    row[0])
		print('Name:',  row[1])
		print('Phone:', row[2])
		print('------------------')

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

ستحصل على النتيجة التالية عند تشغيل الملف Test إذا قمت بتخزين نفس البيانات التي قمنا بإدخالها في الأمثلة السابقة.

Id: 1
Name: Ahmad
Phone: 96101200155
------------------
Id: 2
Name: Rami
Phone: 96170188201
------------------
Id: 3
Name: Sara
Phone: 96103200155
------------------
Id: 4
Name: Rola
Phone: 96176554235
------------------
Id: 5
Name: John
Phone: 96178665711
------------------
Id: 6
Name: Nada
Phone: 96171004321
------------------


المثال التالي يعلمك طريقة تحديد الأعمدة التي تريد عرضها بناءاً على أسماء الأعمدة الموجودة فعلياً في الجدول.
هنا سنحدد للدالة cursor() أن كل سطر سيتم إرجاعه سيكون عبارة عن dict, عندها بشكل تلقائي سيتم وضع إسم كل عامود في الجدول كمفتاح, و القيم المخزنة في الجدول كقيم للمفاتيح.

المثال الثالث

Test.py
                    # connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# dict مع الإشارة إلى أننا نريد إرجاع أي سطر يتم جلبه ككائن cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor(dictionary=True)

		# 'employee' و تمرير نص الإستعلام الذي يقضي بجلب كل معلومات الموظفين المخزنة في الجدول execute() هنا قمنا باستدعاء الدالة
		cursor.execute('SELECT * FROM employee')

		# حتى ترجع كل المعلومات التي أرجعها الإستعلام fetchall() هنا قمنا باستدعاء الدالة
		# result بعدها قمنا بتخزين كل المعلومات التي تم إرجاعها في الكائن
		# يمثل سطر في الجدول dict كل عنصر فيه عبارة عن list عبارة عن result الكائن
		result = cursor.fetchall()

		# result موجود في الكائن dict في كل دورة ترجع for هنا قمنا بإنشاء حلقة
		# row سيتم جلبهم من الكائن phone و قيمة المفتاح name و قيمة المفتاح id قيمة المفتاح
		for row in result:
		print('ID:', row.get('id'))
		print('Name:', row.get('name'))
		print('Phone:', row.get('phone'))
		print('------------------')

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

ستحصل على النتيجة التالية عند تشغيل الملف Test إذا قمت بتخزين نفس البيانات التي قمنا بإدخالها في الأمثلة السابقة.

Id: 1
Name: Ahmad
Phone: 96101200155
------------------
Id: 2
Name: Rami
Phone: 96170188201
------------------
Id: 3
Name: Sara
Phone: 96103200155
------------------
Id: 4
Name: Rola
Phone: 96176554235
------------------
Id: 5
Name: John
Phone: 96178665711
------------------
Id: 6
Name: Nada
Phone: 96171004321
------------------

 طريقة جلب البيانات المخزنة في جدول ضمن شروط محددة في بايثون

المثال التالي يعلمك طريقة جلب البيانات المخزنة في جدول ضمن شروط محددة.
ملاحظة: سنقوم بجلب معلومات كل موظف في الجدول employee عنده رقم Id أكبر من 3.

المثال الأول

Test.py
                    # connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# أكبر من 3 id الذين يملكون رقم 'employee' و تمرير نص الإستعلام الذي يقضي بجلب كل معلومات الموظفين المخزنة في الجدول execute() هنا قمنا باستدعاء الدالة
		cursor.execute('SELECT * FROM employee WHERE id > 3')

		# حتى ترجع كل المعلومات التي أرجعها الإستعلام fetchall() هنا قمنا باستدعاء الدالة
		# result بعدها قمنا بتخزين كل المعلومات التي تم إرجاعها في الكائن
		# يمثل سطر في الجدول tuple كل عنصر فيه عبارة عن list عبارة عن result الكائن
		result = cursor.fetchall()

		# result موجود في الكائن tuple في كل دورة ترجع for هنا قمنا بإنشاء حلقة
		# بعدها تعرضه كما هو .row ثم تخزنه بشكل مؤقت في الكائن
		for row in result:
		print(row)

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

ستحصل على النتيجة التالية عند تشغيل الملف Test إذا قمت بتخزين نفس البيانات التي قمنا بإدخالها في الأمثلة السابقة.

(4, 'Rola', '96176554235')
(5, 'John', '96178665711')
(6, 'Nada', '96171004321')


ملاحظة

في حال قمت بكتابة إستعلام يرجع سطر واحد فقط, عندها الأفضل و الأسهل لك هو استخدام الدالة fetchone() للتعامل مع البيانات التي تم جلبها.

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



المثال التالي يعلمك طريقة جلب سطر واحد من قاعدة البيانات و من ثم عرض البيانات التي تم جلبها كما هي.

المثال الثاني

Test.py
                    # connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# يساوي 1 id و الذي يملك رقم 'employee' و تمرير نص الإستعلام الذي يقضي بجلب كل معلومات الموظف الموجود في الجدول execute() هنا قمنا باستدعاء الدالة
		cursor.execute('SELECT * FROM employee WHERE id = 1')

		# tuple حتى ترجع كل المعلومات التي أرجعها الإستعلام ككائن fetchone() هنا قمنا باستدعاء الدالة
		# result بعدها قمنا بتخزين كل المعلومات التي تم إرجاعها في الكائن
		result = cursor.fetchone()

		# كما هي result هنا قمنا بعرض المعلومات المخزنة في الكائن
		print(result)

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

ستحصل على النتيجة التالية عند تشغيل الملف Test إذا قمت بتخزين نفس البيانات التي قمنا بإدخالها في الأمثلة السابقة.

(1, 'Ahmad', '96101200155')


المثال التالي يعلمك طريقة تحديد الأعمدة التي تريد عرضها بناء على رقم الـ Index الخاص بكل عامود.
فعلياً, بما أن السطر الذي سنجلبه من الجدول سيكون عبارة عن tuple تتألف من ثلاث عناصر, سنقوم بعرض هذه العناصر بالشكل الذي نريده.

المثال الثالث

Test.py
                    # connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# يساوي 1 id و الذي يملك رقم 'employee' و تمرير نص الإستعلام الذي يقضي بجلب كل معلومات الموظف الموجود في الجدول execute() هنا قمنا باستدعاء الدالة
		cursor.execute('SELECT * FROM employee WHERE id = 1')

		# tuple حتى ترجع كل المعلومات التي أرجعها الإستعلام ككائن fetchone() هنا قمنا باستدعاء الدالة
		# result بعدها قمنا بتخزين كل المعلومات التي تم إرجاعها في الكائن
		result = cursor.fetchone()

		# result هنا قمنا بعرض المعلومات المخزنة في الكائن
		print('Id:',    result[0])
		print('Name:',  result[1])
		print('Phone:', result[2])

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

ستحصل على النتيجة التالية عند تشغيل الملف Test إذا قمت بتخزين نفس البيانات التي قمنا بإدخالها في الأمثلة السابقة.

Id: 1
Name: Ahmad
Phone: 96101200155


المثال التالي يعلمك طريقة تحديد الأعمدة التي تريد عرضها بناءاً على أسماء الأعمدة الموجودة فعلياً في الجدول.
هنا سنحدد للدالة cursor() أن السطر الذي سيتم إرجاعه سيكون عبارة عن dict, عندها بشكل تلقائي سيتم وضع إسم كل عامود في الجدول كمفتاح, و القيم المخزنة في الجدول كقيم للمفاتيح.

المثال الرابع

Test.py
                    # connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# dict مع الإشارة إلى أننا نريد إرجاع أي سطر يتم جلبه ككائن cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor(dictionary=True)

		# يساوي 1 id و الذي يملك رقم 'employee' و تمرير نص الإستعلام الذي يقضي بجلب كل معلومات الموظف الموجود في الجدول execute() هنا قمنا باستدعاء الدالة
		cursor.execute('SELECT * FROM employee WHERE id = 1')

		# tuple حتى ترجع كل المعلومات التي أرجعها الإستعلام ككائن fetchone() هنا قمنا باستدعاء الدالة
		# result بعدها قمنا بتخزين كل المعلومات التي تم إرجاعها في الكائن
		result = cursor.fetchone()

		# result هنا قمنا بعرض المعلومات المخزنة في الكائن
		print('ID:', result.get('id'))
		print('Name:', result.get('name'))
		print('Phone:', result.get('phone'))

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

ستحصل على النتيجة التالية عند تشغيل الملف Test إذا قمت بتخزين نفس البيانات التي قمنا بإدخالها في الأمثلة السابقة.

Id: 1
Name: Ahmad
Phone: 96101200155

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

المثال التالي يعلمك طريقة تعديل المعلومات المخزنة في جدول.
فعلياً, سنقوم بتغيير إسم الموظف الذي يملك رقم id يساوي 3 في الجدول employee.


معلومة تقنية

لحفظ أي معلومات قمت بإضافتها, تعديلها أو مسحها من أي جدول في قاعدة البيانات, يجب أن تستدعي الدالة commit() لحفظ التغيرات.


مثال

Test.py
                    # mysql.connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect() هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# يسمح لنا بالتعامل مع قاعدة البيانات cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# 'employee' وضعنا فيه نص الإستعلام الذي يقضي بتعديل قيمة حقل في الجدول sql المتغير
		sql = 'UPDATE employee SET name = %s WHERE ID = %s'

		# sql يمثل المعلومات التي سيتم دمجها مع الإستعلام الذي قمنا بتجهيزه في الكائن tuple عبارة عن val الكائن
		val = ('Lara', '3')

		# sql و تمرير نص الإستعلام المخزن في المتغير execute() هنا قمنا باستدعاء الدالة
		# val و من ثم القيم التي سيتم دمجها مع نص الإستعلام و التي قمنا بتخزينها في الكائن
		cursor.execute(sql, val)

		# لحفظ التغيرات التي تم إجراءها في قاعدة البيانات commit() هنا قمنا باستدعاء الدالة
		db.commit()

		# هنا قمنا بطباعة عدد الأسطر التي تم تعديل محتواها في الجدول بسبب الإستعلام الذي تم إرساله سابقاً
		print(cursor.rowcount, 'record(s) affected')

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

إذا لم يظهر لك أي خطأ بعد تشغيل الملف Test فهذا يعني أنه تم تعديل إسم الموظف بنجاح في الجدول employee.

أيضاً سيتم طباعة الجملة التالية في الكونسول.

1 record(s) affected


معلومة تقنية

قبل إرسال الإستعلام إلى قاعدة البيانات, قامت الدالة execute() بدمج نص الإستعلام و القيم التي مررناها لها كالتالي.

                  UPDATE employee SET name = 'Lara' WHERE ID = 3
	

 طريقة حذف البيانات المخزنة في الجدول في بايثون

المثال التالي يعلمك طريقة حذف سطر من جدول.
فعلياً, سنقوم بحذف كل معلومات الموظف الذي يملك رقم id يساوي 4 في الجدول employee.


معلومة تقنية

لحفظ أي معلومات قمت بإضافتها, تعديلها أو مسحها من أي جدول في قاعدة البيانات, يجب أن تستدعي الدالة commit() لحفظ التغيرات.


المثال الأول

Test.py
                    # mysql.connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect() هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# يسمح لنا بالتعامل مع قاعدة البيانات cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# 'employee' وضعنا فيه نص الإستعلام الذي يقضي بحذف سطر واحد من الجدول sql المتغير
		sql = 'DELETE FROM employee WHERE ID = %s'

		# sql يمثل المعلومات التي سيتم دمجها مع الإستعلام الذي قمنا بتجهيزه في الكائن tuple عبارة عن val الكائن
		val = ('4',)

		# sql و تمرير نص الإستعلام المخزن في المتغير execute() هنا قمنا باستدعاء الدالة
		# val و من ثم القيم التي سيتم دمجها مع نص الإستعلام و التي قمنا بتخزينها في الكائن
		cursor.execute(sql, val)

		# لحفظ التغيرات التي تم إجراءها في قاعدة البيانات commit() هنا قمنا باستدعاء الدالة
		db.commit()

		# هنا قمنا بطباعة عدد الأسطر التي تم حذفها من الجدول بسبب الإستعلام الذي تم إرساله سابقاً
		print(cursor.rowcount, 'record(s) deleted')

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

إذا لم يظهر لك أي خطأ بعد تشغيل الملف Test فهذا يعني أنه تم حذف السطر بنجاح من الجدول employee.

أيضاً سيتم طباعة عدد الأسطر التي تم حذفها كالتالي.

1 record(s) deleted


معلومة تقنية

قبل إرسال الإستعلام إلى قاعدة البيانات, قامت الدالة execute() بدمج نص الإستعلام و القيم التي مررناها لها كالتالي.

                  DELETE FROM employee WHERE ID = 4
	


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

المثال الثاني

Test.py
                    # mysql.connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect() هنا قمنا باستدعاء الدالة
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)

		# يسمح لنا بالتعامل مع قاعدة البيانات cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# 'employee' و تمرير نص الإستعلام الذي يقضي بحذف كل الأسطر الموجودة في الجدول execute() هنا قمنا باستدعاء الدالة
		cursor.execute('DELETE FROM employee')

		# لحفظ التغيرات التي تم إجراءها في قاعدة البيانات commit() هنا قمنا باستدعاء الدالة
		db.commit()

		# هنا قمنا بطباعة عدد الأسطر التي تم حذفها من الجدول بسبب الإستعلام الذي تم إرساله سابقاً
		print(cursor.rowcount, 'record(s) deleted')

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()
	  

إذا لم يظهر لك أي خطأ بعد تشغيل الملف Test فهذا يعني أنه تم حذف كل أسطر الجدول employee بنجاح.

أيضاً سيتم طباعة عدد الأسطر التي تم حذفها كالتالي.

5 record(s) deleted

 طريقة التعامل مع قواعد البيانات بأفضل أسلوب ممكن في بايثون

المثال التالي يعلمك طريقة التعامل مع قواعد البيانات بأفضل أسلوب ممكن لضمان أن لا يحدث أي خطأ يؤدي إلى إيقاف البرنامج بشكل مفاجئ.
فعلياً, سنضع كل كود يمكن أن يسبب خطأ بداخل بلوك try.. except.
كما أننا سنتعمد كتابة إستعلام منطقياً لا يمكن أن يتنفذ حيث أننا سنحاول حذف كل معلومات الموظف الذي يملك رقم id يساوي 1000 في الجدول employee.


معلومة تقنية

لحفظ أي معلومات قمت بإضافتها, تعديلها أو مسحها من أي جدول في قاعدة البيانات, يجب أن تستدعي الدالة commit() لحفظ التغيرات.
و لإلغاء تنفيذ أي إستعلام قمت بإرساله, يجب أن تستدعي الدالة rollback() و التي ستقوم بشكل تلقائي بإلغاء كل التغيرات التي فعلتها سابقاً.


مثال

Test.py
                    # mysql.connector هنا قمنا بتضمين كل محتوى الموديول
		import mysql.connector

		try:
		# MySQL حتى ترجع كائن يسمح لنا بالإتصال بقواعد بيانات connect() هنا قمنا باستدعاء الدالة
		# لأنه يوجد إحتمال أن يحدث خطأ أثناء الإتصال بخادم قاعدة البيانات try قمنا باستدعاء هذه الدالة بداخل بلوك
		db = mysql.connector.connect(
		user='root',
		passwd='',
		host='localhost',
		database='company'
		)
		except:
		# exit(0) في حال حدوث خطأ في الإتصال, سيتم تنفيذ أمر الطباعة التالي و من ثم إيقاف عمل البرنامج بسبب الدالة
		print("Error: Can't connect to database")
		exit(0)


		# يسمح لنا بالتعامل مع قاعدة البيانات cursor لإنشاء كائن cursor() هنا قمنا باستدعاء الدالة
		cursor = db.cursor()

		# 'employee' وضعنا فيه نص الإستعلام الذي يقضي بحذف سطر واحد من الجدول sql المتغير
		sql = 'DELETE FROM employee WHERE ID = %s'

		# sql يمثل المعلومات التي سيتم دمجها مع الإستعلام الذي قمنا بتجهيزه في الكائن tuple عبارة عن val الكائن
		val = ('1000',)

		try:
		# لحفظ أي تغيرات يتم إجراءها commit() لإرسال الإستعلام إلى قاعدة البيانات. و استدعينا الدالة execute() هنا قمنا باستدعاء الدالة
		# يساوي 1000 id لأنه قد يحدث خطأ في حال عدم وجود موظف يملك رقم try قمنا بوضع هاتين الدالتين بداخل بلوك
		cursor.execute(sql, val)
		db.commit()
		except:
		# في حال حدوث خطأ أثناء تنفيذ الإستعلام, سيتم طباعة أنه قد حدث خطأ
		# لإلغاء أي شيء حدث في قاعدة البيانات بعد محاولة تنفيذ الإستعلام rollback() قمنا باستدعاء الدالة
		print("Error: unable to delete a record")
		db.rollback()

		# هنا قمنا بطباعة عدد الأسطر التي تم حذفها من الجدول بسبب الإستعلام الذي تم إرساله سابقاً
		print(cursor.rowcount, 'record(s) deleted')

		# هنا قمنا بإغلاق الإتصال مع قاعدة البيانات
		cursor.close()
		db.close()

	  

بعد تشغيل الملف Test سيحدث خطأ سببه عدم وجود موظف يملك رقم id يساوي 1000 في الجدول employee لكنه لن يؤدي إلى إيقاف البرنامج و لن يسبب أي مشكلة في قاعدة البيانات.

أيضاً سيتم طباعة عدد الأسطر التي تم حذفها كالتالي.

0 record(s) deleted

مفهوم تعدد المهام في بايثون

عندما تستخدم هاتفك أو حاسوبك, ترى أنه يمكنك تشغيل عدة برامج مع بعض في وقت واحد, كل برنامج شغال في الذاكرة يعتبر Process, فمثلاً إذا قمت بتشغيل خمسة برامج مع بعض فهذا يعني أن نظام التشغيل ينظم عمل خمسة Processes مع بعض. آلية تشغيل عدة برامج مع بعض تسمى Multiprocessing.

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

إذاً نستخدم آلية Multithreading لجعل البرنامج قادر على تنفيذ عدة أوامر مع بعض و كأنها تتنفذ في وقت واحد, و هذا ما سنتعلمه في هذا الدرس.


أهمية تعدد المهام

  • جعل المستخدم قادر على تنفيذ عدة عمليات مع بعض في نفس الوقت.

  • جعل تصميم التطبيقات أجمل و إضافة مؤثرات فيه.

  • كل Thread تقوم بتشغيله, يعمل بشكل منعزل عن باقي الأوامر الموجودة في البرنامج, و بالتالي فإنه في حال وقوع أي خطأ في الـ Thread فإنه لن يؤثر على باقي الأوامر الموجود في البرنامج, كما أنه لا يؤثر على أي Thread آخر شغال في البرنامج.

طريقة إنشاء Thread في بايثون

في البداية, إبتداءاً من الإصدار 2.4 في بايثون, أصبح الموديول threading هو الموديول الذي يستخدم لإنشاء Threads.
هذا الموديول عبارة عن موديول جاهز في بايثون, و هو يحتوي على الكلاس Thread الذي يستخدم لبناء Thread و التحكم به.


إنتبه

قبل هذا الموديول كان يوجد موديول إسمه _thread يستخدم لهذا الغرض أيضاً لكنه لم يعد يستخدم الآن و سيتم إلغاؤه مستقبلاً.
لذلك ننصح بعدم استخدام الموديول _thread أو الإعتماد عليه.



خطوات بناء Thread في بايثون

  1. يجب تضمين الموديول threading.

  2. بناء كلاس يرث من الكلاس Thread و تفعل Override لدالة إسمها run(), و فيها تضع الأوامر التي تريدها أن تتنفذ عندما يتم تشغيل الـ Thread.



خطوات تشغيل Thread في بايثون

  1. يجب إنشاء كائن من الكلاس الذي يرث من الكلاس Thread.

  2. من هذا الكائن, نقوم باستدعاء دالة جاهزة إسمها start() و التي ستقوم بشكل تلقائي باستدعاء الدالة run() و تنفيذ الأوامر الموضوعة فيها.



في المثال التالي قمنا بإنشاء كلاس إسمه Worker يرث من الكلاس Thread.

في الدالة __init__() الخاصة بهذا الكلاس, قمنا بتعريف خاصية إسمها name لأننا ننوي إعطاء إسم لكل كائن ننشئه من هذا الكلاس.
كما أننا جعلناها تستدعي الدالة __init__() الموجودة في الكلاس Thread لنكون قادرين على معاملة أي كائن ننشئه من هذا الكلاس كـ Thread.

بعدها فعلنا Override للدالة run() بهدف أن تطبع الإسم الذي نضعه في الخاصية name ثلاث مرات عندما يتم تشغيله.

في الأخير, قمنا بإنشاء كائنين من هذا الكلاس و تشغيلهما في وقت واحد.

مثال

Worker.py
import threading   # Thread قمنا بتضمين هذا الموديول حتى نستطيع الوراثة من الكلاس
	  import time        # التي سنستخدمها في المثال sleep() قمنا بتضمين هذا الموديول لأنها تحتوي على الدالة


	  # Thread يرث من الكلاس Worker هنا قمنا بإنشاء كلاس إسمه
	  class Worker(threading.Thread):

	  # Thread الموجودة في الكلاس __init__() من أجل إستدعاء الدالة __init__() هنا قمنا بتعريف الدالة
	  # name أيضاً, من أجل تحديد أنه يجب تمرير إسم للكائن الذي يتم إنشاؤه من الكلاس و الذي سيتم تخزينه في الخاصية
	  def __init__(self, name):
	  super(Worker, self).__init__()
	  self.name = name

	  # لتحديد ما سيحدث عند تشغيل الكائن الذي ننشئه من هذا الكلاس run() هنا قمنا بتعريف الدالة
	  def run(self):
	  # في البداية سيتم طباعة إسم الكائن و أنه قد بدأ تنفيذه
	  print('Starting', self.name)

	  # sleep() بعدها سيتم طباعة إسم الكائن 3 مرات. و قمنا باستدعاء الدالة
	  # و تمرير الرقم 1 لها لتجعل الحلقة تتوقف مدة ثانية في نهاية كل دورة
	  for i in range(3):
	  print(self.name)
	  time.sleep(1)

	  # في النهاية سيتم طباعة إسم الكائن و أنه قد إنتهى تنفيذه
	  print('Ending', self.name)
	

Test.py
# Worker الذي قمنا بإنشائه في الموديول Worker هنا قمنا بتضمين الكلاس
	  from Worker import Worker

	  # Threads أي قمنا بإنشاء إثنين .Worker هنا قمنا بإنشاء كائنين من الكلاس
	  thread1 = Worker('Thread-1')
	  thread2 = Worker('Thread-2')

	  # مرة من كل كائن run() من كلا الكائنين لتشغيلهما. أي سيتم استدعاء الدالة start() هنا قمنا باستدعاء الدالة
	  thread1.start()
	  thread2.start()

	  # لجعل مفسّر لغة بايثون thread2 و thread1 من الكائنين join() هنا وضعنا قمنا باستدعاء الدالة
	  # قبل أن يتابع تنفيذ باقي الأوامر الموجودة في الملف thread2 و thread1 ينتظر أن يتوقف الكائنين 
	  thread1.join()
	  thread2.join()

	  # تماماً, سيتم تنفيذ أمر الطباعة التالي thread2 و thread1 بعد أن يتوقف الكائنين
	  print('Both threads are end')
	

سنحصل على نتيجة تشبه النتيجة التالية عند تشغيل الملف Test.

Starting Thread-1
Thread-1
Starting Thread-2
Thread-2
Thread-2
Thread-1
Thread-2
Thread-1
Ending Thread-2
Ending Thread-1
Both threads are end

ملاحظة

في المثال السابق, يمكنك حذف السطر 12 لأن الكلاس Worker ورث أيضاً خاصية إسمها name من الكلاس Thread.
لهذا, يمكنك تمرير الإسم مباشرةً إلى الخاصية التي ورثها الكلاس Worker بدل تعريفها فيه من جديد.



معلومة تقنية

عند تشغيل أكثر من Thread في وقت واحد, لا يمكنك ضمان أو تحديد أي Thread سيتنفذ أو ينتهي قبل الآخر.
السبب في ذلك أن معالج الحاسوب ( CPU ) سيقوم بإرسال كل Thread قمت بتشغيله إلى نواة ( Core ) حتى ينفذهم لك في وقت واحد.
و منطقياً, النواة التي عليها ضغط أقل ستنتهي من تنفيذ أوامر الـ Thread بشكل أسرع.

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

مفهوم الـ Main Thread في بايثون

في بايثون, كل كود يتنفذ في البرنامج, فإنه حتماً يتنفذ بداخل Thread واحد على الأقل.
أي حتى لو لم تقم بوضع الكود بداخل Thread فإنه سيتم وضعه في Thread.
و بالتالي في حال قمت بتشغيل Thread في البرنامج, فهذا يعني أن البرنامج حالياً يعمل فيه إثنين Threads و ليس Thread واحد.


في المثال التالي, قمنا بطباعة عدد الـ Threads الذين يتنفذون حالياً علماً بأننا لم نقم بإنشاء أي كائن من كلاس يرث من الكلاس Thread.

مثال

Worker.py
import threading   # Thread قمنا بتضمين هذا الموديول حتى نستطيع الوراثة من الكلاس

	  # الذين يعملون حالياً Threads هنا قمنا بطباعة عدد الـ
	  print('Active thread(s) count:', threading.active_count())

	  # تم تشغيله Thread الأساسي في البرنامج. أي أول Thread هنا قمنا بطباعة إسم الـ
	  print('Main thread object:', threading.main_thread().name)

	  # الحالي الذي يتم تنفيذه Thread هنا قمنا بطباعة إسم الـ
	  print('Current thread object:', threading.main_thread().name)
	

سنحصل على نتيجة تشبه النتيجة التالية عند تشغيل الملف Test.

Active thread(s) count: 1
Main thread object: MainThread
Current thread object: MainThread

نلاحظ أن عدد الـ Threads الذين يتنفذون حالياً هو 1, و هذا يثبت أن الكود الأساسي في البرنامج يتم وضعه في Thread.

كما أن إسم الـ Thread الأساسي و إسم الـ Thread الذي يتنفذ حالياً هو MainThread.

دوال الموديول threading في بايثون

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

إسم الدالة مع تعريفها
threading.active_count() ترجع عدد الـ Threads الذين يتنفذون في الوقت الحالي الذي تم فيه إستدعاءها.
threading.main_thread() ترجع كائن الـ thread الأساسي في البرنامج.
معلومة: الـ thread الأساسي في البرنامج هو أول Thread بدأ مفسر لغة بايثون بتنفيذ الأوامر الموضوعة فيه.
threading.current_thread() ترجع كائن الـ thread الذي يتنفذ في الوقت الحالي الذي تم فيه إستدعاءها.
threading.enumerate() ترجع كائن list يحتوي على كل كائن thread يتنفذ في الوقت الحالي الذي تم فيه إستدعاءها.
ملاحظة: لا ترجع أي thread تم إيقافه أو لم يتم تشغيله من الأساس.

خصائص و دوال الكلاس Thread في بايثون

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

إسم الدالة مع تعريفها
run() نفعل لها Override لنضع فيها الأوامر التي نريدها أن تتنفذ عند تشغيل كائن الـ Thread.
start() تستخدم لتشغيل كائن الـ Thread الذي قام باستدعائها.
فعلياً, الدالة start() تقوم فقط باستدعاء الدالة run() لتنفيذ الأوامر الموضوعة فيها.

إنتبه: لا يمكن تشغيل نفس كائن الـ Thread أكثر من مرة. أي لا يمكن إستدعاء الدالة start() أكثر من مرة من نفس كائن الـ Thread.
في حال تم استدعاء الدالة start() مرتين من نفس كائن الـ Thread فإنها ترمي الإستثناء RuntimeError.
is_alive() ترجع True طالما أن كائن الـ Thread لم ينتهي تنفيذه بعد حتى لو كان لم يتم البدء بتنفيذه أصلاً.
غير ذلك ترجع False.
join(timeout=None) تجعل كائن الـ Thread الذي قام باستدعائها ينتظر إلى أن ينتهي تنفيذ الـ Thread الذي يعمل قبله قبل أن يبدأ هو بتنفيذ الأوامر الموجودة فيه.
في حال أردت تأخير تنفيذ الـ Thread الذي سيعمل عند إنتهاء تنفيذ الـ Thread لمدة محددة فيمكنك تمرير رقم مكان الباراميتر timeout يمثل هذه المدة, مع الإشارة إلى أن الرقم الذي تمرره يمثل مدة الإنتظار بالثواني. فمثلاً إذا قمت بتمرير الرقم 1.5 فهذا يعني أنك تقصد ثانية و نصف.

معلومة تقنية
هذه الدالة مهمة عند الحاجة لجعل الـ Threads يتنفذوا الواحد تلو الآخر بدل أن يتنفذوا في وقت واحد و يسببوا مشكلة يقال لها Deadlock.
و لا تقلق إذا لم تكن تعرف المقصود من مصطلح Deadlock الآن لأنك ستفهمها لاحقاً من الأمثلة.

في حال وجد مترجم لغة بايثون أن عندما سيتم تشغيل الـ Thread سيحدث Deadlock, سترمي الإستثناء RuntimeError.

الجدول التالي يحتوي على خصائص الكلاس Thread.

إسم الخاصية مع تعريفها
name ترجع الإسم الذي تم إعطاؤه لكائن الـ Thread الذي قام باستدعائها.
فعلياً, عند إنشاء كائن من الكلاس الذي يرث من الكلاس Thread يمكنك تمرير الإسم الذي تريد إعطاؤه له لحظة إنشاءه.
ident ترجع رقم التعرفة ( ID ) الذي يتم توليده بشكل عشوائي و إعطائه لكائن الـ Thread الذي قام باستدعائها.
daemon هذه الخاصية يمكن الإستفادة منها في حال كنت تنوي تشغيل Thread من داخل Thread آخر.
إذا مررت لها القيمة True قبل أن تقوم بتشغيل الـ Thread الآخر, سيفهم مترجم لغة بايثون أنك تنوي إيقاف الـ Thread الآخر بشكل تلقائي عندما يتوقف الـ Thread الذي قام باستدعائه في الأساس. كما أنه في حال كان الـ Thread الآخر متصل بملف, بقاعدة بيانات, أو بالإنترنت إلخ.. فإن إغلاقه بشكل مفاجئ لا يضمن أن يتم إغلاق الإتصالات التي كان يجريها.

في حال وجد مترجم لغة بايثون أن عندما سيتم تشغيل الـ Thread سيحدث Deadlock, سترمي الإستثناء RuntimeError.

المزامنة في بايثون

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


السيناريو الأول

في حال قمت ببناء Thread مهمته جلب علامات الطالب المخزنة في قاعدة بيانات و من ثم حساب معدله العام, و بعدما تم حساب المعدل و عرض المعدل للطالب, قام Thread آخر بتعديل بعض العلامات في قاعدة البيانات لأنه وجد أن الطالب عنده غياب كثير.
إذاً, النتيجة التي أعطانا إياها الـ Thread الأول في هذه الحالة ليست صحيحة, حيث أنه كان يفترض حساب المعدل بعد أن تم إدخال أيام الغياب ضمن المعادلة التي تحسب له معدله النهائي و تعرض له إن كان ناجحاً بناءاً على معدله النهائي و عدد الأيام التي حضر فيها إلى الجامعة.
فعلى سبيل المثال, قد يكون من شروط الجامعة أنه في حال تغيّب الطالب مدة 30 يوم خلال الفصل الواحد, يعتبر راسباً في كل المواد.

إذاً, لحل المشكلة السابقة, كان يجب مزامنة عمل الـ Thread الأول و الـ Thread الثاني.
أي كان يجب تشغيل الـ Thread الذي يجلب أيام الغياب أولاً.
ثم بعد تخزين أيام الغياب و توقف الـ Thread عن العمل, يجب تشغيل الـ Thread الذي يجلب علامات الطالب و يعطيه النتيجة النهائية.


السيناريو الثاني

في حال كان يوجد Thread يريد تعديل محتوى ملف, و كان يوجد Thread آخر يقوم بقراءة محتوى نفس الملف.
في هذه الحالة, سيحدث أيضاً خطأ و هو أن الـ Thread الذي يقوم بالقراءة, سيقرأ محتوى الملف القديم, بدون معرفة أنه قد تم تحديث محتوى هذا الملف في الوقت الذي كان يقرأ منه و يجري عمليات ما بناءاً على المحتوى الذي قرأه وقتها.
لحل هذه المشكلة, كان يجب مزامنة عمل الـ Thread الذي يقرأ من الملف و الـ Thread الذي يعدل في الملف لضمان أن لا يتعاملا معه في وقت واحد.


السيناريو الثالث

في حال قمت بتشغيل إثنين Threads, و في مرحلة ما أصبح الإثنين عالقين بسبب أن الـ Thread الأول بحاجة للوصول إلى شيء يستخدمه الـ Thread الثاني. و بنفس الوقت الـ Thread الثاني بحاجة للوصول إلى شيء يستخدمه الـ Thread الأول. هذه المعضلة تسمى Deadlock, و هي يمكنك تخليها كما في الصورة التالية.

لحل هذه المشكلة, كان يمكن تشغيل كل Thread على حدا.



في المثال التالي قمنا بتعديل المثال السابق لجعل الـ Threads الذين ننشأهم من الكلاس Worker يعملون بطريقة متزامنة, أي الواحد تلو الآخر و ليس مع بعض.
ما فعلناه ببساطة, هو إنشاء كائن من الكلاس Lock, ثم وضع الكود الذي نريده أن يتنفذ بشكل متزامن بداخل بلوك من هذا الكائن.
ملاحظة: قمنا بتعليم الأسطر التي قمنا بإضافتها على الكود السابق باللون الأصفر.

مثال

Worker.py
import threading   # Thread قمنا بتضمين هذا الموديول حتى نستطيع الوراثة من الكلاس
	  import time        # التي سنستخدمها في المثال sleep() قمنا بتضمين هذا الموديول لأنها تحتوي على الدالة


	  # لأننا سنستخدمه لجعل أي كائن ننشئه من الكلاس يعمل بطريقة متزامنة Lock هنا قمنا بإنشاء كائن من الكلاس
	  lock = threading.Lock()

	  # Thread يرث من الكلاس Worker هنا قمنا بإنشاء كلاس إسمه
	  class Worker(threading.Thread):

	  # Thread الموجودة في الكلاس __init__() من أجل إستدعاء الدالة __init__() هنا قمنا بتعريف الدالة
	  # name أيضاً, من أجل تحديد أنه يجب تمرير إسم للكائن الذي يتم إنشاؤه من الكلاس و الذي سيتم تخزينه في الخاصية
	  def __init__(self, name):
	  super(Worker, self).__init__()
	  self.name = name

	  # لتحديد ما سيحدث عند تشغيل الكائن الذي ننشئه من هذا الكلاس run() هنا قمنا بتعريف الدالة
	  def run(self):
	  # هنا قمنا بجعل الكود يتنفذ بطريقة متزامنة
	  with lock:
	  # في البداية سيتم طباعة إسم الكائن و أنه قد بدأ تنفيذه
	  print('Starting', self.name)

	  # sleep() بعدها سيتم طباعة إسم الكائن 3 مرات. و قمنا باستدعاء الدالة
	  # و تمرير الرقم 1 لها لتجعل الحلقة تتوقف مدة ثانية في نهاية كل دورة
	  for i in range(3):
	  print(self.name)
	  time.sleep(1)

	  # في النهاية سيتم طباعة إسم الكائن و أنه قد إنتهى تنفيذه
	  print('Ending', self.name)
	

Test.py
# Worker الذي قمنا بإنشائه في الموديول Worker هنا قمنا بتضمين الكلاس
	  from Worker import Worker

	  # Threads أي قمنا بإنشاء إثنين .Worker هنا قمنا بإنشاء كائنين من الكلاس
	  thread1 = Worker('Thread-1')
	  thread2 = Worker('Thread-2')

	  # مرة من كل كائن run() من كلا الكائنين لتشغيلهما. أي سيتم استدعاء الدالة start() هنا قمنا باستدعاء الدالة
	  thread1.start()
	  thread2.start()

	  # لجعل مفسّر لغة بايثون thread2 و thread1 من الكائنين join() هنا وضعنا قمنا باستدعاء الدالة
	  # قبل أن يتابع تنفيذ باقي الأوامر الموجودة في الملف thread2 و thread1 ينتظر أن يتوقف الكائنين 
	  thread1.join()
	  thread2.join()

	  # تماماً, سيتم تنفيذ أمر الطباعة التالي thread2 و thread1 بعد أن يتوقف الكائنين
	  print('Both threads are end')
	

سنحصل على النتيجة التالية عند تشغيل الملف Test.

Starting Thread-1     <-- الأول Thread هنا تم البدء بتنفيذ الـ
Thread-1
Thread-1
Thread-1
Ending Thread-1       <-- الأول Thread هنا إنتهى تنفيذ أوامر الـ
Starting Thread-2     <-- الثاني Thread هنا تم البدء بتنفيذ الـ
Thread-2
Thread-2
Thread-2
Ending Thread-2       <-- الثاني Thread هنا إنتهى تنفيذ أوامر الـ
Both threads are end


في حال أردت تطبيق أسلوب المزامنة بدون إستخدام أسلوب with block:, يجب وضع الكود الذي تريد مزامنته بين الدالتين acquire() و release().
إذاً يمكنك كتابة كود الدالة run() كالتالي و الحصول على نفس النتيجة.

	def run(self):

	block.acquire()    # هنا قمنا بجعل الكود الذي سيتم تنفيذه بعدها يعمل بطريقة متزامنة

	print('Starting', self.name)
	for i in range(3):
	print(self.name)
	time.sleep(1)

	print('Ending', self.name)

	block.release()    # هنا قمنا بإيقاف التزامن
  

تجميع الـ Threads بداخل Queue في بايثون

في حال أردت تشغيل مجموعة Threads بشكل متزامن, يمكنك إنشاء كائن من الكلاس Queue و وضعهم فيه.
بعدها تقوم بإنشاء حلقة للمرور على Thread واحد منهم في كل مرة, و من ثم تقوم بتشغيله.


الجدول التالي يحتوي على دوال الكلاس Queue الأكثر إستخداماً.

إسم الدالة مع تعريفها
get() ترجع العنصر التالي من كائن الـ Queue الذي قام باستدعائها و من ثم تقوم بحذفه منها.
في حالتنا سترجع Thread جديد في كل مرة نستدعيها فيها.
في حال كان كائن الـ Queue و تم استدعاءها فإنها ترمي الإستثناء Empty.
put(item) تضيف الكائن الذي نمرره لها كعنصر في كائن الـ Queue الذي قام باستدعائها.
في حالتنا نمرر لها كائن من كلاس يمثل Thread.
qsize() ترجع عدد العناصر الموجودة في كائن الـ Queue الذي قام باستدعائها.
في حالتنا عدد الـ Threads الموجودين فيه و الذين لم يتم تنفيذهم بعد.
empty() تستخدم لمعرفة ما إن كان كائن الـ Queue الذي قام باستدعائها فارغاً أم لا.
ترجع True إذا كان فارغاً, غير ذلك ترجع False.
Full() تستخدم لمعرفة ما إن كان كائن الـ Queue الذي قام باستدعائها ممتلىء أم لا, أي قادر على تخزين عناصر جديدة أم لا.
ترجع True إذا كان عدد العناصر الموضوعة فيه أقل من عدد العناصر التي يمكنه تخزينها, غير ذلك ترجع False.


في المثال التالي قمنا قمنا بإنشاء ثلاث كائنات تمثل Threads و وضعناهم بشكل مؤقت في list إسمه listWorkers. بعدها قمنا بتخزين كائنات الـ listWorkers في Queue إسمه queueWorkers.
في الآخير قمنا بتشغيل كل الـ Threads الموضوعين في الكائن queueWorkers.

ملاحظة: جعلنا الـ Threads يعملوا بشكل متزامن.
و بالتالي في حال لم ترد جعلهم يعملوا بشكل متزامن, سيكون عليك فقط إزالة جملة with lock: الموضوعة في الدالة run().

مثال

Worker.py
import threading   # Thread قمنا بتضمين هذا الموديول حتى نستطيع الوراثة من الكلاس
	  import time        # التي سنستخدمها في المثال sleep() قمنا بتضمين هذا الموديول لأنها تحتوي على الدالة


	  # لأننا سنستخدمه لجعل أي كائن ننشئه من الكلاس يعمل بطريقة متزامنة Lock هنا قمنا بإنشاء كائن من الكلاس
	  lock = threading.Lock()

	  # Thread يرث من الكلاس Worker هنا قمنا بإنشاء كلاس إسمه
	  class Worker(threading.Thread):

	  # Thread الموجودة في الكلاس __init__() من أجل إستدعاء الدالة __init__() هنا قمنا بتعريف الدالة
	  # name أيضاً, من أجل تحديد أنه يجب تمرير إسم للكائن الذي يتم إنشاؤه من الكلاس و الذي سيتم تخزينه في الخاصية
	  def __init__(self, name):
	  super(Worker, self).__init__()
	  self.name = name

	  # لتحديد ما سيحدث عند تشغيل الكائن الذي ننشئه من هذا الكلاس run() هنا قمنا بتعريف الدالة
	  def run(self):
	  # هنا قمنا بجعل الكود يتنفذ بطريقة متزامنة
	  with lock:
	  # في البداية سيتم طباعة إسم الكائن و أنه قد بدأ تنفيذه
	  print('Starting', self.name)

	  # sleep() بعدها سيتم طباعة إسم الكائن 3 مرات. و قمنا باستدعاء الدالة
	  # و تمرير الرقم 1 لها لتجعل الحلقة تتوقف مدة ثانية في نهاية كل دورة
	  for i in range(3):
	  print(self.name)
	  time.sleep(1)

	  # في النهاية سيتم طباعة إسم الكائن و أنه قد إنتهى تنفيذه
	  print('Ending', self.name)
	

Test.py
from Worker import Worker  # Worker الذي قمنا بإنشائه في الموديول Worker هنا قمنا بتضمين الكلاس
	  from queue import Queue    # queue الموجود في الموديول Queue هنا قمنا بتضمين الكلاس

	  # Thread أي وضعنا فيه 3 كائنات تمثل .Worker وضعنا فيه 3 كائنات من الكلاس list هنا قمنا بإنشاء
	  workerList = [
	  Worker('Thread-1'),
	  Worker('Thread-2'),
	  Worker('Thread-3')
	  ]

	  # لأننا نريد وضع نفس العناصر بداخله workerList عدد عناصره يساوي عدد عناصر الكائن Queue هنا قمنا بإنشاء كائن
	  workerQueue = Queue(len(workerList))

	  # workerQueue و من ثم تضيفه كعنصر في الكائن workerList في كل دورة ترجع كائن من الكائنات الموجودة في for هنا قمنا بإنشاء حلقة
	  # workerQueue في الكائن workerList في النهاية سيتم وضع كل الكائنات الموجودة في الكائن
	  for thread in workerList:
	  workerQueue.put(thread)

	  # غير فارغ workerQueue لا تتوقف عن تكرار ما بداخلها طالما أن الكائن while هنا قمنا بإنشاء حلقة
	  # و من ثم تنفيذه workerQueue من الكائن Worker في كل دورة من دورات الحلقة, سيتم إخراج كائن
	  while not workerQueue.empty():
	  workerQueue.get().start()


	  # workerList في كل دورة ترجع كائن من الكائنات الموجودة في for هنا قمنا بإنشاء حلقة
	  # لكي يجعل مفسّر لغى بايثون لا ينفذ أي أوامر أخرى موجودة بعد الحلقة join() ترجعه سيستدعي الدالة Worker كل كائن
	  for thread in workerList:
	  thread.join()

	  # تماماً, سيتم تنفيذ أمر الطباعة التالي thread3 و thread2 ,thread1 بعد أن تتوقف الكائنات
	  print('All threads are end')
	

سنحصل على النتيجة التالية عند تشغيل الملف Test.

Starting Thread-1     <-- الأول Thread هنا تم البدء بتنفيذ الـ
Thread-1
Thread-1
Thread-1
Ending Thread-1       <-- الأول Thread هنا إنتهى تنفيذ أوامر الـ
Starting Thread-2     <-- الثاني Thread هنا تم البدء بتنفيذ الـ
Thread-2
Thread-2
Thread-2
Ending Thread-2       <-- الثاني Thread هنا إنتهى تنفيذ أوامر الـ
Starting Thread-3     <-- الثالث Thread هنا تم البدء بتنفيذ الـ
Thread-3
Thread-3
Thread-3
Ending Thread-3       <-- الثالث Thread هنا إنتهى تنفيذ أوامر الـ
All threads are end

الفرق بين الـ Process و الـ Thread بشكل عام

Process تعني برنامج شغال حالياً, و قد قام نظام التشغيل بحجز مساحة خاصة له في الذاكرة.

Thread عبارة عن مجموعة أوامر يتم تنفيذها أثناء تنفيذ أوامر أخرى في نفس البرنامج. يمكنك تشغيل أكثر من Thread في نفس الوقت في البرنامج, و يمكن أيضاً مشاركة المعلومات بينهم. مع ملاحظة أنه يتم إنشاء جميع الـ Threads من ضمن المساحة المحجوزة للـ Process في الذاكرة.