هل من الآمن الإنتاج من داخل كتلة "مع" في بايثون (ولماذا)؟

StackOverflow https://stackoverflow.com/questions/685046

  •  22-08-2019
  •  | 
  •  

سؤال

يبدو أن الجمع بين coroutines والحصول على الموارد قد يكون له بعض العواقب غير المقصودة (أو غير البديهية).

السؤال الأساسي هو ما إذا كان شيء مثل هذا يعمل أم لا:

def coroutine():
    with open(path, 'r') as fh:
        for line in fh:
            yield line

وهو ما يفعله.(يمكنك اختباره!)

القلق الأعمق هو ذلك with من المفترض أن يكون شيئًا بديلاً لـ finally, حيث تتأكد من تحرير المورد في نهاية الكتلة.يمكن لـ Coroutines تعليق واستئناف التنفيذ من داخل ال with كتلة، لذلك كيف يتم حل الصراع؟

على سبيل المثال، إذا قمت بفتح ملف بالقراءة/الكتابة داخل وخارج الكوروتين بينما لم يتم إرجاع الكوروتين بعد:

def coroutine():
    with open('test.txt', 'rw+') as fh:
        for line in fh:
            yield line

a = coroutine()
assert a.next() # Open the filehandle inside the coroutine first.
with open('test.txt', 'rw+') as fh: # Then open it outside.
    for line in fh:
        print 'Outside coroutine: %r' % repr(line)
assert a.next() # Can we still use it?

تحديث

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

import threading

lock = threading.Lock()

def coroutine():
    with lock:
        yield 'spam'
        yield 'eggs'

generator = coroutine()
assert generator.next()
with lock: # Deadlock!
    print 'Outside the coroutine got the lock'
assert generator.next()
هل كانت مفيدة؟

المحلول

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

شيء واحد لم أكن أعرفه أنني تعلمته ردًا على سؤالك وهو أن هناك طريقة إغلاق () جديدة على المولدات:

close() يثير جديد GeneratorExit استثناء داخل المولد لإنهاء التكرار.عند تلقي هذا الاستثناء، يجب أن يتم رفع كود المولد GeneratorExit أو StopIteration.

close() يتم استدعاؤه عندما يتم جمع البيانات المهملة للمولد، وهذا يعني أن كود المولد يحصل على فرصة أخيرة للتشغيل قبل تدمير المولد.هذه الفرصة الأخيرة تعني ذلك try...finally يمكن الآن ضمان عمل البيانات الموجودة في المولدات؛ال finally سيحصل البند الآن دائمًا على فرصة للتشغيل.يبدو هذا وكأنه جزء بسيط من التوافه اللغوية، ولكن باستخدام المولدات و try...finally هو في الواقع ضروري من أجل تنفيذ with بيان وصفه PEP 343.

http://docs.python.org/whatsnew/2.5.html#pep-342-new-generator-features

بحيث يتعامل مع الوضع حيث أ with يتم استخدام العبارة في المولد، ولكنها تنتج في المنتصف ولكنها لا تعود أبدًا - وهي عبارة عن مدير السياق __exit__ سيتم استدعاء الطريقة عندما يتم جمع البيانات المهملة للمولد.


يحرر:

فيما يتعلق بمشكلة التعامل مع الملف:أنسى أحيانًا وجود منصات لا تشبه POSIX.:)

بقدر ما تذهب الأقفال ، أعتقد أن Rafał Dowgird يضرب الرأس على الظفر عندما يقول "عليك فقط أن تدرك أن المولد يشبه أي كائن آخر يحمل الموارد". لا أعتقد with العبارة ذات صلة حقًا هنا، نظرًا لأن هذه الوظيفة تعاني من نفس مشكلات الجمود:

def coroutine():
    lock.acquire()
    yield 'spam'
    yield 'eggs'
    lock.release()

generator = coroutine()
generator.next()
lock.acquire() # whoops!

نصائح أخرى

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

with coroutine() as cr:
  doSomething(cr)

ولكن بدلا من ذلك يجب أن:

cr = coroutine()
try:
  doSomething(cr)
finally:
  cr.close()

جامع القمامة يفعل close() على أية حال، ولكن من الممارسات السيئة الاعتماد على ذلك لتحرير الموارد.

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

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

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

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

بالنسبة إلى TLDR، انظر إليها بهذه الطريقة:

with Context():
    yield 1
    pass  # explicitly do nothing *after* yield
# exit context after explicitly doing nothing

ال Context ينتهي بعد pass تم (أيلا شئ)، pass ينفذ بعد yield تم (أييستأنف التنفيذ).لذلك with ينتهي بعد يتم استئناف التحكم في yield.

تلدر:أ with يبقى السياق محتجزًا عندما yield التحكم بالإصدارات.


هناك في الواقع قاعدتان فقط ذات صلة هنا:

  1. متى with الافراج عن مواردها؟

    إنه يفعل ذلك مرة واحدة ومباشرة بعد تم الانتهاء من كتلته.السابق يعني أنه لا يفرج عنه خلال أ yield, ، لأن ذلك قد يحدث عدة مرات.الأخير يعني أنه يطلق سراحه بعد yield أكملت.

  2. متى yield مكتمل؟

    افكر في yield كنداء عكسي:يتم تمرير التحكم إلى المتصل، وليس إلى المتصل به.بصورة مماثلة، yield يكتمل عند إعادة التحكم إليه، تمامًا كما يحدث عندما ترجع المكالمة التحكم.

لاحظ أن كلاهما with و yield يعملون على النحو المنشود هنا!نقطة أ with lock هو حماية المورد ويظل محميًا خلال فترة yield.يمكنك دائمًا إطلاق هذه الحماية بشكل صريح:

def safe_generator():
  while True:
    with lock():
      # keep lock for critical operation
      result = protected_operation()
    # release lock before releasing control
    yield result
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top