अजगर में एक बंदर कैसे काम करता है?


80

मुझे एक फ़ंक्शन को दूसरे फ़ंक्शन के साथ एक अलग मॉड्यूल से बदलने में परेशानी हो रही है और यह मुझे पागल कर रहा है।

मान लें कि मेरे पास एक मॉड्यूल बारकोड है जो इस तरह दिखता है:

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!

मैं क्या गलत कर रहा हूं?


दूसरा काम नहीं कर सकता, क्योंकि आपका सिर्फ आपके स्थानीय दायरे में do_something_ सस्ती का अर्थ पुनर्परिभाषित करता है। मुझे नहीं पता, लेकिन तीसरा काम क्यों नहीं कर रहा है ...
पजटन

1
जैसा कि निकोलस बताते हैं, आप एक संदर्भ की नकल कर रहे हैं और केवल एक संदर्भ की जगह ले रहे हैं। from module import non_module_memberऔर मॉड्यूल-स्तर के बंदर-पैचिंग इस कारण से असंगत हैं, और दोनों को आमतौर पर सबसे अच्छा बचा जाता है।
bobince

पसंदीदा पैकेज नामकरण योजना बिना अंडरस्कोर के साथ कम है, अर्थात apackage
माइक ग्राहम

1
@bobince, मॉड्यूल-लेवल म्यूटेबल स्टेट इस तरह से सबसे अच्छी तरह से बचा जाता है, जब से मान्यता प्राप्त लंबे समय से ग्लोबल्स के बुरे परिणामों के साथ। हालांकि, from foo import barबस ठीक है और उचित होने पर अनुशंसित है।
माइक ग्राहम

जवाबों:


87

यह सोचने में मदद मिल सकती है कि पायथन नामस्थान कैसे काम करते हैं: वे अनिवार्य रूप से शब्दकोष हैं। तो जब आप ऐसा करते हैं:

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.'

उम्मीद है कि आप यह क्यों काम नहीं होता है :-) एक बार जब आप एक नाम स्थान में एक नाम आयात करते हैं, नाम स्थान आप आयातित में नाम का मूल्य का एहसास कर सकते हैं से अप्रासंगिक है। आप केवल स्थानीय मॉड्यूल के नामस्थान में, या a_package.baz के नाम स्थान में do_something_ सस्ती का मान संशोधित कर रहे हैं। लेकिन क्योंकि बार आयात करता है do_something_ सस्ती, बजाय इसे मॉड्यूल नाम स्थान से संदर्भित करने के लिए, आपको इसके नाम स्थान पर लिखने की आवश्यकता है:

import bar
bar.do_something_expensive = lambda: 'Something really cheap.'

तीसरे पक्ष के पैकेजों के बारे में क्या, यहाँ की तरह ?
टेंगरेरी

21

इसके लिए एक बहुत ही सुंदर डेकोरेटर है: गुइडो वैन रोसुम: पायथन-देव सूची: मंकीपिंगिंग मुहावरे

वहाँ भी dectools पैकेज है, जो मैंने एक PyCon 2010 देखा था, जो इस संदर्भ में भी उपयोग करने में सक्षम हो सकता है, लेकिन यह वास्तव में अन्य तरीके से जा सकता है (विधि घोषणात्मक स्तर पर मंकीपैकिंग ... जहाँ आप नहीं हैं)


4
वे डेकोरेटर इस मामले में आवेदन नहीं करते हैं।
माइक ग्राहम

1
@ मायकेग्राम: गुइडो के ईमेल में इस बात का उल्लेख नहीं है कि उनका उदाहरण कोड किसी भी तरीके को बदलने की अनुमति देता है, न कि केवल एक नया जोड़ना। इसलिए, मुझे लगता है कि वे इस मामले पर लागू होते हैं।
टॉउमासालो

@ माइकइग्रम गुइडो उदाहरण एक विधि कथन का मजाक उड़ाने के लिए पूरी तरह से काम करता है, मैंने सिर्फ खुद की कोशिश की! setattr सिर्फ '=' कहने का एक फैंसी तरीका है; इसलिए 'a = 3' या तो 'a' नामक एक नया वैरिएबल बनाता है और इसे तीन में सेट करता है या किसी मौजूदा वैरिएबल के मान को 3 से बदल देता है
क्रिस हुआंग-लीवर

6

यदि आप इसे केवल अपनी कॉल के लिए पैच करना चाहते हैं और अन्यथा मूल कोड छोड़ दें तो आप https://docs.python.org/3/library/unittest.mock.html#patch (पायथन 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!'

3

पहले स्निपेट में, आप bar.do_something_expensiveफ़ंक्शन ऑब्जेक्ट को a_package.baz.do_something_expensiveसंदर्भित करते हैं जो उस क्षण को संदर्भित करता है। वास्तव में "मंकीपैच" करने के लिए जिसे आपको फ़ंक्शन को स्वयं बदलने की आवश्यकता होगी (आप केवल वही बदल रहे हैं जिसे नाम संदर्भित करते हैं); यह संभव है, लेकिन आप वास्तव में ऐसा नहीं करना चाहते हैं।

के व्यवहार को बदलने के अपने प्रयासों में a_function, आपने दो काम किए हैं:

  1. पहले प्रयास में, आप अपने मॉड्यूल में do_something_ सस्ती वैश्विक नाम बनाते हैं। हालांकि, आप कॉल कर रहे हैं a_function, जो नामों को हल करने के लिए आपके मॉड्यूल में नहीं दिखता है, इसलिए यह अभी भी उसी फ़ंक्शन को संदर्भित करता है।

  2. दूसरे उदाहरण में आप बदलते हैं जो 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बल्कि में और परिवर्तन क्या यह मुश्किल का उल्लेख करने के कोडित है ढंग से काम चुपके से कोशिश कर रहा से एक तर्क के रूप में एक समारोह लेने के लिए और फोन है कि करने के लिए, या
  • कक्षा के एक उदाहरण में उपयोग किए जाने वाले फ़ंक्शन को संग्रहीत करें; इस तरह से हम पाइथन में उत्परिवर्ती राज्य करते हैं।

ग्लोबल्स का उपयोग करना (यह जो अन्य मॉड्यूल से मॉड्यूल-स्तर का सामान बदल रहा है) एक बुरी चीज है जो अचूक, भ्रामक, सबसे अस्थिर, अस्थिर कोड की ओर जाता है जिसके प्रवाह को ट्रैक करना मुश्किल है।


0

do_something_expensivea_function()फ़ंक्शन में, फ़ंक्शन ऑब्जेक्ट की ओर इशारा करते हुए मॉड्यूल के नाम स्थान के भीतर एक चर है। जब आप मॉड्यूल को फिर से परिभाषित करते हैं तो आप इसे एक अलग नामस्थान में कर रहे हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.