كيف يمكن للقرد تصحيح وظيفة في بيثون؟
-
24-09-2019 - |
سؤال
أواجه مشكلة في استبدال وظيفة من وحدة مختلفة بوظيفة أخرى وهي تقودني إلى الجنون.
دعنا نقول أن لدي شريط نمطية يشبه هذا:
from a_package.baz import do_something_expensive
def a_function():
print do_something_expensive()
ولدي وحدة أخرى تبدو هكذا:
from bar import a_function
a_function()
from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'
a_function()
import a_package.baz
a_package.baz.do_something_expensive = lambda: 'Something really cheap.'
a_function()
أتوقع الحصول على النتائج:
Something expensive!
Something really cheap.
Something really cheap.
لكن بدلاً من ذلك أحصل على هذا:
Something expensive!
Something expensive!
Something expensive!
ما الخطأ الذي افعله؟
المحلول
قد يساعد ذلك في التفكير في كيفية عمل مساحات أسماء الثعبان: إنها قواميس بشكل أساسي. لذلك عندما تفعل هذا:
from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'
أعتقد أنه من مثل هذا:
do_something_expensive = a_package.baz['do_something_expensive']
do_something_expensive = lambda: 'Something really cheap.'
نأمل أن تتمكن من إدراك سبب عدم عمل هذا بعد ذلك :-) بمجرد استيراد اسم إلى مساحة اسم ، فإن قيمة الاسم في مساحة الاسم التي قمت باستيرادها من عند غير ذي صلة. أنت تعدل فقط قيمة do_something_expless في مساحة اسم الوحدة المحلية ، أو في مساحة اسم A_Package.baz أعلاه. ولكن نظرًا لأن BAR يستورد do_something_expless مباشرة ، بدلاً من الرجوع إليه من مساحة اسم الوحدة النمطية ، تحتاج إلى الكتابة إلى مساحة الاسم:
import bar
bar.do_something_expensive = lambda: 'Something really cheap.'
نصائح أخرى
هناك ديكور أنيقة حقًا لهذا: Guido Van Rossum: Python-Dev List: Monkeypatching Idioms.
هناك أيضا dectools الحزمة ، التي رأيتها Pycon 2010 ، والتي قد تكون قادرة على استخدامها في هذا السياق أيضًا ، ولكن قد يسير هذا في الواقع في الاتجاه الآخر (Monkeypatching على المستوى التعريفي ... حيث لا)
إذا كنت ترغب فقط في تصحيحها لمكالمتك وخلاف ذلك ، اترك الكود الأصلي الذي يمكنك استخدامه https://docs.python.org/3/library/unittest.mock.html#patch (منذ Python 3.3):
with patch('a_package.baz.do_something_expensive', new=lambda: 'Something really cheap.'):
print do_something_expensive()
# prints 'Something really cheap.'
print do_something_expensive()
# prints 'Something expensive!'
في المقتطف الأول ، تصنع bar.do_something_expensive
الرجوع إلى كائن الوظيفة الذي a_package.baz.do_something_expensive
يشير في تلك اللحظة. إلى "monkeypatch" حقًا الذي ستحتاج إلى تغيير الوظيفة نفسها (يمكنك فقط تغيير الأسماء التي تشير إليها) ؛ هذا ممكن ، لكنك لا تريد فعل ذلك.
في محاولاتك لتغيير سلوك a_function
, ، لقد فعلت شيئين:
في المحاولة الأولى ، تقوم بعمل اسم عالمي do_something_expless في الوحدة النمطية. ومع ذلك ، أنت تتصل
a_function
, ، الذي لا يبحث في الوحدة النمطية لحل الأسماء ، لذلك لا يزال يشير إلى نفس الوظيفة.في المثال الثاني ، تقوم بتغيير ماذا
a_package.baz.do_something_expensive
يشير إلى ، ولكنbar.do_something_expensive
لا يرتبط بطريقة سحرية. لا يزال هذا الاسم يشير إلى كائن الوظيفة الذي نظر إليه عندما تم تشغيله.
إن أبسط النهج غير المباشر هو التغيير bar.py
ليقول
import a_package.baz
def a_function():
print a_package.baz.do_something_expensive()
قد يكون الحل الصحيح أحد شيئين:
- إعادة تعريف
a_function
لاتخاذ وظيفة كوسيطة واتصل بذلك ، بدلاً من محاولة التسلل وتغيير الوظيفة التي يتم ترميزها بشدة للإشارة إلى ، أو - تخزين الوظيفة المراد استخدامها في مثيل للفئة ؛ هذه هي الطريقة التي نفعل بها حالة قابلة للتغيير في بيثون.
استخدام Globals (هذا هو ما هو تغيير مستوى الوحدة النمطية من الوحدات الأخرى) هو أ شيء سيء وهذا يؤدي إلى رمز لا يمكن التخلص منه ، مربك ، غير قابل للاستمتاع ، الذي لا يمكن أن يكون من الصعب تتبع تدفقه.
do_something_expensive
في ال a_function()
الوظيفة هي مجرد متغير داخل مساحة اسم الوحدة النمطية التي تشير إلى كائن دالة. عند إعادة تعريف الوحدة النمطية ، تقوم بذلك في مساحة اسم مختلفة.