المحاضرة 8: الدوال ككائنات (Functions as Objects)
المحاضرة 8: الدوال ككائنات (Functions as Objects)
المصادر والنسبة والترخيص
المادة الأصلية: آنا بيل (Ana Bell)، MIT OpenCourseWare (MIT OCW)، معهد ماساتشوستس للتكنولوجيا، مقرر 6.100L: مقدمة في علوم الحاسوب والبرمجة باستخدام Python، خريف 2022 (Fall 2022).
- صفحة المحاضرة الرسمية.
- صفحة الشرائح الرسمية.
- ملف الشرائح الأصلي، PDF.
- ملف شيفرة المحاضرة الأصلي.
- تفريغ المحاضرة على OCW (النسخة الإنجليزية الرسمية).
هذه ترجمة وتكييف عربي غير رسمي وفق رخصة CC BY-NC-SA 4.0: النسبة–غير التجاري–المشاركة بالمثل، ولا تعني اعتمادًا أو تأييدًا من MIT أو MIT OCW. شروط الاستخدام والاستشهاد.
منهج الترجمة: لكل صفحة في ملف الشرائح عنوان مستقل ورقم مطابق. شرائح «التكبير» (ZOOMING OUT) وشرائح تتبّع النطاق (Scope) تتكرّر فيها الشيفرة نفسها مع جدول جديد من أسماء الكائنات وقيمها؛ نُقلت كل شريحة على حدة مع جدولها، لأن معنى الشريحة هو الجدول لا الشيفرة. الشيفرة والشفرة الوهمية محفوظة بالإنجليزية، ولم تُترجَم أسماء الدوال ولا الكلمات المفتاحية ولا أسماء المتغيّرات. ولم تُضمَّن صور أو صفحات من ملف PDF.
الشريحة 1: الدوال ككائنات (Functions as Objects)
نزّل الشرائح وملفات .py لمتابعة الشرح.
6.100L، المحاضرة 8 — آنا بيل (Ana Bell).
الشريحة 2: الدالة من المحاضرة السابقة (Function from last lecture)
def is_even( i ):
"""
Input: i, a positive int
Returns True if i is even and False otherwise
"""
return i%2 == 0
- الدالة تُرجِع دائمًا قيمة.
الشريحة 3: ماذا لو لم تكن هناك الكلمة المفتاحية return؟ (What if there is no return keyword)
def is_even( i ):
"""
Input: i, a positive int
Does not return anything
"""
i%2 == 0
- بايثون تُرجِع القيمة
Noneإذا لم يُعطَ أيreturn. - تمثّل
Noneغياب القيمة (Absence of a Value). - إذا استدعيتَها في صدفة (Shell) فلا يُطبع شيء.
- لا يُولِّد ذلك خطأً دلاليًّا ساكنًا (Static Semantic Error).
الشريحة 4: إضافة return None صراحةً
def is_even( i ):
"""
Input: i, a positive int
Does not return anything
"""
i%2 == 0
return None
الشريحة 5: جرّب بنفسك! (YOU TRY IT!)
ما الذي يُطبع إذا شغّلتَ هذه الشيفرة كملف؟
def add(x,y):
return x+y
def mult(x,y):
print(x*y)
add(1,2)
print(add(2,3))
mult(3,4)
print(mult(4,5))
الشريحة 6: print مقابل return
print |
return |
|---|---|
| يمكن استعماله خارج الدوال. | لا معنى له إلا داخل دالة. |
يمكن تنفيذ عبارات print كثيرة داخل الدالة. |
يُنفَّذ واحد فقط من return داخل الدالة. |
يمكن تنفيذ الشيفرة داخل الدالة بعد عبارة print. |
الشيفرة داخل الدالة بعد عبارة return لا تُنفَّذ. |
| له قيمة مرتبطة به، تُخرَج إلى الطرفية (Console). | له قيمة مرتبطة به، تُسلَّم إلى الدالة المستدعِيَة (Calling Function). |
تعبير print نفسه يُرجِع القيمة None. |
— |
الشريحة 7: جرّب بنفسك! — أصلح الدالة (YOU TRY IT!)
أصلح الشيفرة التي تحاول كتابة هذه الدالة:
def is_triangular(n):
""" n is an int > 0
Returns True if n is triangular, i.e. equals a continued
summation of natural numbers (1+2+3+...+k), False otherwise """
total = 0
for i in range(n):
total += i
if total == n:
print(True)
print(False)
الشريحة 8: الدوال تدعم التعدُّد الوحداتي (Modularity)
إليك طريقة الجذر التربيعي بالتنصيف (Bisection Square Root Method) بوصفها دالة:
def bisection_root(x):
# Initialize variables
epsilon = 0.01
low = 0
high = x
ans = (high + low)/2.0
while abs(ans**2 - x) >= epsilon:
# iterate
if ans**2 < x:
low = ans
else:
high = ans
ans = (high + low)/2.0
# print(ans, 'is close to the root of', x)
return ans
الشريحة 9: استدعِها بقيم مختلفة
ملاحظات الشريحة مربوطة بأسطر الشيفرة في الشريحة السابقة:
| الملاحظة في الشريحة | موضعها في الشيفرة |
|---|---|
| guess not close enough — التخمين ليس قريبًا كفاية | شرط while abs(ans**2 - x) >= epsilon: |
update low or high, depends on guess too small or too large — حدّث low أو high حسب هل التخمين صغير جدًا أم كبير جدًا |
الفرع if ans**2 < x: low = ans وelse: high = ans |
| new value for guess — القيمة الجديدة للتخمين | ans = (high + low)/2.0 داخل الحلقة |
| return result — إرجاع النتيجة | return ans |
نادِها بقيم مختلفة:
print(bisection_root(4))
print(bisection_root(123))
اكتب دالة تستدعي هذه الدالة!
الشريحة 10: جرّب بنفسك! (YOU TRY IT!)
اكتب دالة تحقّق المواصفات التالية:
def count_nums_with_sqrt_close_to (n, epsilon):
""" n is an int > 2
epsilon is a positive number < 1
Returns how many integers have a square root within epsilon of n """
استعمل bisection_root التي كتبناها للحصول على تقريب (Approximation) للجذر التربيعي لعدد صحيح.
مثلًا: print(count_nums_with_sqrt_close_to(10, 0.1)) يطبع 4 لأن جذر كل هذه الأعداد الصحيحة على مسافة أقل من 0.1:
- جذر
99هو9.949699401855469 - جذر
100هو9.999847412109375 - جذر
101هو10.049758911132812 - جذر
102هو10.099456787109375
الشريحة 11: التكبير (ZOOMING OUT) — هذا صندوقي الأسود
البرنامج:
def sum_odd(a, b):
sum_of_odds = 0
for i in range(a, b+1):
if i%2 == 1:
sum_of_odds += i
return sum_of_odds
low = 2
high = 7
my_sum = sum_odd(low, high)
جدول نطاق البرنامج (Program Scope):
| الاسم | القيمة | ملاحظة |
|---|---|---|
sum_odd |
Some function code object — كائن دالة فيه شيفرة | مُعرَّفة بـdef |
low |
2 |
|
high |
7 |
|
my_sum |
— | بعد استدعاء دالة واحدة (One function call) |
الشريحة 12: التكبير (ZOOMING OUT)
الشريحة نفسها، وجدول نطاق البرنامج فيه الآن اسمًا إضافيًّا بعد تنفيذ جسم الدالة:
| الاسم | القيمة | ملاحظة |
|---|---|---|
sum_odd |
Some function code object | |
low |
2 |
|
high |
7 |
|
my_sum |
— | لم يُنفَّذ استدعاء بعد |
الشريحة 13: التكبير (ZOOMING OUT)
الشريحة نفسها، وجدول نطاق البرنامج بعد اكتمال الاستدعاء:
| الاسم | القيمة | ملاحظة |
|---|---|---|
sum_odd |
Some function code object | الصندوق الأسود |
low |
2 |
|
high |
7 |
|
my_sum |
15 |
الشريحة 14: نطاق الدالة (Function Scope)
شريحة فاصلة: ترقيم الشرائح من 11 إلى 13 يتبع ترتيب الصفحات في ملف PDF، وهذه الشريحة تعلن انتقال المحاضرة إلى فهم استدعاءات الدوال.
الشريحة 15: فهم استدعاءات الدوال (Understanding Function Calls)
- كيف ينفّذ بايثون استدعاء دالة؟
- كيف يعرف بايثون أي قيمة مرتبطة باسم متغيّر؟
- ينشئ بيئة جديدة (New Environment) مع كل استدعاء دالة!
- مثل برنامج مصغّر (Mini Program) عليه أن يُنجزه.
- يعمل هذا البرنامج المصغّر بعد إسناد معاملاته (Parameters) إلى بعض المدخلات (Inputs).
- يؤدّي العمل، أي جسم الدالة (Body of the Function).
- يُرجِع قيمة.
- تختفي البيئة بعد أن يُرجِع القيمة.
الشريحة 16: البيئات (Environments)
- البيئة العامة (Global Environment):
- حيث يتفاعل المستخدم مع مفسّر بايثون (Python Interpreter).
- حيث يبدأ البرنامج.
- استدعاء دالة يُنشئ بيئة جديدة (Frame / Scope).
الشريحة 17: نطاق المتغيّرات (Variable Scope)
- المعاملات الصورية (Formal Parameters) تُربط بقيمة معاملات المدخلات (Input Parameters).
- النطاق (Scope) هو خريطة (Mapping) من الأسماء إلى الكائنات (Objects):
- يعرّف السياق (Context) الذي يُقيَّم فيه جسم الدالة.
- قيم المتغيّرات تُعطى بارتباطات الأسماء (Bindings of Names).
- تعبيرات جسم الدالة تُقيَّم بالنسبة إلى هذا النطاق الجديد.
def f( x ):
x = x + 1
print('in f(x): x =', x)
return x
x = 3
y = f( x )
ملاحظة المترجم: في النصّ المستخرَج ظهر سطران متباعدان هما xy = 3 وz = f( y x ) بسبب انتقال الأعمدة في ملف PDF؛ والمقصود x = 3 ثم y = f( x ) كما هو واضح من الشرائح 18 إلى 24. اعتُمد التصحيح الظاهر من بقية الشرائح.
الشريحة 18: نطاق المتغيّرات — بعد تقييم def
جدول النطاق العام (Global Scope):
| الاسم | القيمة | ملاحظة |
|---|---|---|
x |
— | لم يُسنَد بعد |
f |
Some function code object | كائن الدالة |
الشريحة 19: نطاق المتغيّرات — بعد تنفيذ أول إسناد
جدول النطاق العام:
| الاسم | القيمة | ملاحظة |
|---|---|---|
x |
3 |
|
f |
Some function code object |
الشريحة 20: نطاق المتغيّرات — بعد استدعاء f
الجداول صارت ثلاثة: النطاق العام، ونطاق جديد باسم f scope.
| النطاق | الاسم | القيمة | ملاحظة |
|---|---|---|---|
| Global scope | x |
3 |
|
| Global scope | f |
Some function code object | |
| f scope | x |
3 |
المعامل الصوري مربوط بالمُدخل 3 |
الشريحة 21: نطاق المتغيّرات — بعد استدعاء f مع المتغيّر y
النطاق العام فيه y = 3 بدلًا من x = 3، ونطاق f كما هو.
| النطاق | الاسم | القيمة |
|---|---|---|
| Global scope | y |
3 |
| Global scope | f |
Some function code object |
| f scope | x |
3 |
الشريحة 22: نطاق المتغيّرات — تقييم جسم f في نطاق f
يُطبع in f(x): x = 4، واسم x في النطاق العام ما زال 3.
| النطاق | الاسم | القيمة |
|---|---|---|
| Global scope | x |
3 |
| Global scope | f |
Some function code object |
| f scope | x |
4 |
الشريحة 23: نطاق المتغيّرات — أثناء return
نفس جداول الشريحة 22، مع عبارة returns 4 بجوار نطاق f.
الشريحة 24: نطاق المتغيّرات — بعد تنفيذ الإسناد الثاني
نطاق f اختفى، وظهر اسم جديد في النطاق العام.
| النطاق | الاسم | القيمة |
|---|---|---|
| Global scope | x |
3 |
| Global scope | f |
Some function code object |
| Global scope | z |
4 |
الشريحة 25: الفكرة الكبرى (BIG IDEA)
عليك أن تعرف أي تعبيرٍ تنفّذه لتعرف في أي نطاقٍ أنت.
الشريحة 26: مثال آخر على النطاق (Another Scope Example)
- داخل الدالة، يمكنك الوصول إلى متغيّر مُعرَّف في الخارج.
- داخل الدالة، لا يمكنك تعديل متغيّر مُعرَّف في الخارج. يمكن ذلك باستعمال المتغيّرات العامة
global، لكن هذا غير مُستحسن. - استعمل Python Tutor لتنفّذ هذه الأمثلة خطوة بخطوة!
def f(y):
x = 1
x += 1
print(x)
x = 5
f(x)
print(x)
المُخرَج: 2 ثم 5.
def g(y):
print(x)
print(x + 1)
x = 5
g(x)
print(x)
المُخرَج: 5 ثم 6 ثم 5.
def h(y):
x += 1
x = 5
h(x)
print(x)
المُخرَج: خطأ (Error).
الشريحة 27: الدوال كوسائط (Functions as Arguments)
شريحة فاصلة تعلن موضوعًا جديدًا.
الشريحة 28: الإجراءات من الرتبة العليا (Higher Order Procedures)
- الكائنات (Objects) في بايثون لها نوع (Type):
intوfloatوstrوBooleanوNoneTypeوfunction. - الكائنات يمكن أن تظهر في الطرف الأيمن (RHS) من عبارة إسناد (Assignment): تربط اسمًا بكائن.
- الكائنات يمكن استعمالها كوسيط (Argument) لإجراء (Procedure)، ويمكن إرجاعها كقيمة من إجراء.
- الدوال كائنات من الفئة الأولى أيضًا (First Class Objects):
- تعامل الدوال معاملة الأنواع الأخرى بالضبط.
- يمكن أن تكون الدوال وسائط لدالة أخرى.
- يمكن أن تُرجِع دالة أخرى دوالًا كقيم.
الشريحة 29: الكائنات في البرنامج (Objects in a Program)
def is_even(i):
return i%2 == 0
r = 2
pi = 22/7
my_func = is_even
a = is_even(3)
b = my_func(4)
جدول الكائنات في النطاق العام:
| الاسم | ملاحظات | القيمة |
|---|---|---|
is_even |
Some function code object — كائن دالة فيه شيفرة | |
my_func |
Some function code object — مرتبط بالكائن نفسه | |
r |
int object | 2 |
pi |
float object | 3.14285714 |
a |
False |
|
b |
True |
الشريحة 30: الفكرة الكبرى (BIG IDEA)
كل شيء في بايثون كائن (an object).
الشريحة 31: دالة كمعامل (Function as a Parameter)
def calc(op, x, y):
return op(x,y)
def add(a,b):
return a+b
def div(a,b):
if b != 0:
return a/b
print("Denominator was 0.")
print(calc(add, 2, 3))
الشريحة 32: نفّذ الشيفرة خطوة بخطوة (Step through the code)
الشيفرة نفسها، ومعها جدول نطاق البرنامج:
| الاسم | ملاحظات |
|---|---|
calc |
Some function code object |
add |
Some function code object |
div |
Some function code object |
res |
— |
السطر المطلوب تنفيذه هو res = calc(add, 2, 3).
الشريحة 33: أنشئ نطاق calc (Create calc scope)
الشريحة نفسها، وجدول نطاق البرنامج كما هو، وقد ظهر نطاق جديد فارغ باسم calc scope.
الشريحة 34: طابِق المعاملات الصورية في calc (Match formal params in calc)
| النطاق | الاسم | القيمة | ملاحظات |
|---|---|---|---|
| Program Scope | calc |
— | Some function code object |
| Program Scope | add |
— | Some function code object |
| Program Scope | div |
— | Some function code object |
| Program Scope | res |
— | |
| calc scope | op |
add |
Some function code object |
| calc scope | x |
2 |
|
| calc scope | y |
3 |
الشريحة 35: السطر الأول والوحيد في calc
الشريحة نفسها، بنفس جداول الشريحة 34، والموجَّه الآن على تنفيذ return op(x,y).
الشريحة 36: أنشئ نطاق add (Create scope of add)
الشريحة نفسها، بنفس جداول الشريحة 34، وقد ظهر نطاق ثالث باسم add scope.
الشريحة 37: طابِق المعاملات الصورية في add (Match formal params in add)
| النطاق | الاسم | القيمة | ملاحظات |
|---|---|---|---|
| Program Scope | calc |
— | Some function code object |
| Program Scope | add |
— | Some function code object |
| Program Scope | div |
— | Some function code object |
| Program Scope | res |
— | |
| calc scope | op |
add |
Some function code object |
| calc scope | x |
2 |
|
| calc scope | y |
3 |
|
| add scope | a |
2 |
|
| add scope | b |
3 |
الشريحة 38: نفّذ سطر add
الشريحة نفسها، بنفس جداول الشريحة 37، مع عبارة returns 5 بجوار نطاق add.
الشريحة 39: استبدل استدعاء الدالة بقيمة return (Replace func call with return)
الشريحة نفسها، ونطاق add اختفى من الجداول بعد انتهاء الدالة:
| النطاق | الاسم | القيمة | ملاحظات |
|---|---|---|---|
| Program Scope | calc |
— | Some function code object |
| Program Scope | add |
— | Some function code object |
| Program Scope | div |
— | Some function code object |
| Program Scope | res |
— | |
| calc scope | op |
add |
Some function code object |
| calc scope | x |
2 |
|
| calc scope | y |
3 |
الشريحة 40: نفّذ سطر calc
الشريحة نفسها، بنفس جداول الشريحة 39، مع عبارة returns 5 بجوار نطاق calc.
الشريحة 41: استبدل استدعاء الدالة بقيمة return
الشريحة نفسها، وبقي جدول النطاق العام فقط:
| النطاق | الاسم | القيمة | ملاحظات |
|---|---|---|---|
| Program Scope | calc |
— | Some function code object |
| Program Scope | add |
— | Some function code object |
| Program Scope | div |
— | Some function code object |
| Program Scope | res |
5 |
الشريحة 42: جرّب بنفسك! (YOU TRY IT!)
نفّذ تتبّعًا مشابهًا (Similar Trace) لاستدعاء الدالة:
def calc(op, x, y):
return op(x,y)
def div(a,b):
if b != 0:
return a/b
print("Denom was 0.")
res = calc(div,2,0)
ما قيمة res؟ وما الذي يُطبع؟
الشريحة 43: مثال آخر — الدوال كمعاملات (Another Example: Functions as Params)
def func_a():
print('inside func_a')
def func_b(y):
print('inside func_b')
return y
def func_c(f, z):
print('inside func_c')
return f(z)
print(func_a())
print(5 + func_b(2))
print(func_c(func_b, 3))
الشريحة 44: الدوال كمعاملات — النطاق العام (Functions as Parameters)
جدول النطاق العام:
| الاسم | ملاحظات |
|---|---|
func_a |
Some function code object |
func_b |
Some function code object |
func_c |
Some function code object |
الشريحة 45: الدوال كمعاملات — نطاق func_a
نفس جدول النطاق العام، وقد ظهر func_a scope، وأصبح في النطاق العام اسم جديد قيمته None.
ملاحظة المترجم: يبيّن النصّ أن تنفيذ print(func_a()) ينشئ اسمًا في النطاق العام قيمته None؛ وهذا يوافق كون func_a لا تُرجِع شيئًا، فتُرجِع None ضمنًا.
الشريحة 46: الدوال كمعاملات — إكمال تنفيذ print(func_a())
الشريحة نفسها، بنفس جداول الشريحة 45.
الشريحة 47: الدوال كمعاملات — نطاق func_b
ظهر func_b scope بالمحتوى:
| النطاق | الاسم | القيمة |
|---|---|---|
| func_b scope | y |
2 |
وبقي جدول النطاق العام كما في الشريحة 44 مع اسم إضافي قيمته None.
الشريحة 48: الدوال كمعاملات — تنفيذ جسم func_b
الشريحة نفسها، والاسم في النطاق العام صارت قيمته 7، لأن 5 + func_b(2) تُنتج 7.
الشريحة 49: الدوال كمعاملات — func_b تُرجِع
عبارة returns 2 بجوار نطاق func_b الذي صار:
| النطاق | الاسم | القيمة |
|---|---|---|
| func_b scope | y |
2 |
الشريحة 50: الدوال كمعاملات — إكمال السطر
الشريحة نفسها، وجدول النطاق العام فيه 7، وبقي func_b scope بقيمة y = 2.
الشريحة 51: الدوال كمعاملات — نطاق func_c
ظهر func_c scope:
| النطاق | الاسم | القيمة | ملاحظات |
|---|---|---|---|
| func_c scope | f |
func_b |
Some function code object |
| func_c scope | z |
— |
الشريحة 52: الدوال كمعاملات — مطابقة المعاملات في func_c
| النطاق | الاسم | القيمة | ملاحظات |
|---|---|---|---|
| func_c scope | f |
func_b |
Some function code object |
| func_c scope | z |
3 |
|
| func_b scope | y |
3 |
مع عبارة returns 3 بجوار نطاق func_b.
الشريحة 53: الدوال كمعاملات — تنفيذ جسم func_c
جدول النطاق العام فيه 7 و3، ويظهر نطاقا func_c وfunc_b، مع عبارة returns 3 بجوار نطاق func_c.
الشريحة 54: جرّب بنفسك! (YOU TRY IT!)
اكتب دالة تحقّق هذه المواصفات:
def apply(criteria,n):
"""
* criteria is a func that takes in a number and returns a bool
* n is an int
Returns how many ints from 0 to n (inclusive) match
the criteria (i.e. return True when run with criteria)
"""
الشريحة 55: الخلاصة (SUMMARY)
- الدوال كائنات من الفئة الأولى (First Class Objects):
- لها نوع.
- يمكن إسنادها قيمة تُربط باسم.
- يمكن استعمالها كوسيط لإجراء آخر.
- يمكن إرجاعها كقيمة من إجراء آخر.
- يجب الحذر من البيئات (Environments):
- البرنامج الرئيسي يعمل في البيئة العامة (Global Environment).
- كل استدعاء دالة يحصل على بيئة مؤقتة جديدة.
- هذا يتيح إنشاء شيفرة موجزة وسهلة القراءة.
الشريحة 56: MITOpenCourseWare
https://ocw.mit.edu
مقرر 6.100L: مقدمة في علوم الحاسوب والبرمجة باستخدام Python، خريف 2022.
للاطّلاع على كيفية الاستشهاد بهذه المواد أو على شروط الاستخدام: https://ocw.mit.edu/terms.