بعض الحركات البهلوانية في بايثون functional stuff in python
هذا الموضوع ماخوذ عن حوار في #linuxac
ما هو الـ self في بايثون؟؟
تخيل ان عندك كائن obj من كلاس kls
حين تنادي:
obj.method()
لا تمرر شيئا الى method، و لكن عندما تعرفه داخل الكلاس، يجب ان تضع self:
def method(self):
....
لماذا؟
حسنا، لماذا بالضبط؟ لان مؤلفي بايثون هكذا ارادو،
و لكن يبقى السؤال، لماذا؟ هل هناك فائدة؟
نعم، هناك فائدة.
حين تنادي obj.method، قد تظن انك تنادي نفس الـ method المعرف داخل الكلاس، و لكن الحقيقة ليست كذلك.
الحقيقة انك تاخذ العضو method من الكائن obj و تقوم بمناداته — و انت لا تعرف حقيقة ما هو method، قد يكون function عادي، و قد يكون اكثر من ذلك ;)
كيف يكون اكثر من ذلك؟
في الحقيقة ان obj.method يقوم تلقائيا بتمرير الكائن obj الى الـ method المعرف داخل الكلاس.
كيف يمرره تلقائيا؟
حسنا، لغة بايثون تحتوي على خصائص الـ functional programming، اي يمكن تمرير فنكشن الى فنكشن اخر كبارامتر، و يمكن ارجاع فنكشن من فنكشن اخر، و يمكن تعيين فنكشن الى متغير و من ثم استدعاء الفنكشن عن طريق المتغير.
تخيل معي هذا الفنكشن البسيط جدا:
def f(x): print x
مجرد ياخذ كائن (رقم او نص .. او اي شيء) و يطبعه على الشاشة.
و الان تخيل:
def g(): f(10)
مجرد “بروكسي” بين الرقم 10 و بين الفنكشن f، يقوم بطباعة الرقم 10 عن طريق تمريره الى f
طيب، كل شي سهل لحد الان و يمكن فعل هذه الحركة حتى في سي و جافا.
و الان .. اول حركة بهلوانية:
def magic():
x = 10
def ninja():
print x
ninja()
x = 20
ninja()
ماللذي يحصل هنا؟؟ عرفنا فنكشن نينجا داخل ماجك، يقوم نينجا بطباعة الكائن x، و لكن الكائن x معرف خارج ننجا، و مع ذلك يمكنه الوصول اليه.
و الان لو عرفنا اكس على انه 10 قبل تعريف نينجا و قمنا باستدعائه، اكيد سيقوم بطباعة 10. و لكن لو غيرنا اكس الى 20، و قمنا باستدعاء نينجا، فإنه سيقوم بطباعة 20، اي ان الاكس داخل نينجا هو نفس الاكس اللذي نقوم بالتلاعب به.
def magic():
x = 10
def ninja():
print x
return ninja
هنا نقوم بتعريف نينجا داخل ماجك و نقوم بارجاعه الى من يستدعي ماجك
>>> magic()
<function ninja at 0x872b72c>
يعني عندما استدعينا ماجك، حصلنا على فنكشن اسمه ninja، يمكننا تخزين هذا الفنكشن في متغير و استدعائه:
>>> a = magic()
>>> a()
10
و هنا سنحصل على الرقم 10 .. و هو القيمة المخزنة داخل اكس .. المعرف داخل ماجك!
هل لاحظتم ماللذي حصل هنا؟ اكس معرف داخل ماجك، و لكنه لم يمت بمجرد الخروج من ماجك كما كان سيحدث لو كنا نبرمج في سي او جافا.
هذه الحركة اسمها closure، و يمكن البحث في كووكل عن closure functional programming للمزيد من الشروح
في السابق رأينا ان g هو مثل البروكسي بين 10 و بين f، و لكن في ماجك فإن ماجك نفسه ليس بروكسي، و لكنه يقوم بإنشاء بروكسي (نينجا) و ارجاعه لنا.
و الان ناتي الى المرحلة التالية، حيث نقوم بانشاء بروكسي لاي كائن اخر بطريقة ديناميكية:
def bind(obj):
def kungfu():
return f(obj)
return kungfu
هنا وظيفة بايند ان يقوم بربط كائن ما مع الفنكشن f عن طريق انشاء بروكسي اسمع كون فو
>>> bind(5)
<function kungfu at 0x872b7d4>
هنا قام بايند بربط الرقم 5 مع f و اعطانا فنكشن (بروكسي) يقوم بتمرير 5 الى f
يمكن استخدامه بهذا الشكل:
>>> a = bind(5)
>>> b = bind(10)
هنا، a عبارة عن فنكشن يطبع 5، و b عبارة عن فنكشن يطبع 10
>>> a()
5
>>> b()
10
هل فهمتم شيئا؟
الموضوع صعب اذا كانت هذه اول مرة ترى مثل هذه الاشياء، و يحتاج فترة للهضم.
و الان، يمكننا انشاء بروكسي بين اي كائن و بين f:
>>> my_object = 10
>>> g = bind(my_object)
>>> g()
10
لاحظ هنا ان g يحتوي في داخله على كود يقوم بتمرير my_object تلقائيا الى f
يعني تخيل ان my_object هو ليس مجرد رقم 10 بل هو كائن مركب .. و نستطيع بكل بساطة ان نقوم بالصاق فنكشن جديد الى هذا الكائن بهذا الشكل:
my_object.f = bind(my_object)
هنا، نستطيع ان نستدعي:
my_object.f()
و هذا الـ f سيقوم بتمرير my_object تلقائيا الى الفنكشن الاصلي f اللذي عرفناه في البداية على انه يقوم بطباعة الكائن على الشاشة.
و لكن لنعد الى الفنكشن bind، شمعنى يعني فقط يمرر الى f؟
يمكننا تطويره قليلا و جعله يربط بين اي فنكشن و اي كائن مهما كان:
def bind2(object, function):
def inner():
return function(object)
return inner
لاحظ ما يحدث هنا:
my_object.f = bind2(my_object, f)
هنا تقريبا نفس ما يحصل في بايثون نفسها (بشكل مبسط جدا جدا) حيث نقوم باضافة حقل f الى الكائن my_object و هذا الحقل هو في الحقيقة فنكشن يقوم بتمرير الكائن الى الفنكشن f الرئيسي
الفرق ان بايثون تقوم بربط الكائن بالفنكشن عن طريق types.MethodType و هو يقوم بشيء مماثل تقريبا، و لكنه ليس فنكشن بل كلاس (constructor)، و ياخذ بارامتر اضافي و هو الكلاس، و طبعا لانه كلاس، فلا يقوم بارجاع فنكشن حقيقي بل يقوم بارجاع كائن يمكن استدعائه لانه يطبق خاصية __call__
يمكننا تخيل ما يحدث على هذا الشكل:
تخيل كلاس اسمه M و ليكن فارغا:
class M(object): pass
a = M()
ننشيء منه كائن و نسميه a
و الان نعرف فنكشن جديد:
def method1(self):
print self.x
و الان نقوم بربط هذا الفنكشن مع الكائن a
a.m = types.MethodType(method1, a, M)
و هنا نلاحظ ان الحقل m هو عبارة عن “ميثود” بكل ما تعنيه الكلمة من معنى (في بايثون على الاقل)، و نلاحظ ان الفنكشن method1 ياخذ بارامتر هو self، و لكننا حين نستدعي a.m لا نمرر شيئا الى الفنكشن، و هذا لان الكائن a نفسه يمرر تلقائيا.