التاريخ الكبير للحجج الوظيفية في بايثون

حسنًا ، في الواقع ، تاريخ الحجج في بايثون ليس بهذا الحجم.



لطالما فوجئت أنه للعمل مع حجج وظائف Python ، تحتاج فقط إلى فهم *argsو **kwargs. وفوجئت ليس عبثا. كما اتضح ، فإن الحجج بعيدة كل البعد عن السهولة. في هذا المنشور ، أريد أن أقدم نظرة عامة على كل ما يتعلق بحجج الوظائف في بايثون. آمل في النهاية أن أكون قادرًا حقًا على إظهار الصورة العامة للعمل مع الحجج ، وألا يصبح هذا المقال منشورًا آخر لن يتمكن القارئ من العثور فيه على أي شيء جديد. والآن - إلى النقطة.







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



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



def foo(a):
    a = a+5
    print(a)             #  15

a = 10
foo(a)
print(a)                 #  10


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



وإذا تم تمرير الكائنات القابلة للتغيير إلى الوظائف ، فقد تواجه سلوك نظام يختلف عما سبق.



def foo(lst):
    lst = lst + ['new entry']
    print(lst)                #  ['Book', 'Pen', 'new entry']

lst = ['Book', 'Pen']
print(lst)                    #  ['Book', 'Pen']
foo(lst)
print(lst)                    #  ['Book', 'Pen']


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



def foo(lst):
    lst[1] = 'new entry'
    print(lst)                #  ['Book', 'new entry']

lst = ['Book', 'Pen']
print(lst)                     #  ['Book', 'Pen']
foo(lst)
print(lst)                     #  ['Book', 'new entry']


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



def foo(lst):
    lst = lst[:]
    lst[1] = 'new entry'
    print(lst)                   #  ['Book', 'new entry']

lst = ['Book', 'Pen']
print(lst)                       #  ['Book', 'Pen']
foo(lst)
print(lst)                       #  ['Book', 'Pen']


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



إذن ، إليك ما يجب أن تعرفه عن وسيطات الدالة:



  1. الترتيب الذي يتم به تمرير الوسائط الموضعية إلى الوظائف.
  2. الترتيب الذي يتم به تمرير الوسائط المسماة إلى الوظائف.
  3. تعيين قيم الوسيطة الافتراضية.
  4. تنظيم معالجة مجموعات الحجج متغيرة الطول.
  5. تفريغ الحجج.
  6. استخدام الوسائط التي لا يمكن تمريرها إلا بالاسم (الكلمة الأساسية فقط).


دعونا نلقي نظرة على كل من هذه النقاط.



1. ترتيب تمرير الحجج الموضعية إلى الوظائف



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



def foo(d, e, f):
    print(d, e, f)

a, b, c = 1, 2, 3
foo(a, b, c)                  #  1, 2, 3
foo(b, a, c)                  #  2, 1, 3
foo(c, b, a)                  #  3, 2, 1


المتغيرات a، bو cلها قيم 1 و 2 و 3. هذه المتغيرات تلعب دور الحجج التي يتم استدعاء الدالة foo. عندما يتم استدعاء الوظيفة لأول مرة ، فإنها تتوافق مع المعلمات d، eو f. تنطبق هذه الآلية على جميع النقاط الست المذكورة أعلاه تقريبًا حول ما تحتاج إلى معرفته حول وسيطات الدالة في Python. يلعب موقع الوسيطة الموضعية التي تم تمريرها إلى الوظيفة عندما يتم استدعاؤها دورًا رئيسيًا في تعيين القيم لمعلمات الوظيفة.



2. ترتيب تمرير الحجج المسماة للوظائف



يتم تمرير الوسيطات المسماة إلى وظائف لها أسماء هذه الوسائط المقابلة للأسماء التي تم تعيينها لها عند إعلان الوظيفة.



def foo(arg1=0, arg2=0, arg3=0):
    print(arg1, arg2, arg3)

a, b, c = 1, 2, 3
foo(a,b,c)                          #  1 2 3
foo(arg1=a, arg2=b, arg3=c)         #  1 2 3
foo(arg3=c, arg2=b, arg1=a)         #  1 2 3
foo(arg2=b, arg1=a, arg3=c)         #  1 2 3


كما ترى ، fooتأخذ الوظيفة 3 وسيطات. تتم تسمية هذه الحجج arg1، arg2و arg3. انتبه إلى كيفية تغيير موضع الحجج عند استدعاء الوظيفة. يتم التعامل مع الوسائط المسماة بشكل مختلف عن الحجج الموضعية ، على الرغم من أن النظام يستمر في قراءتها من اليسار إلى اليمين. تأخذ Python في الاعتبار أسماء الوسائط ، وليس مواقعها ، عند تعيين القيم المناسبة لمعلمات الوظيفة. نتيجة لذلك ، اتضح أن الوظيفة تنتج نفس الشيء بغض النظر عن مواضع الوسائط التي تم تمريرها إليها. هو دائما 1 2 3.



يرجى ملاحظة أن الآليات الموضحة في الفقرة 1 تستمر في العمل هنا.



3. تعيين قيم الوسيطة الافتراضية



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



def foo(arg1=0, arg2=0, arg3=0):
    print(arg1, arg2, arg3)

a, b, c = 1, 2, 3
foo(arg1=a)                         #  1 0 0
foo(arg1=a, arg2=b )                #  1 2 0
foo(arg1=a, arg2=b, arg3=c)         #  1 2 3


يرجى ملاحظة أننا في هذا المثال لا نقوم بتمرير جميع الحجج إلى الوظيفة كما هو موضح في إعلانها. في هذه الحالات ، يتم تعيين القيم الافتراضية للمعلمات المقابلة. دعنا نتابع هذا المثال:



foo(arg2=b)                         #  0 2 0
foo(arg2=b, arg3=c )                #  0 2 3

foo(arg3=c)                         #  0 0 3
foo(arg3=c, arg1=a )                #  1 0 3


هذه أمثلة بسيطة ومفهومة لاستخدام الآليات الموضحة أعلاه لاستدعاء الوظائف مع تمرير الحجج المسماة إليها. دعنا الآن نعقد تجاربنا من خلال الجمع بين ما تحدثنا عنه حتى الآن في النقاط رقم 1 ورقم 2 ورقم 3:



foo(a, arg2=b)                      #  1 2 0
foo(a, arg2=b, arg3=c)              #  1 2 3
foo(a, b, arg3=c)                   #  1 2 3

foo(a)                              #  1 0 0
foo(a,b)                            #  1 2 0


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



هنا أود أن ألفت انتباهكم إلى أحد التفاصيل الرائعة. وهي أن الوسائط الموضعية لا يمكن تحديدها بعد الوسائط المسماة. إليك مثال لمساعدتك على فهم هذه الفكرة بشكل أفضل:



foo(arg1=a, b)
>>>
foo(arg1=a, b)
           ^
SyntaxError: positional argument follows keyword argument
foo(a, arg2=b, c)
>>>
foo(a, arg2=b, c)
              ^
SyntaxError: positional argument follows keyword argument


يمكنك أن تأخذها كقاعدة. لا يتعين على الوسائط الموضعية اتباع الوسائط المسماة عند استدعاء دالة.



4. تنظيم تجهيز مجموعات الحجج متغيرة الطول



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



def foo(*args):
    print(args)

a, b, c = 1, 2, 3

foo(a, b, c)                #  (1, 2, 3)
foo(a, b)                   #  (1, 2)
foo(a)                      #  (1)
foo(b, c)                   #  (2, 3)


يثبت هذا الرمز أن المعلمة argsتخزن مجموعة تحتوي على ما تم تمريره إلى الوظيفة عندما تم استدعاؤها.



def foo(**kwargs):
    print(kwargs)

foo(a=1, b=2, c=3)        #  {'a': 1, 'b': 2, 'c': 3}
foo(a=1, b=2)             #  {'a': 1, 'b': 2}
foo(a=1)                  #  {'a': 1}
foo(b=2, c=3)             #  {'b': 2, 'c': 3}


يوضح الكود أعلاه أن المعلمة kwargsتخزن قاموسًا من أزواج المفتاح والقيمة التي تمثل الوسيطات المسماة التي تم تمريرها إلى الوظيفة عند استدعائها.



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



def foo(*args):
    print(args)

foo(a=1, b=2, c=3)
>>>
foo(a=1, b=2, c=3)
TypeError: foo() got an unexpected keyword argument 'a'
#########################################################
def foo(**kwargs):
    print(kwargs)

a, b, c = 1, 2, 3
foo(a, b, c)
>>>
TypeError: foo() takes 0 positional arguments but 3 were given


الآن دعنا نجمع كل ما حللناه في النقاط # 1 و # 2 و # 3 و # 4 ، ونجرب كل هذا ، ونفحص مجموعات مختلفة من الوسائط التي يمكن تمريرها إلى الدوال عندما يتم استدعاؤها.



def foo(*args,**kwargs):
    print(args, kwargs)

foo(a=1,)
# () {'a': 1}

foo(a=1, b=2, c=3)
# () {'a': 1, 'b': 2, 'c': 3}

foo(1, 2, a=1, b=2)
# (1, 2) {'a': 1, 'b': 2}

foo(1, 2)
# (1, 2) {}


كما ترى ، لدينا مجموعة argsوقاموس تحت تصرفنا kwargs.



وهنا قاعدة أخرى. يكمن في حقيقة أنه *argsلا يمكن استخدام الهيكل بعد الهيكل **kwargs.



def foo(**kwargs, *args):
    print(kwargs, args)
>>>
    def foo(**kwargs, *args):
                      ^
SyntaxError: invalid syntax


تنطبق نفس القاعدة على الترتيب الذي يتم به تحديد الوسيطات عند استدعاء الدوال. يجب ألا تتبع الوسائط الموضعية الوسائط المسماة.



foo(a=1, 1)
>>>
    foo(a=1, 1)
            ^
SyntaxError: positional argument follows keyword argument
foo(1, a=1, 2)
>>>
    foo(1, a=1, 2)
               ^
SyntaxError: positional argument follows keyword argument


عندما تعلن وظائف، يمكنك الجمع بين الحجج الموضعية، *argsو *kwagrsعلى النحو التالي:



def foo(var, *args,**kwargs):
    print(var, args, kwargs)

foo(1, a=1,)                            #  1
# 1 () {'a': 1}

foo(1, a=1, b=2, c=3)                   #  2
# 1 () {'a': 1, 'b': 2, 'c': 3}

foo(1, 2, a=1, b=2)                     #  3
# 1 (2,) {'a': 1, 'b': 2}
foo(1, 2, 3, a=1, b=2)                  #  4
# 1 (2, 3) {'a': 1, 'b': 2}
foo(1, 2)                               #  5
# 1 (2,) {}


عند الإعلان عن دالة ، fooافترضنا أنه يجب أن تحتوي على حجة موضعية واحدة مطلوبة. ويتبعها مجموعة من الوسائط الموضعية متغيرة الطول ، وتتبع هذه المجموعة مجموعة من الوسائط ذات الطول المتغير المسماة. بمعرفة هذا ، يمكننا بسهولة "فك تشفير" كل من استدعاءات الوظائف المذكورة أعلاه.



و 1يتم تمرير وظيفة الحجج 1و a=1. هذه ، على التوالي ، الحجج الموضعية والمسمية. 2هي مجموعة متنوعة 1. هنا ، طول مجموعة الحجج الموضعية هو صفر.



في 3تمرير الوظائف 1، 2و a=1,b=2. هذا يعني أنه يقبل الآن حجتين موضعية ووسيطتين مسماة. وفقًا لإعلان الوظيفة ، اتضح أن1تؤخذ على أنها حجة الموضعية المطلوبة، 2يذهب إلى مجموعة من الحجج طول الموضعية متغيرة، و a=1و b=2ينتهي بها المطاف في مجموعة من طول اسمه الحجج المتغيرة.



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



def foo(var, *args,**kwargs):
    print(var, args, kwargs)

foo(a=1)
>>>
foo(a=1)
TypeError: foo() missing 1 required positional argument: 'var'


تباين آخر لهذه الوظيفة هو دالة تعلن أنها تأخذ وسيطة موضعية واحدة مطلوبة ووسيطة واحدة مسماة ، متبوعة بمجموعات متغيرة الطول من الوسائط الموضعية والمسمية.



def foo(var, kvar=0, *args,**kwargs):
    print(var, kvar, args, kwargs)

foo(1, a=1,)                               #  1
# 1 0 () {'a': 1}

foo(1, 2, a=1, b=2, c=3)                   #  2
# 1 0 () {'a': 1, 'b': 2, 'c': 3}

foo(1, 2, 3, a=1, b=2)                     #  3
# 1 2 () {'a': 1, 'b': 2}

foo(1, 2, 3, 4, a=1, b=2)                  #  4
# 1 2 (3,) {'a': 1, 'b': 2}

foo(1, kvar=2)                             #  5
# 1 2 () {}


يمكن "فك تشفير" استدعاءات هذه الوظيفة بنفس الطريقة التي تم بها تحليل الوظيفة السابقة.



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



foo()
>>>
foo()
TypeError: foo() missing 1 required positional argument: 'var'
foo(1)
# 1 0 () {}


لاحظ أن المكالمة foo(1)تعمل بشكل جيد. النقطة هنا هي أنه إذا تم استدعاء دالة دون تحديد قيمة لوسيطة مسماة ، فسيتم تعيين القيمة لها تلقائيًا.



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



foo(kvar=1)                             #  1
>>>
TypeError: foo() missing 1 required positional argument: 'var'
foo(kvar=1, 1, a=1)                      #  2
>>>
SyntaxError: positional argument follows keyword argument
foo(1, kvar=2, 3, a=2)                   #  3
>>>
SyntaxError: positional argument follows keyword argument


انتبه بشكل خاص لخطأ وقت التشغيل 3.



5. تفريغ الحجج



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



args = (1, 2, 3, 4)
print(*args)                  #  1 2 3 4
print(args)                   #  (1, 2, 3, 4)

kwargs = { 'a':1, 'b':2}
print(kwargs)                 #  {'a': 1, 'b': 2}
print(*kwargs)                #  a b


يمكنك فك ضغط المتغيرات باستخدام الصيغة *و **. هذه هي الطريقة التي يتم استخدامها بها عند تمرير المجموعات والقوائم والقواميس إلى وظيفة.



def foo(a, b=0, *args, **kwargs):
    print(a, b, args, kwargs)

tup = (1, 2, 3, 4)
lst = [1, 2, 3, 4]
d = {'e':1, 'f':2, 'g':'3'}

foo(*tup)             # foo(1, 2, 3, 4)
# 1 2 (3, 4) {}

foo(*lst)             # foo(1, 2, 3, 4)
# 1 2 (3, 4) {}

foo(1, *tup)          # foo(1, 1, 2, 3, 4)
# 1 1 (2, 3, 4) {}

foo(1, 5, *tup)       # foo(1, 5, 1, 2, 3, 4)
# 1 5 (1, 2, 3, 4) {}

foo(1, *tup, **d)     # foo(1, 1, 2, 3, 4 ,e=1 ,f=2, g=3)
# 1 1 (2, 3, 4) {'e': 1, 'f': 2, 'g': '3'}

foo(*tup, **d)         # foo(1, 1, 2, 3, 4 ,e=1 ,f=2, g=3)
# 1 2 (3, 4) {'e': 1, 'f': 2, 'g': '3'}
d['b'] = 45
foo(2, **d)             # foo(1, e=1 ,f=2, g=3, b=45)
# 2 45 () {'e': 1, 'f': 2, 'g': '3'}


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



عند تجربة تفريغ الوسائط ، قد تواجه خطأً جديدًا:



foo(1, *tup, b=5)
>>>
TypeError: foo() got multiple values for argument 'b'
foo(1, b=5, *tup)
>>>
TypeError: foo() got multiple values for argument 'b'


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



6. استخدام الوسيطات التي لا يمكن تمريرها إلا بالاسم (الكلمات الرئيسية فقط)



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



def foo(a, *args, b):
    print(a, args, b)

tup = (1, 2, 3, 4)

foo(*tup, b=35)
# 1 (2, 3, 4) 35

foo(1, *tup, b=35)
# 1 (1, 2, 3, 4) 35

foo(1, 5, *tup, b=35)
# 1 (5, 1, 2, 3, 4) 35

foo(1, *tup, b=35)
# 1 (1, 2, 3, 4) 35

foo(1, b=35)
# 1 () 35

foo(1, 2, b=35)
# 1 (2,) 35

foo(1)
# TypeError: foo() missing 1 required keyword-only argument: 'b'

foo(1, 2, 3)
# TypeError: foo() missing 1 required keyword-only argument: 'b'


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



def foo(a, *, b, c):
    print(a, b, c)

tup = (1, 2, 3, 4)

foo(1, b=35, c=55)
# 1 35 55

foo(c= 55, b=35, a=1)
# 1 35 55

foo(1, 2, 3)
# TypeError: foo() takes 1 positional argument but 3 were given

foo(*tup, b=35)
# TypeError: foo() takes 1 positional argument but 4 positional arguments (and 1 keyword-only argument) were given

foo(1, b=35)
# TypeError: foo() takes 1 positional argument but 4 positional arguments (and 1 keyword-only argument) were given


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



def foo(a, *, b=0, c, d=0):
    print(a, b, c, d)

foo(1, c=55)
# 1 0 55 0

foo(1, c=55, b=35)
# 1 35 55 0

foo(1)
# TypeError: foo() missing 1 required keyword-only argument: 'c'


لاحظ أن وظيفة يمكن أن يسمى عادة دون المرور بأي حجج لذلك، bو dلأنها أعطيت القيم الافتراضية.



النتيجة



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



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






All Articles