2012-06-24 9 views
9

मुझे पायथन में वर्णक प्रोटोकॉल की सूक्ष्मता को समझने में थोड़ी सी मदद की ज़रूरत है, क्योंकि यह विशेष रूप से staticmethod ऑब्जेक्ट्स के व्यवहार से संबंधित है। मैं एक छोटी सी उदाहरण के साथ iteratively शुरू करेंगे, और फिर इसे विस्तार, की जांच यह प्रत्येक चरण में व्यवहार है:कक्षा को सजाने के लिए डिस्क्रिप्टर प्रोटोकॉल क्यों तोड़ता है, इस प्रकार staticmethod ऑब्जेक्ट्स को अपेक्षित व्यवहार से रोकता है?

class Stub: 
    @staticmethod 
    def do_things(): 
     """Call this like Stub.do_things(), with no arguments or instance.""" 
     print "Doing things!" 

इस बिंदु पर, इस बर्ताव की उम्मीद के रूप में, लेकिन यहाँ क्या हो रहा है थोड़ा सूक्ष्म है: जब आप Stub.do_things() पर कॉल करें, आप सीधे do_things का आह्वान नहीं कर रहे हैं। इसके बजाय, Stub.do_things एक staticmethod उदाहरण को संदर्भित करता है, जिसने हम अपने स्वयं के वर्णनकर्ता प्रोटोकॉल के अंदर जो फ़ंक्शन चाहते हैं उसे लपेट लिया है, जैसे कि आप वास्तव में staticmethod.__get__ का आविष्कार कर रहे हैं, जो पहले हम जिस फ़ंक्शन को चाहते हैं उसे वापस लौटाते हैं, और तब बाद में कॉल किया जाता है।

>>> Stub 
<class __main__.Stub at 0x...> 
>>> Stub.do_things 
<function do_things at 0x...> 
>>> Stub.__dict__['do_things'] 
<staticmethod object at 0x...> 
>>> Stub.do_things() 
Doing things! 

अब तक इतना अच्छा है। इसके बाद, मैं एक डेकोरेटर उस वर्ग इन्स्टेन्शियशन अनुकूलित करने के लिए उपयोग किया जाएगा में वर्ग रैप करने के लिए की जरूरत है - डेकोरेटर निर्धारित करेगा कि क्या नया instantiations अनुमति देने के लिए या कैश की गई उदाहरणों प्रदान: अब

def deco(cls): 
    def factory(*args, **kwargs): 
     # pretend there is some logic here determining 
     # whether to make a new instance or not 
     return cls(*args, **kwargs) 
    return factory 

@deco 
class Stub: 
    @staticmethod 
    def do_things(): 
     """Call this like Stub.do_things(), with no arguments or instance.""" 
     print "Doing things!" 

, स्वाभाविक रूप से इस भाग के रूप में-है staticmethods तोड़ने की उम्मीद की जाएगी, क्योंकि कक्षा अब इसके सजावट के पीछे छिपी हुई है, यानी Stub बिल्कुल कक्षा नहीं है, लेकिन factory का एक उदाहरण है जो Stub के उदाहरणों को उत्पन्न करने में सक्षम है। वास्तव में:

>>> Stub 
<function factory at 0x...> 
>>> Stub.do_things 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: 'function' object has no attribute 'do_things' 
>>> Stub() 
<__main__.Stub instance at 0x...> 
>>> Stub().do_things 
<function do_things at 0x...> 
>>> Stub().do_things() 
Doing things! 

अब तक मैं समझता हूं कि यहां क्या हो रहा है। मेरा लक्ष्य staticmethods की कार्यक्षमता को पुनर्स्थापित करने के लिए है, जैसा कि आप उन्हें उम्मीद करेंगे, भले ही कक्षा लपेटी हो। भाग्य के रूप में, पाइथन stdlib में functools नामक कुछ शामिल है, जो केवल इस उद्देश्य के लिए कुछ औजार प्रदान करता है, यानी, फ़ंक्शन को अन्य कार्यों की तरह व्यवहार करने की तरह व्यवहार करता है। तो मैं इस तरह देखने के लिए मेरी डेकोरेटर बदलने: अब

def deco(cls): 
    @functools.wraps(cls) 
    def factory(*args, **kwargs): 
     # pretend there is some logic here determining 
     # whether to make a new instance or not 
     return cls(*args, **kwargs) 
    return factory 

, बातें दिलचस्प पाने के लिए शुरू:

>>> Stub 
<function Stub at 0x...> 
>>> Stub.do_things 
<staticmethod object at 0x...> 
>>> Stub.do_things() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: 'staticmethod' object is not callable 
>>> Stub() 
<__main__.Stub instance at 0x...> 
>>> Stub().do_things 
<function do_things at 0x...> 
>>> Stub().do_things() 
Doing things! 

प्रतीक्षा .... क्या?functools रैपिंग फ़ंक्शन पर staticmethod की प्रतिलिपि बनाता है, लेकिन यह कॉल करने योग्य नहीं है? क्यों नहीं? मुझे यहाँ क्या याद आया?

मैं थोड़ी देर के लिए इसके साथ खेल रहा था और मैं वास्तव में staticmethod के अपने स्वयं के पुनर्मूल्यांकन के साथ आया था जो इसे इस स्थिति में काम करने की अनुमति देता है, लेकिन मुझे वास्तव में समझ में नहीं आता कि यह क्यों जरूरी था या यदि यह भी है इस समस्या का सबसे अच्छा समाधान।

class staticmethod(object): 
    """Make @staticmethods play nice with decorated classes.""" 

    def __init__(self, func): 
     self.func = func 

    def __call__(self, *args, **kwargs): 
     """Provide the expected behavior inside decorated classes.""" 
     return self.func(*args, **kwargs) 

    def __get__(self, obj, objtype=None): 
     """Re-implement the standard behavior for undecorated classes.""" 
     return self.func 

def deco(cls): 
    @functools.wraps(cls) 
    def factory(*args, **kwargs): 
     # pretend there is some logic here determining 
     # whether to make a new instance or not 
     return cls(*args, **kwargs) 
    return factory 

@deco 
class Stub: 
    @staticmethod 
    def do_things(): 
     """Call this like Stub.do_things(), with no arguments or instance.""" 
     print "Doing things!" 

वास्तव में यह बिल्कुल के रूप में की उम्मीद काम करता है:: यहाँ पूरा उदाहरण है

>>> Stub 
<function Stub at 0x...> 
>>> Stub.do_things 
<__main__.staticmethod object at 0x...> 
>>> Stub.do_things() 
Doing things! 
>>> Stub() 
<__main__.Stub instance at 0x...> 
>>> Stub().do_things 
<function do_things at 0x...> 
>>> Stub().do_things() 
Doing things! 

क्या दृष्टिकोण आप एक सजाया वर्ग के भीतर की उम्मीद के रूप में एक staticmethod व्यवहार बनाने के लिए ले जाएगा? क्या यह सबसे अच्छा तरीका है? अंतर्निहित staticmethod __call__ को अपने आप पर क्यों लागू नहीं करता है ताकि यह बिना किसी झगड़े के काम कर सके?

धन्यवाद।

def deco(cls): 
    class factory(cls): 
     def __new__(cls_factory, *args, **kwargs): 
      # pretend there is some logic here determining 
      # whether to make a new instance or not 
      return cls.__new__(*args, **kwargs) 
    return factory 

है कि यह करना चाहिए:

+1

क्लास विधि का उपयोग क्यों नहीं करें? इसे एक ही तरीके से कहा जा सकता है, लेकिन यह अधिक शक्तिशाली है और (I * think *) इस और अन्य मेटाप्रोग्रामिंग के साथ अधिक संगत है। – delnan

+0

यह मेरे लिए 'classmethod 'का उपयोग करने के लिए हुआ, हालांकि वे या तो कॉल करने योग्य नहीं हैं (जिसका अर्थ है कि यह वही समस्या है जो मैं वर्तमान में कर रहा हूं), और यह भी कि क्योंकि मैं कुछ हद तक' Stub.class_attribute का संदर्भ देने के स्पष्टीकरण की तरह हूं '' classmethod' के अंदर 'cls.class_attribute' के बजाय' staticmethod' के अंदर '। – robru

उत्तर

4

समस्या यह है कि आप कक्षा से Stub को किसी वर्ग से फ़ंक्शन में बदल रहे हैं। यह एक बहुत ही गंभीर उल्लंघन है और यह आश्चर्य की बात नहीं है कि चीजें तोड़ रही हैं।

तकनीकी कारण यह है कि आपके staticmethod रों तोड़ रहे है कि __name__, __doc__ और __module__ आदि (स्रोत: http://hg.python.org/cpython/file/3.2/Lib/functools.py) को कॉपी करके functools.wraps काम करता है आवरण को लपेटा उदाहरण से, अपडेट करते समय आवरण के __dict__ से लिपटे उदाहरण के __dict__। यह अब स्पष्ट होना चाहिए कि क्यों staticmethod काम नहीं करता है - इसके वर्णक प्रोटोकॉल को कक्षा के बजाय फ़ंक्शन पर बुलाया जा रहा है, इसलिए यह एक बाध्य कॉल करने योग्य लौटने पर छोड़ देता है और केवल अपने गैर-कॉल करने योग्य स्वयं को लौटाता है।

w.r.t. वास्तव में जो आप रुचि रखते हैं (कुछ प्रकार के सिंगलटन?), आप शायद अपने सजावटी को __new__ के साथ कक्षा वापस करने के लिए चाहते हैं जिसमें आवश्यक व्यवहार हो। तुम्हें पता है, अवांछित बुलाया जा रहा है __init__ के बारे में चिंतित होने की जरूरत नहीं है जब तक अपने आवरण वर्ग के रूप में __new__ वास्तव में, आवरण वर्ग प्रकार के एक मूल्य के बजाय लिपटे वर्ग का एक उदाहरण वापस नहीं करता है:

def deco(wrapped_cls): 
    @functools.wraps(wrapped_cls) 
    class Wrapper(wrapped_cls): 
     def __new__(cls, *args, **kwargs): 
      ... 
      return wrapped_cls(*args, **kwargs) 
    return Wrapper 

नोट wrapped_cls सजावट के लिए तर्क (जो रैपर वर्ग में बंद हो जाता है) और Wrapper.__new__ पर तर्क के बीच का अंतर।

ध्यान दें कि कक्षा को लपेटने वाली कक्षा पर functools.wraps का उपयोग करना बिल्कुल ठीक है - बस एक फ़ंक्शन को लपेटने वाली कक्षा पर नहीं!

तुम भी लपेटा वर्ग, जिस स्थिति में आप की जरूरत नहीं है संशोधित कर सकते हैं functools.wraps:

def deco(wrapped_cls): 
    def __new__(cls, *args, **kwargs) 
     ... 
     return super(wrapped_cls, cls)(*args, **kwargs) 
    wrapped_cls.__new__ = classmethod(__new__) 
    return wrapped_cls 

नोट तथापि है कि इस विधि मौजूदा उदाहरणों पर __init__ लागू है, तो आप होगा खत्म हो जाएगा उस के आसपास काम करने के लिए (उदाहरण के लिए __init__ को मौजूदा उदाहरणों पर शॉर्ट-सर्किट में लपेटकर)।

एक परिशिष्ट के रूप में: आपके फ़ंक्शन-रैपिंग-ए-क्लास सजावट को उन मामलों में काम करना संभव हो सकता है जिन्हें आप बहुत सारे प्रयासों के बारे में जानते हैं, लेकिन आप अभी भी समस्याओं में भाग लेंगे - उदाहरण के लिए, isinstance(myObject, Stub)Stub के रूप में काम करने का कोई मौका अब type नहीं है!

+0

सिंगलटन नहीं बल्कि यादें। मेरा [असली दुनिया कोड] (https://github.com/robru/gottengeography/blob/ff6bc740c8aa432703a7783cb708c4a986d890ca/gg/common.py#L41) में एक उत्कृष्ट सिद्धांत है जो पूरी तरह व्यवहार और उपयोग के मामलों को समझाता है (लाइन 41 से 124 प्रासंगिक हैं)। आपके समाधान के लिए मैं कोशिश कर रहा हूं और यह काम नहीं कर रहा है। 'रैपर .__ new__' को भी बुलाया नहीं जाता है और सजाए गए वर्गों में बिना किसी कैशिंग के' __init__' विधियां आती हैं। – robru

+1

@Robru यह मेरा समाधान नहीं है; यह विरासत के माध्यम से संचालित एक पाइथन कक्षा सजावट के लिए मानक पैटर्न है। आप बिना वर्गीकृत वर्ग के लिपटे वर्ग को भी संशोधित कर सकते हैं; मैं इसका एक उदाहरण भी प्रदान करूंगा। – ecatmur

+0

मैंने आपका जवाब स्वीकार कर लिया है क्योंकि आपने स्पष्ट रूप से इस में बहुत प्रयास किया है (धन्यवाद!), लेकिन यह दुर्भाग्य से मेरे लिए काम नहीं करेगा, इस प्रश्न के दायरे से परे कारणों से (देखें लिंक मेरी पिछली टिप्पणी; मैं एक सामान्य तरीके से कार्यों और कक्षाओं को लपेट रहा हूं)। – robru

2

आप लगभग मैं क्या yould किया है किया है। समस्या हो सकती है कि __init__ को __new__ द्वारा लौटाए गए पुराने उदाहरणों पर भी बुलाया जाता है।

+0

एक समस्या जो मैं यहां देखता हूं वह यह है कि मुझे अपने कामकाज चलाने के लिए 'functools.wraps' का उपयोग करने की आवश्यकता है। इसके अलावा मुझे विश्वास है कि आप '__nit__' को फिर से चलाने के बारे में सही हैं, भले ही' __new__' पुराना उदाहरण लौटा रहा हो, जो ठीक है, जिसे मैं टालना चाहता हूं। – robru

+0

हालांकि मैंने आपको अपवित्र किया है क्योंकि आपका समाधान, एक लिपटे फ़ंक्शन के बजाय उप-वर्ग के रूप में, मौजूदा 'staticmethod को मानक बिल्टिन' staticmethod' के साथ काम करने की अनुमति देता है। – robru

संबंधित मुद्दे