هذه هي المجموعة الثانية عشرة من نصائح 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'), ))