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