Pythonetc compilation ، July 2019


هذه هي المجموعة الثانية عشرة من نصائح Python والبرمجة من خلاصتيpythonetc.

← المجموعات السابقة


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

يعمل بشكل جيد ، يعرض 2 :

 def make_closure(x):    def closure():        print(x)    return closure make_closure(2)() 

وهذا الكود يلقي UnboundLocalError: local variable 'x' referenced before assignment :

 def make_closure(x):    def closure():        print(x)        x *= 2        print(x)    return closure make_closure(2)() 


لجعل التعليمات البرمجية تعمل ، استخدم nonlocal . هذا يخبر صراحة المترجم الشفوي بعدم اعتبار الواجب بمثابة تعريف:

 def make_closure(x):    def closure():        nonlocal x        print(x)        x *= 2        print(x)    return closure make_closure(2)() 


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

 def sparse_list(iterable, num_of_zeros=1):    result = []    zeros = [0 for _ in range(num_of_zeros)]    first = True    for x in iterable:        if not first:            result += zeros        result.append(x)        first = False    return result assert sparse_list([1, 2, 3], 2) == [    1,    0, 0,    2,    0, 0,    3, ] 

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

 def sparse_list(iterable, num_of_zeros=1):    result = []    zeros = [0 for _ in range(num_of_zeros)]    iterator = iter(iterable)    try:       result.append(next(iterator))    except StopIteration:        return []    for x in iterator:       result += zeros       result.append(x)    return result 

يمكنك أيضًا استخدام enumerate وإجراء اختبار i == 0 (إنه يعمل فقط لتحديد العنصر الأول ، وليس الأخير) ، ومع ذلك ، فإن أفضل حل هو مولد يقوم بإرجاع الإشارات first last مع العنصر iterable :

 def first_last_iter(iterable):    iterator = iter(iterable)    first = True    last = False    while not last:    if first:        try:            current = next(iterator)            except StopIteration:                return    else:        current = next_one    try:        next_one = next(iterator)    except StopIteration:        last = True   yield (first, last, current)    first = False 

الآن قد تبدو الوظيفة الأصلية كما يلي:

 def sparse_list(iterable, num_of_zeros=1):    result = []    zeros = [0 for _ in range(num_of_zeros)]    for first, last, x in first_last_iter(iterable):        if not first:            result += zeros        result.append(x)    return result 


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

 from contextlib import contextmanager import time @contextmanager def timeit():    start = time.monotonic()    yield    print(time.monotonic() - start) def main():    with timeit():        time.sleep(2) main() 


عادةً لا يعرف مديرو السياق المتداخل أنها متداخلة. يمكنك إخبارهم بذلك عن طريق إنشاء مديرين داخليين باستخدام مدير خارجي:

 from contextlib import AbstractContextManager import time class TimeItContextManager(AbstractContextManager):    def __init__(self, name, parent=None):    super().__init__()    self._name = name    self._parent = parent    self._start = None    self._substracted = 0    def __enter__(self):    self._start = time.monotonic()    return self          def __exit__(self, exc_type, exc_value, traceback):    delta = time.monotonic() - self._start    if self._parent is not None:           self._parent.substract(delta)    print(self._name, 'total', delta)    print(self._name, 'outer', delta - self._substracted)    return False    def child(self, name):    return type(self)(name, parent=self)    def substract(self, n):    self._substracted += n timeit = TimeItContextManager def main():    with timeit('large') as large_t:    with large_t.child('medium') as medium_t:        with medium_t.child('small-1'):            time.sleep(1)        with medium_t.child('small-2'):            time.sleep(1)        time.sleep(1)    time.sleep(1) main() 


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

في بعض الحالات ، قد يكون من الأنسب تعديل جميع الوظائف في السلسلة لنقل جزء جديد من البيانات. بدلاً من ذلك ، يمكنك تحديد سياق تستخدمه جميع الوظائف في السلسلة. كيف نفعل ذلك؟

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

إذا كان التطبيق الخاص بك ذو مؤشرات ترابط متعددة ، فلن تعمل المتغيرات العامة العادية من أجلك ، لأنها ليست خيط أمان آمن. في كل نقطة زمنية ، يمكنك الحصول على سلاسل متعددة للمكالمات ، وكل منها يحتاج إلى سياق خاص به. سوف تساعدك وحدة threading على توفير threading.local() للكائن threading.local() ، والذي يعتبر آمنًا للخيط. يمكنك تخزين البيانات فيه باستخدام وصول بسيط إلى السمات: threading.local().symbol = '@' .

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

 import asyncio import sys global_symbol = '.' async def indication(timeout):    while True:       print(global_symbol, end='')       sys.stdout.flush()       await asyncio.sleep(timeout) async def sleep(t, indication_t, symbol='.'):    loop = asyncio.get_event_loop()    global global_symbol    global_symbol = symbol    task = loop.create_task(          indication(indication_t)    )    await asyncio.sleep(t)    task.cancel() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather(    sleep(1, 0.1, '0'),    sleep(1, 0.1, 'a'),    sleep(1, 0.1, 'b'),    sleep(1, 0.1, 'c'), )) 

يمكنك إصلاح ذلك عن طريق إجبار الدورة على ضبط واستعادة السياق في كل مرة تقوم فيها بالتبديل بين coroutines. يمكنك تطبيق هذا السلوك باستخدام الوحدة النمطية لـ contextvars ، والتي كانت متاحة منذ Python 3.7.

 import asyncio import sys import contextvars global_symbol = contextvars.ContextVar('symbol') async def indication(timeout):    while True:       print(global_symbol.get(), end='')        sys.stdout.flush()       await asyncio.sleep(timeout) async def sleep(t, indication_t, symbol='.'):    loop = asyncio.get_event_loop()    global_symbol.set(symbol)    task = loop.create_task(indication(indication_t))    await asyncio.sleep(t)    task.cancel() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather(    sleep(1, 0.1, '0'),    sleep(1, 0.1, 'a'),    sleep(1, 0.1, 'b'),    sleep(1, 0.1, 'c'), )) 

Source: https://habr.com/ru/post/ar462311/


All Articles