PEP 572 (تعابير التعيين في بيثون 3.8)

مرحبا يا هابر. هذه المرة سنلقي نظرة على PEP 572 ، الذي يتحدث عن تعبيرات المهمة. إذا كنت لا تزال متشككًا في عامل التشغيل ": =" أو لا تفهم تمامًا قواعد استخدامه ، فإن هذه المقالة لك. ستجد هنا العديد من الأمثلة والإجابات على السؤال: "لماذا هذا؟" تبين أن هذه المقالة مكتملة قدر الإمكان ، وإذا كان لديك القليل من الوقت ، فابحث في القسم الذي كتبته. في البداية ، يتم جمع "الأطروحات" الرئيسية للعمل المريح مع تعبيرات المهمة. سامحني مسبقًا إذا وجدت أخطاء (اكتب عنها ، سأصلحها). لنبدأ:

PEP 572 - تعابير التعيين

بيب572
عنوان:تعبيرات المهمة
المؤلفون:Chris Angelico <rosuav at gmail.com> ، Tim Peters <tim.peters at gmail.com> ، Guido van Rossum <guido at python.org>
نقاش:doc-sig في python.org
الحالة:قبلت
نوع:اساسي
خلقت:28 فبراير 2018
نسخة بايثون:3.8
نشر الخبر:28-فبراير -2018، 02-مارس -2018، 23-مارس -2018، 04-أبريل -2018، 17-أبريل -2018، 25-أبريل -2018، 09-يوليو -2018، 05-أغسطس -2019
إذن اعتماد المعيار:mail.python.org/pipermail/python-dev/2018-July/154601.html (مع VPN لفترة طويلة ، ولكن يتم تحميله)
المحتوى


حاشية. ملاحظة


ستتحدث هذه الاتفاقية عن إمكانية التعيين داخل التعبيرات ، باستخدام التدوين الجديد NAME: = expr.

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

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

التبرير


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

بالإضافة إلى ذلك ، يمكن أن يساعد تسمية أجزاء من تعبير كبير في تصحيح الأخطاء التفاعلية من خلال توفير أدوات لعرض المطالبات والنتائج المتوسطة. بدون القدرة على التقاط نتائج التعبيرات المتداخلة ، تحتاج إلى تغيير التعليمات البرمجية المصدر ، ولكن باستخدام تعبيرات التعيين تحتاج فقط إلى إدراج بعض "علامات" النموذج "name: = تعبير". هذا يزيل إعادة الهيكلة غير الضرورية ، وبالتالي يقلل من احتمالية حدوث تغييرات غير مقصودة في التعليمات البرمجية أثناء التصحيح (أحد الأسباب الشائعة ل Heisenbugs هو الأخطاء التي تغير خصائص الرمز أثناء التصحيح وقد تظهر بشكل غير متوقع في الإنتاج]) ، وستكون هذه الشفرة أكثر قابلية للفهم لآخر للمبرمج.

أهمية الكود الحقيقي


خلال تطوير PEP هذا ، ركز الكثير من الناس (المؤيدون والنقاد على حد سواء) على أمثلة الألعاب من ناحية ، والأمثلة المعقدة بشكل مفرط من ناحية أخرى.

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

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

مثال آخر على الكود الحقيقي هو الملاحظة غير المباشرة لكيفية تقدير المبرمجين للدمج. فحص Guido van Rossum قاعدة التعليمات البرمجية Dropbox ووجد بعض الأدلة على أن المبرمجين يفضلون كتابة سطور أقل من التعليمات البرمجية عن استخدام بعض التعبيرات الصغيرة.

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

match = re.match(data)
group = match.group(1) if match else None

يفضل المبرمجون هذا الخيار:

group = re.match(data).group(1) if re.match(data) else None

في ما يلي مثال آخر يوضح أن المبرمجين يرغبون أحيانًا في بذل المزيد من الجهد للحفاظ على "المستوى السابق" من المسافة البادئة:

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None

يحسب هذا الرمز النمط 2 ، حتى إذا كان النمط 1 متطابقًا بالفعل (في هذه الحالة ، لن يتم استيفاء الشرط الفرعي الثاني مطلقًا). لذلك ، يكون الحل التالي أكثر فعالية ولكنه أقل جاذبية:

match1 = pattern1.match(data)
if match1:
    result = match1.group(1)
else:
    match2 = pattern2.match(data)
    if match2:
        result = match2.group(2)
    else:
        result = None

النحو والمعاني


في معظم الحالات التي تستخدم فيها Python تعبيرات عشوائية ، يمكنك الآن استخدام تعبيرات المهمة. لديهم النموذج NAME: = expr ، حيث expr هو أي تعبير Python صالح ، باستثناء المجموعة tuple غير المقسمة ، و NAME هو المعرف. تتطابق قيمة مثل هذا التعبير مع الأصل ، ولكن التأثير الإضافي هو تخصيص قيمة للكائن الهدف:

# Handle a matched regex
if (match := pattern.search(data)) is not None:
    # Do something with match

# A loop that can't be trivially rewritten using 2-arg iter()
while chunk := file.read(8192):
   process(chunk)

# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]

# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]

حالات إستثنائية


هناك العديد من الأماكن التي لا يُسمح فيها بتعبيرات المهمة لتجنب الغموض أو الارتباك بين المستخدمين:

  • يحظر استخدام تعبيرات التخصيص غير المضمنة بين قوسين في المستوى "العلوي":

    y := f(x)  # 
    (y := f(x))  # ,   

    ستسهل هذه القاعدة على المبرمج الاختيار بين عامل التعيين وتعبير التعيين - لن يكون هناك موقف نحوي يتساوى فيه كلا الخيارين.
  • . :

    y0 = y1 := f(x)  # 
    y0 = (y1 := f(x))  # ,   

    . :

    foo(x = y := f(x))  # 
    foo(x=(y := f(x)))  # ,     

    , .
  • . :

    def foo(answer = p := 42):  # 
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...

    , (. , «» ).
  • , . :

    def foo(answer: p := 42 = 5):  # 
        ...
    def foo(answer: (p := 42) = 5):  # ,  
        ...

    : , "=" ":=" .
  • -. :

    (lambda: x := 1) # 
    lambda: (x := 1) # ,  
    (x := lambda: 1) # 
    lambda line: (m := re.match(pattern, line)) and m.group(1) # Valid

    - , ":=". . , , () , .
  • f- . :

    >>> f'{(x:=10)}'  # ,  
    '10'
    >>> x = 10
    >>> f'{x:=10}'    # ,  ,  '=10'
    '        10'

    , , f-, . f- ":" . , f- . , .


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

هناك حالة خاصة واحدة: تعبير التخصيص الذي يحدث في مولدات القوائم أو المجموعات أو القواميس أو في "تعبيرات المولدات" نفسها (يشار إليها فيما بعد باسم "المولدات" (الفهم)) يربط المتغير بالنطاق الذي يحتوي عليه المولد ، مع مراعاة معدّل globab أو غير عالمي ، إن وجد.

الأساس المنطقي لهذه الحالة الخاصة ذو شقين. أولاً ، يسمح لنا بالتقاط "العضو" في التعبيرات أي () وكل () ، على سبيل المثال:

if any((comment := line).startswith('#') for line in lines):
    print("First comment:", comment)
else:
    print("There are no comments")

if all((nonblank := line).strip() == '' for line in lines):
    print("All lines are blank")
else:
    print("First non-blank line:", nonblank)

ثانيًا ، يوفر طريقة مضغوطة لتحديث متغير من مولد ، على سبيل المثال:

# Compute partial sums in a list comprehension
total = 0
partial_sums = [total := total + v for v in values]
print("Total:", total)

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

على سبيل المثال ، [i: = i + 1 for i in range (5)] غير صالح: الحلقة for تحدد أن i محلي بالنسبة للمولد ، لكن الجزء "i: = i + 1" يصر على أن أنا متغير من الخارج نطاق لنفس السبب ، لن تعمل الأمثلة التالية:


[[(j := j) for i in range(5)] for j in range(5)] # 
[i := 0 for i, j in stuff]                       # 
[i+1 for i in (i := stuff)]                      # 

على الرغم من أنه من الممكن من الناحية الفنية تعيين دلالات متناسقة لمثل هذه الحالات ، إلا أنه من الصعب تحديد ما إذا كانت الطريقة التي نفهم بها هذه الدلالات ستعمل في شفرتك الحقيقية. هذا هو السبب في أن التنفيذ المرجعي يضمن أن مثل هذه الحالات تثير SyntaxError بدلاً من تنفيذها بسلوك غير محدد ، اعتمادًا على تنفيذ الأجهزة المعينة. ينطبق هذا التقييد حتى إذا لم يتم تنفيذ تعبير المهمة أبداً:

[False and (i := 0) for i, j in stuff]     # 
[i for i, j in stuff if True or (j := 1)]  # 

# [.  . - ""   
# ,       
# ,    ,   ]

بالنسبة إلى نص المولد (الجزء قبل الكلمة الأساسية الأولى "لـ") وتعبير عامل التصفية (الجزء بعد "if" وقبل أي "متداخلة") ، ينطبق هذا التقييد حصريًا على أسماء المتغيرات التي تستخدم في الوقت نفسه كمتغيرات متكررة. كما قلنا بالفعل ، تقدم تعبيرات Lambda نطاقًا صريحًا جديدًا للوظيفة وبالتالي يمكن استخدامها في تعبيرات المولدات دون قيود إضافية. [تقريبا. مرة أخرى ، باستثناء تلك الحالات: [i for i in range (2، (lambda: (s: = 2) ()))]]

نظرًا لقيود التصميم في التنفيذ المرجعي (لا يمكن لمحلل جدول الرموز التعرف على ما إذا كانت الأسماء من الجزء الأيسر من المولد تستخدم في الجزء المتبقي حيث يوجد التعبير القابل للتكرار) ، وبالتالي فإن تعبيرات التخصيص ممنوعة تمامًا كجزء من التكرار (في الجزء بعد كل "في" و قبل أي كلمة رئيسية لاحقة "if" أو "for"). أي أن كل هذه الحالات غير مقبولة:

[i+1 for i in (j := stuff)]                    # 
[i+1 for i in range(2) for j in (k := stuff)]  # 
[i+1 for i in [j for j in (k := stuff)]]       # 
[i+1 for i in (lambda: (j := stuff))()]        # 

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

class Example:
    [(j := i) for i in range(5)]  # 

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

انظر الملحق ب للحصول على أمثلة حول كيفية يتم تحويل تعبيرات التخصيص الموجودة في المولدات إلى رمز مكافئ.

الأولوية النسبية: =


يتم تجميع عامل التشغيل: = أقوى من الفاصلة في جميع المواقف النحوية حيثما أمكن ، ولكنه أضعف من جميع عوامل التشغيل الأخرى ، بما في ذلك أو ، وليس ، والتعبيرات الشرطية (A إذا كان C آخر ب). كما هو موضح في قسم "الحالات الاستثنائية" أعلاه ، لا تعمل تعبيرات المهمة مطلقًا على نفس "المستوى" مثل المهمة الكلاسيكية =. إذا كان هناك ترتيب مختلف للعمليات ، استخدم الأقواس.

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

x := 0 # 

(x := 0) #  

x = y := 0 # 

x = (y := 0) #  

len(lines := f.readlines()) # 

foo(x := 3, cat='vector') # 

foo(cat=category := 'vector') # 

foo(cat=(category := 'vector')) #  

لا يُنصح باستخدام معظم الأمثلة "الصالحة" أعلاه عمليًا ، حيث قد لا يفهم الأشخاص الذين يقومون بمسح شفرة المصدر الخاصة بك بسرعة معناها بشكل صحيح. ولكن في الحالات البسيطة ، يُسمح بذلك:

# Valid
if any(len(longline := line) >= 100 for line in lines):
    print("Extremely long line:", longline)

توصي PEP هذه دائمًا بوضع مسافات حولها: = ، على غرار توصية PEP 8 لـ = للمهمة الكلاسيكية. (اختلاف التوصية الأخيرة هو أنها تحظر المسافات حول = ، والتي يتم استخدامها لتمرير الوسيطات الرئيسية للدالة.)

تغيير ترتيب الحسابات.


من أجل الحصول على دلالات واضحة المعالم ، تتطلب هذه الاتفاقية تحديد إجراء التقييم بوضوح. من الناحية الفنية ، هذا ليس شرطا جديدا. لدى Python بالفعل قاعدة مفادها أنه يتم عادةً تقييم التعبيرات الفرعية من اليسار إلى اليمين. ومع ذلك ، فإن تعبيرات التخصيص تجعل هذه "الآثار الجانبية" أكثر وضوحًا ، ونقترح تغييرًا واحدًا في ترتيب الحساب الحالي:

  • في مولدات القاموس {X: Y for ...} ، يتم تقييم Y حاليًا قبل X. نقترح تغيير هذا بحيث يتم حساب X قبل Y. (في إملاء كلاسيكي مثل {X: Y} ، وكذلك في dict ((X ، Y) لـ ...) تم تنفيذ هذا بالفعل. لذلك ، يجب أن تلتزم مولدات القاموس بهذه الآلية)


الاختلافات بين تعبيرات المهمة وإرشادات المهمة.


الأهم من ذلك ، ": =" هو تعبير ، مما يعني أنه يمكن استخدامه في الحالات التي تكون فيها التعليمات غير صالحة ، بما في ذلك وظائف لامدا والمولدات. على العكس من ذلك ، لا تدعم تعبيرات التخصيص الوظيفة الموسعة التي يمكن استخدامها في تعليمات التعيين:

  • المهمة المتتالية غير مدعومة مباشرة

    x = y = z = 0  # Equivalent: (z := (y := (x := 0)))
  • لا يتم دعم "الأهداف" المنفصلة ، باستثناء اسم المتغير البسيط NAME:

    # No equivalent
    a[i] = x
    self.rest = []
  • تختلف الوظيفة والأولوية "حول" الفواصل:

    x = 1, 2  # Sets x to (1, 2)
    (x := 1, 2)  # Sets x to 1
  • لا تحتوي قيم التفريغ والتعبئة على مكافئ "خالص" أو غير مدعومة على الإطلاق

    # Equivalent needs extra parentheses
    loc = x, y  # Use (loc := (x, y))
    info = name, phone, *rest  # Use (info := (name, phone, *rest))
    
    # No equivalent
    px, py, pz = position
    name, phone, email, *other_info = contact
  • التعليقات التوضيحية من النوع المضمّن غير مدعومة:

    # Closest equivalent is "p: Optional[int]" as a separate declaration
    p: Optional[int] = None
  • لا يوجد شكل مختصر للعمليات:

    total += tax  # Equivalent: (total := total + tax)

تغييرات المواصفات أثناء التنفيذ


تم إجراء التغييرات التالية استنادًا إلى خبرتنا والتحليل الإضافي بعد الكتابة الأولى لهذا PEP وقبل إصدار Python 3.8:

  • لضمان الاتساق مع الاستثناءات المماثلة الأخرى ، وعدم تقديم اسم جديد قد لا يكون مناسبًا للمستخدمين النهائيين ، تمت إزالة الفئة الفرعية المقترحة أصلاً من TargetScopeError لـ SyntaxError وتم تقليلها إلى SyntaxError المعتاد. [3]
  • نظرًا للقيود المفروضة على تحليل جدول أحرف CPython ، فإن التنفيذ المرجعي لتعبير التعيين يرفع SyntaxError لجميع الاستخدامات داخل التكرارات. في السابق ، حدث هذا الاستثناء فقط إذا تزامن اسم المتغير الجاري إنشاؤه مع الاسم المستخدم بالفعل في التعبير التكراري. يمكن مراجعة ذلك إذا كانت هناك أمثلة مقنعة بما فيه الكفاية ، ولكن التعقيد الإضافي يبدو غير مناسب لحالات الاستخدام "الافتراضية" البحتة.

أمثلة


أمثلة مكتبة Python القياسية


site.py


لا يتم استخدام env_base إلا في حالة ما ، لذلك يمكن وضع التعيين إذا كان "رأس" الكتلة المنطقية.

  • الكود الحالي:
    env_base = os.environ.get("PYTHONUSERBASE", None)
    if env_base:
        return env_base
  • كود محسن:
    if env_base := os.environ.get("PYTHONUSERBASE", None):
        return env_base

_pydecimal.py


يمكنك تجنب الأعداد المتداخلة ، وبالتالي إزالة مستوى واحد من المسافة البادئة.

  • الكود الحالي:
    if self._is_special:
        ans = self._check_nans(context=context)
        if ans:
            return ans
  • كود محسن:
    if self._is_special and (ans := self._check_nans(context=context)):
        return ans

copy.py


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

  • الكود الحالي:
    reductor = dispatch_table.get(cls)
    if reductor:
        rv = reductor(x)
    else:
        reductor = getattr(x, "__reduce_ex__", None)
        if reductor:
            rv = reductor(4)
        else:
            reductor = getattr(x, "__reduce__", None)
            if reductor:
                rv = reductor()
            else:
                raise Error(
                    "un(deep)copyable object of type %s" % cls)
  • كود محسن:

    if reductor := dispatch_table.get(cls):
        rv = reductor(x)
    elif reductor := getattr(x, "__reduce_ex__", None):
        rv = reductor(4)
    elif reductor := getattr(x, "__reduce__", None):
        rv = reductor()
    else:
        raise Error("un(deep)copyable object of type %s" % cls)

datetime.py


يستخدم tz فقط لـ s + = tz. تحريكه إلى الداخل إذا كان يساعد على إظهار مجال استخدامه المنطقي.

  • الكود الحالي:

    s = _format_time(self._hour, self._minute,
                     self._second, self._microsecond,
                     timespec)
    tz = self._tzstr()
    if tz:
        s += tz
    return s
  • كود محسن:

    s = _format_time(self._hour, self._minute,
                     self._second, self._microsecond,
                     timespec)
    if tz := self._tzstr():
        s += tz
    return s

sysconfig.py


استدعاء fp.readline () "كشرط" في حلقة الوقت (بالإضافة إلى استدعاء طريقة .match ()) في حالة if يجعل الشفرة أكثر إحكاما دون تعقيد فهمها.

  • الكود الحالي:

    while True:
        line = fp.readline()
        if not line:
            break
        m = define_rx.match(line)
        if m:
            n, v = m.group(1, 2)
            try:
                v = int(v)
            except ValueError:
                pass
            vars[n] = v
        else:
            m = undef_rx.match(line)
            if m:
                vars[m.group(1)] = 0
  • كود محسن:

    while line := fp.readline():
        if m := define_rx.match(line):
            n, v = m.group(1, 2)
            try:
                v = int(v)
            except ValueError:
                pass
            vars[n] = v
        elif m := undef_rx.match(line):
            vars[m.group(1)] = 0

تبسيط مولدات القائمة


الآن يمكن تصفية مولد القائمة بشكل فعال عن طريق "التقاط" الشرط:

results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]

بعد ذلك ، يمكن إعادة استخدام المتغير في تعبير آخر:

stuff = [[y := f(x), x/y] for x in range(5)]

يرجى ملاحظة أنه في كلتا الحالتين يكون المتغير y في نفس نطاق نتائج المتغيرات والأشياء.

التقاط القيم في الظروف


يمكن استخدام تعبيرات الواجب بشكل فعال في شروط عبارة if أو while:

# Loop-and-a-half
while (command := input("> ")) != "quit":
    print("You entered:", command)

# Capturing regular expression match objects
# See, for instance, Lib/pydoc.py, which uses a multiline spelling
# of this effect
if match := re.search(pat, text):
    print("Found:", match.group(0))
# The same syntax chains nicely into 'elif' statements, unlike the
# equivalent using assignment statements.
elif match := re.search(otherpat, text):
    print("Alternate found:", match.group(0))
elif match := re.search(third, text):
    print("Fallback found:", match.group(0))

# Reading socket data until an empty string is returned
while data := sock.recv(8192):
    print("Received data:", data)

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

فرع


مثال من عالم UNIX المنخفض المستوى: [تقريبًا. Fork () هو استدعاء نظام على أنظمة تشغيل تشبه Unix تنشئ عملية فرعية جديدة بالنسبة للوالد.]

if pid := os.fork():
    # Parent code
else:
    # Child code

البدائل المرفوضة


بشكل عام ، الاقتراحات المماثلة شائعة جدًا في مجتمع الثعبان. فيما يلي عدد من الجمل البديلة لتعبيرات التخصيص المحددة للغاية بحيث لا يمكن فهمها وقد تم رفضها لصالح ما ورد أعلاه.

تغيير نطاق المولدات


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

تهجئات بديلة


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

  1. EXPR كاسم:

    stuff = [[f(x) as y, x/y] for x in range(5)]

    EXPR as NAME import, except with, (, ).

    ( , «with EXPR as VAR» EXPR VAR, EXPR.__enter__() VAR.)

    , ":=" :
    • , if f(x) as y , ​​ if f x blah-blah, if f(x) and y.
    • , as , , :
      • import foo as bar
      • except Exc as var
      • with ctxmgr() as var

      , as if while , as « » .
    • «»
      • NAME = EXPR
      • if NAME := EXPR

      .
  2. EXPR -> NAME

    stuff = [[f(x) -> y, x/y] for x in range(5)]

    , R Haskell, . ( , - y < — f (x) Python, - .) «as» , import, except with, . Python ( ), ":=" ( Algol-58) .
  3. «»

    stuff = [[(f(x) as .y), x/.y] for x in range(5)] # with "as"
    stuff = [[(.y := f(x)), x/.y] for x in range(5)] # with ":="

    . Python, , .
  4. where: :

    value = x**2 + 2*x where:
        x = spam(1, 4, 7, q)

    ( , «»). , «» ( with:). . PEP 3150, ( given: ).
  5. TARGET from EXPR:

    stuff = [[y from f(x), x/y] for x in range(5)]

    هذه الصيغة أقل تعارضًا مع الآخرين مما هي عليه (ما لم تحسب الزيادة Exc من Excs الإنشاءات) ، لكن بخلاف ذلك تكون قابلة للمقارنة معهم. بدلاً من التوازي مع expr كهدف: (والذي قد يكون مفيدًا ، ولكن يمكن أن يكون مربكًا أيضًا) ، فإن هذا الخيار لا يوازي أي شيء على الإطلاق ، ولكن من المستغرب تذكره بشكل أفضل.


حالات خاصة في العبارات الشرطية


واحدة من حالات الاستخدام الأكثر شيوعًا لتعبيرات المهمة هي عبارات if and while. بدلاً من حل أكثر عمومية ، يؤدي استخدام صيغة العبارات المحسّنة إلى تحسين بناء جملة من خلال إضافة وسيلة لالتقاط القيمة المراد مقارنتها:

if re.search(pat, text) as match:
    print("Found:", match.group(0))

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

المزايا : لا غموض نحوي. العيوب : حتى إذا كنت تستخدمه فقط في عبارات if / while ، فإنه يعمل بشكل جيد فقط في بعض الحالات.

حالات خاصة في المولدات


حالة أخرى شائعة الاستخدام لتعبيرات التعيين هي المولدات (list / set / dict و genexps). كما ذكر أعلاه ، تم تقديم اقتراحات لحلول محددة.

  1. أين ، السماح ، أو إعطاء:

    stuff = [(y, x/y) where y = f(x) for x in range(5)]
    stuff = [(y, x/y) let y = f(x) for x in range(5)]
    stuff = [(y, x/y) given y = f(x) for x in range(5)]

    ينتج عن هذه الطريقة تعبير فرعي بين الحلقة for والتعبير الرئيسي. كما يقدم أيضًا كلمة أساسية إضافية للغة ، والتي يمكن أن تخلق تعارضات. من بين الخيارات الثلاثة ، أين هو الأنظف والأكثر قابلية للقراءة ، ولكن الصراعات المحتملة لا تزال موجودة (على سبيل المثال ، SQLAlchemy و numpy لها طرقها ، وكذلك tkinter.dnd.Icon في المكتبة القياسية).
  2. مع NAME = EXPR:

    stuff = [(y, x/y) with y = f(x) for x in range(5)]

    , , with. . , «» for. C, , . : « «with NAME = EXPR:» , ?»
  3. with EXPR as NAME:

    stuff = [(y, x/y) with f(x) as y for x in range(5)]

    , as, . , for. with

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

أولوية أقل للمشغل


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

pos = -1
while pos := buffer.find(search_term, pos + 1) >= 0:
    ...

بمجرد أن تعيد find () -1 ، تنتهي الحلقة. إذا: = يربط المعاملات بحرية مثل = ، فإن نتيجة find () سيتم "التقاطها" أولاً في عامل المقارنة وستُرجع عادةً صواب أو خطأ ، وهو أقل فائدة.

على الرغم من أن هذا السلوك سيكون مناسبًا عمليًا في العديد من المواقف ، إلا أنه سيكون أكثر صعوبة في شرحه. ولذا يمكننا القول أن "عامل التشغيل: = يتصرف مثل عامل التعيين المعتاد." بمعنى ، تم اختيار أولوية: = قريبة قدر الإمكان من عامل التشغيل = (باستثناء أن: = له أولوية أعلى من الفاصلة).

أنت تعطي فاصلات على اليمين


يجادل بعض النقاد بأن تعابير الواجب يجب أن تتعرف على الصفوف دون إضافة الأقواس بحيث يكون الإدخالان متساويين:

(point := (x, y))
(point := x, y)

(في الإصدار الحالي من المعيار ، سيكون السجل الأخير معادلًا للتعبير ((النقطة: = س) ، ص).)

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

foo (x: = 1, y)
foo (x: = (1, y))

ونحصل على المخرج الوحيد الأقل إرباكًا: اجعل العامل: = أولوية أقل من الفاصلة.

طلب الأقواس دائمًا


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

# Top level in if
if match := pattern.match(line):
    return match.group(1)

# Short call
len(lines := f.readlines())

اعتراضات متكررة


لماذا لا تقوم فقط بتحويل عبارات المهمة إلى تعبيرات؟


تُعرّف لغة C واللغات المشابهة عامل = على أنه تعبير ، وليس تعليماً ، كما تفعل Python. يسمح هذا بالتعيين في العديد من المواقف ، بما في ذلك الأماكن التي تتم فيها مقارنة المتغيرات. التشابه النحوي بين if (x == y) و if (x = y) يتعارض مع دلالاتها المختلفة بشكل حاد. وبالتالي ، يقدم هذا الشخص PEP عامل التشغيل: = لتوضيح اختلافاتهم.

لماذا تهتم بتعبيرات التخصيص إذا كانت تعليمات التخصيص موجودة ؟


هذان الشكلان لهما مرونة مختلفة. عامل التشغيل: = يمكن استخدامه داخل تعبير أكبر ، وفي عامل التشغيل = يمكن استخدامه من قبل "عائلة عوامل التشغيل المصغرة" من النوع "+ =". أيضًا = يسمح لك بتعيين القيم حسب السمات والفهارس.

لماذا لا تستخدم النطاق المحلي وتمنع تلوث مساحة الاسم؟


تضمنت الإصدارات السابقة من هذا المعيار نطاقًا محليًا حقيقيًا (يقتصر على عبارة واحدة) لتعبيرات المهمة ، ومنع تسرب الاسم وتلوث مساحة الاسم. على الرغم من حقيقة أن هذا في بعض المواقف أعطى ميزة معينة ، إلا أنه في العديد من الحالات الأخرى يعقد المهمة ، ولا تبرر مزايا النهج الحالي. يتم ذلك من أجل بساطة اللغة. لم تعد بحاجة إلى هذا المتغير؟ هناك حل: حذف المتغير باستخدام الكلمة الأساسية del أو إضافة تسطير سفلي أقل إلى اسمه.

(يود المؤلف أن يشكر غيدو فان روسوم وكريستوف غروث على اقتراحاتهما لتطوير معيار PEP في هذا الاتجاه. [2])

توصيات الاسلوب


نظرًا لأنه يمكن استخدام تعبيرات التخصيص أحيانًا على قدم المساواة مع عامل التعيين ، فإن السؤال الذي يطرح نفسه هو ما الذي لا يزال مفضلاً؟ .. وفقًا لاتفاقيات النمط الأخرى (مثل PEP 8) ، هناك توصيتان:

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

شكر


يود مؤلفو هذا المعيار شكر نيك كوجلان وستيفن دابرانو على مساهماتهم الهامة في هذا الشخص ، بالإضافة إلى أعضاء Python Core Mentorship لمساعدتهم في تنفيذ ذلك.

الملحق أ: استنتاجات تيم بيترز


هنا مقال قصير كتبه تيم بيترز حول هذا الموضوع.

لا أحب الرمز "المرتبك" ، ولا أحب أيضًا وضع منطق غير ذي صلة بالمفاهيم على سطر واحد. لذا ، على سبيل المثال ، بدلاً من:

i = j = count = nerrors = 0

أفضل أن أكتب:

i = j = 0
count = 0
nerrors = 0

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

mylast = mylast[1]
yield mylast[0]

أفضل بكثير من هذا:

yield (mylast := mylast[1])[0]

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

while True:
    old = total
    total += term
    if old == total:
        return total
    term *= mx2 / (i*(i+1))
    i += 2

في شكل أقصر ، فقدنا "المنطق". تحتاج إلى فهم كيفية عمل هذا الرمز. عقلي لا يريد القيام بذلك:

while total != (total := total + term):
    term *= mx2 / (i*(i+1))
    i += 2
return total

لكن مثل هذه الحالات نادرة. إن مهمة الحفاظ على النتيجة شائعة جدًا ، ولا تعني عبارة "متفرق أفضل من كثيف" أن "الفراغ تقريبًا أفضل من المتناثر" [تقريبًا. إشارة إلى Zen Python]. على سبيل المثال ، لدي العديد من الوظائف التي ترجع بلا أو صفر للقول "ليس لدي أي شيء مفيد ، ولكن بما أن هذا يحدث غالبًا ، لا أريد أن أزعجك مع الاستثناءات". في الواقع ، تُستخدم هذه الآلية أيضًا في التعبيرات العادية التي تُرجع بلا عند عدم وجود تطابقات. لذلك ، في هذا المثال ، الكثير من التعليمات البرمجية:

result = solution(xs, n)
if result:
    # use result

أجد الخيار التالي أكثر قابلية للفهم ، وبالطبع أكثر قابلية للقراءة:

if result := solution(xs, n):
    # use result

في البداية لم أعلق أهمية كبيرة على هذا ، لكن مثل هذا البناء القصير ظهر كثيرًا لدرجة أنه سرعان ما أزعجني لدرجة أنني لم أتمكن من استخدامه. لقد فاجأني! [تقريبا. يبدو أن هذا قد كُتب قبل إصدار Python 3.8 رسميًا.]

هناك حالات أخرى تكون فيها تعبيرات المهمة "إطلاق نار" حقًا. بدلاً من البحث عن التعليمات البرمجية مرة أخرى ، أعطى Kirill Balunov مثالاً جيدًا على وظيفة copy () من مكتبة copy.py القياسية:

reductor = dispatch_table.get(cls)
if reductor:
    rv = reductor(x)
else:
    reductor = getattr(x, "__reduce_ex__", None)
    if reductor:
        rv = reductor(4)
    else:
        reductor = getattr(x, "__reduce__", None)
        if reductor:
            rv = reductor()
        else:
            raise Error("un(shallow)copyable object of type %s" % cls)

إن المسافة البادئة المتزايدة باستمرار مضللة: بعد كل شيء ، المنطق مسطح في الواقع: أول اختبار ناجح "يفوز":

if reductor := dispatch_table.get(cls):
    rv = reductor(x)
elif reductor := getattr(x, "__reduce_ex__", None):
    rv = reductor(4)
elif reductor := getattr(x, "__reduce__", None):
    rv = reductor()
else:
    raise Error("un(shallow)copyable object of type %s" % cls)

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

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

diff = x - x_base
if diff:
    g = gcd(diff, n)
    if g > 1:
        return g

تحولت إلى:

if (diff := x - x_base) and (g := gcd(diff, n)) > 1:
    return g

لذلك ، في معظم الأسطر التي يحدث فيها التخصيص المتغير ، لن أستخدم تعبيرات التعيين. لكن هذا التصميم متكرر لدرجة أنه لا يزال هناك العديد من الأماكن التي أغتنم فيها هذه الفرصة. في معظم الحالات الأخيرة ، ربحت القليل ، حيث ظهرت في كثير من الأحيان. في الجزء الفرعي المتبقي ، أدى هذا إلى تحسينات متوسطة أو كبيرة. وبالتالي ، سأستخدم تعبيرات التخصيص في كثير من الأحيان أكثر من ثلاث مرات إذا ، ولكن في كثير من الأحيان أقل بكثير من التخصيص المعزز [تقريبًا. خيارات قصيرة: * = ، / = ، + = ، إلخ.].

مثال رقمي


لدي مثال آخر ضربني في وقت سابق.

إذا كانت جميع المتغيرات هي أعداد صحيحة موجبة ، وكان المتغير a أكبر من الجذر n لـ x ، فإن هذه الخوارزمية ترجع التقريب "السفلي" للجذر n لـ x (وتضاعف تقريبًا عدد البتات الدقيقة لكل تكرار):

while a > (d := x // a**(n-1)):
    a = ((n-1)*a + d) // n
return a

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

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

while True:
    d = x // a**(n-1)
    if a <= d:
        break
    a = ((n-1)*a + d) // n
return a

الملحق ب: مترجم الشفرة الخام للمولدات


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

نظرًا لأن [x for ...] يعادل القائمة (x for ...) ، فإن الأمثلة لا تفقد عموميتها. وبما أن هذه الأمثلة تهدف فقط إلى توضيح القواعد العامة ، فإنها لا تدعي أنها واقعية.

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

لنتذكر أولاً الرمز الذي يتم إنشاؤه "تحت غطاء المحرك" للمولدات بدون تعبيرات التخصيص:

  • شفرة المصدر (يستخدم EXPR غالبًا متغير VAR):

    def f():
        a = [EXPR for VAR in ITERABLE]
  • الكود المحول (دعونا لا نقلق بشأن تعارض الأسماء):

    def f():
        def genexpr(iterator):
            for VAR in iterator:
                yield EXPR
        a = list(genexpr(iter(ITERABLE)))


دعنا نضيف تعبير مهمة بسيط.

  • مصدر:

    def f():
        a = [TARGET := EXPR for VAR in ITERABLE]
    
  • الكود المحول:

    def f():
        if False:
            TARGET = None  # Dead code to ensure TARGET is a local variable
        def genexpr(iterator):
            nonlocal TARGET
            for VAR in iterator:
                TARGET = EXPR
                yield TARGET
        a = list(genexpr(iter(ITERABLE)))

الآن دعنا نضيف عبارة TARGET العامة إلى تعريف وظيفة f ().

  • مصدر:

    def f():
        global TARGET
        a = [TARGET := EXPR for VAR in ITERABLE]
    
  • الكود المحول:

    def f():
        global TARGET
        def genexpr(iterator):
            global TARGET
            for VAR in iterator:
                TARGET = EXPR
                yield TARGET
        a = list(genexpr(iter(ITERABLE)))

أو العكس بالعكس ، دعنا نضيف TARGET غير المحلي إلى تعريف دالة f ().

  • مصدر:

    def g():
        TARGET = ...
        def f():
            nonlocal TARGET
            a = [TARGET := EXPR for VAR in ITERABLE]
    
  • الكود المحول:

    def g():
        TARGET = ...
        def f():
            nonlocal TARGET
            def genexpr(iterator):
                nonlocal TARGET
                for VAR in iterator:
                    TARGET = EXPR
                    yield TARGET
            a = list(genexpr(iter(ITERABLE)))

أخيرًا ، لنضع مولدين.

  • مصدر:

    def f():
        a = [[TARGET := i for i in range(3)] for j in range(2)]
        # I.e., a = [[0, 1, 2], [0, 1, 2]]
        print(TARGET)  # prints 2
    
  • الكود المحول:

    def f():
        if False:
            TARGET = None
        def outer_genexpr(outer_iterator):
            nonlocal TARGET
            def inner_generator(inner_iterator):
                nonlocal TARGET
                for i in inner_iterator:
                    TARGET = i
                    yield i
            for j in outer_iterator:
                yield list(inner_generator(range(3)))
        a = list(outer_genexpr(range(2)))
        print(TARGET)

الملحق ج: لا تغييرات في دلالات النطاق


لاحظ أنه في Python ، لم تتغير دلالات النطاق. نطاق الوظائف المحلية لا يزال يتحدد في وقت الترجمة ولها فترة زمنية غير محددة في وقت التشغيل (الإغلاق). مثال:

a = 42
def f():
    # `a` is local to `f`, but remains unbound
    # until the caller executes this genexp:
    yield ((a := i) for i in range(3))
    yield lambda: a + 100
    print("done")
    try:
        print(f"`a` is bound to {a}")
        assert False
    except UnboundLocalError:
        print("`a` is not yet bound")

ثم:

>>> results = list(f()) # [genexp, lambda]
done
`a` is not yet bound
# The execution frame for f no longer exists in CPython,
# but f's locals live so long as they can still be referenced.
>>> list(map(type, results))
[<class 'generator'>, <class 'function'>]
>>> list(results[0])
[0, 1, 2]
>>> results[1]()
102
>>> a
42

المراجع


  1. إثبات تنفيذ المفهوم
  2. مناقشة دلالات تعبيرات المهمة (VPN ضيقة ولكن محملة)
  3. مناقشة TargetScopeError في PEP 572 (تم تحميله بشكل مشابه لما سبق)

حقوق النشر


هذه الوثيقة متاحة للجمهور.

المصدر: github.com/python/peps/blob/master/pep-0572.rst

جزئي


للبدء ، دعونا نلخص:
  • بحيث لا يحاول الناس إزالة الازدواج الدلالي ، في العديد من الأماكن "الكلاسيكية" حيث يمكن للمرء استخدام كل من "=" و ": =" هناك قيود ، لذلك يجب غالبًا وضع عامل التشغيل :: = بين قوسين. يجب مراجعة هذه الحالات في القسم الذي يصف الاستخدام الأساسي.
  • تكون أولوية تعبيرات التخصيص أعلى قليلاً من تلك الموجودة في الفاصلة. ونتيجة لذلك ، لا يتم تشكيل الصفوف أثناء المهمة. كما أنه يجعل من الممكن استخدام عامل التشغيل: = عند تمرير الوسائط إلى دالة.
  • , , , . . lambda , «» .
  • : ,
  • , .
  • / .
  • , .

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

All Articles