क्या करता है configools.wraps?


650

इस सवाल पर एक टिप्पणी में एक और सवाल का जवाब , किसी ने कहा कि वे निश्चित नहीं थे कि क्या functools.wrapsकर रहे थे। इसलिए, मैं यह सवाल इसलिए पूछ रहा हूं ताकि भविष्य के संदर्भ के लिए स्टैकऑवरफ्लो पर इसका एक रिकॉर्ड होगा: क्या करता functools.wrapsहै, बिल्कुल?

जवाबों:


1069

जब आप एक डेकोरेटर का उपयोग करते हैं, तो आप एक फ़ंक्शन को दूसरे के साथ बदल रहे हैं। दूसरे शब्दों में, यदि आपके पास एक डेकोरेटर है

def logged(func):
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

फिर जब आप कहेंगे

@logged
def f(x):
   """does some math"""
   return x + x * x

यह कहने के समान ही है

def f(x):
    """does some math"""
    return x + x * x
f = logged(f)

और आपके फ़ंक्शन fको फ़ंक्शन के साथ बदल दिया जाता है with_logging। दुर्भाग्य से, इसका मतलब है कि यदि आप कहते हैं

print(f.__name__)

यह प्रिंट होगा with_loggingक्योंकि यह आपके नए फ़ंक्शन का नाम है। वास्तव में, यदि आप डॉकस्ट्रिंग को देखते हैं f, तो यह रिक्त होगा क्योंकि with_loggingइसमें डॉकस्ट्रिंग नहीं है, और इसलिए आपके द्वारा लिखा गया डॉकस्ट्रिंग अब नहीं होगा। इसके अलावा, यदि आप उस फ़ंक्शन के लिए pydoc परिणाम को देखते हैं, तो इसे एक तर्क के रूप में सूचीबद्ध नहीं किया जाएगा x; इसके बजाय इसे लेने के रूप में सूचीबद्ध किया जाएगा *argsऔर **kwargsक्योंकि with_log लेता है।

यदि एक डेकोरेटर का उपयोग हमेशा किसी फ़ंक्शन के बारे में इस जानकारी को खोने का मतलब होता है, तो यह एक गंभीर समस्या होगी। इसलिए हमारे पास है functools.wraps। यह एक डेकोरेटर में उपयोग किए जाने वाले फ़ंक्शन को लेता है और फ़ंक्शन नाम, डॉकस्ट्रिंग, तर्क सूची इत्यादि पर कॉपी करने की कार्यक्षमता को जोड़ता है और चूंकि wrapsखुद एक डेकोरेटर है, निम्न कोड सही काम करता है:

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print(f.__name__)  # prints 'f'
print(f.__doc__)   # prints 'does some math'

7
हां, मैं डेकोरेटर मॉड्यूल से बचना पसंद करता हूं क्योंकि फंक्शनलबेल्व्रेप्स मानक पुस्तकालय का हिस्सा है और इस तरह एक और बाहरी निर्भरता का परिचय नहीं देता है। लेकिन डेकोरेटर मॉड्यूल वास्तव में मदद की समस्या को हल करता है, जो किसी दिन उम्मीद के अनुसार कार्य करता है।
एली कोर्टराइट

6
यदि आप रैप्स का उपयोग नहीं करते हैं तो क्या हो सकता है इसका एक उदाहरण यहां है: डॉक्टूल परीक्षण अचानक गायब हो सकते हैं। ऐसा इसलिए है क्योंकि डॉक्टुल्स को सजाए गए कार्यों में परीक्षण नहीं मिल सकता है जब तक कि रैप्स () जैसे कुछ ने उन्हें भर में कॉपी नहीं किया है।
एंड्रयू कुक

88
हमें functools.wrapsइस नौकरी की आवश्यकता क्यों है , क्या यह केवल पहली जगह में डेकोरेटर पैटर्न का हिस्सा नहीं होना चाहिए? आप @wraps का उपयोग कब नहीं करना चाहेंगे?
विम

56
@wim: मैंने कुछ डेकोरेटर लिखे हैं जो @wrapsकॉपी किए गए मूल्यों पर विभिन्न प्रकार के संशोधन या एनोटेशन करने के लिए अपना स्वयं का संस्करण करते हैं। मौलिक रूप से, यह पायथन दर्शन का एक विस्तार है जो स्पष्ट रूप से निहित से बेहतर है और विशेष मामले नियमों को तोड़ने के लिए विशेष रूप से पर्याप्त नहीं हैं। (कोड बहुत सरल है और भाषा समझने में आसान है कि @wrapsकिसी प्रकार के विशेष ऑप्ट-आउट तंत्र का उपयोग करने के बजाय मैन्युअल रूप से प्रदान किया जाना चाहिए।)
ssokolow

35
@LucasMalor सभी डेकोरेटर उन कार्यों को नहीं लपेटते हैं जो वे सजाते हैं। कुछ साइड-इफेक्ट्स को लागू करते हैं, जैसे कि उन्हें किसी प्रकार के लुकअप सिस्टम में पंजीकृत करना।
ssokolow

22

मैं अपने सज्जाकारों के लिए अक्सर फ़ंक्शंस के बजाय क्लासेस का उपयोग करता हूं। मुझे इससे थोड़ी परेशानी हो रही थी क्योंकि किसी ऑब्जेक्ट में वे सभी विशेषताएँ नहीं होंगी जो किसी फ़ंक्शन से अपेक्षित होती हैं। उदाहरण के लिए, किसी ऑब्जेक्ट में विशेषता नहीं होगी __name__। मेरे पास इसके साथ एक विशिष्ट मुद्दा था जो ट्रेसो के लिए बहुत कठिन था जहां त्रुटि "वस्तु की कोई विशेषता नहीं है" "" रिपोर्ट कर रही थी __name__। दुर्भाग्य से, क्लास-स्टाइल डेकोरेटर्स के लिए, मुझे विश्वास नहीं है कि @wrap काम करेगा। मैंने इसके बजाय एक बेस डेकोरेटर क्लास बनाया है:

class DecBase(object):
    func = None

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

    def __getattribute__(self, name):
        if name == "func":
            return super(DecBase, self).__getattribute__(name)

        return self.func.__getattribute__(name)

    def __setattr__(self, name, value):
        if name == "func":
            return super(DecBase, self).__setattr__(name, value)

        return self.func.__setattr__(name, value)

यह वर्ग उस विशेषता को बताता है जिसे सजाया जा रहा है। तो, अब आप एक साधारण डेकोरेटर बना सकते हैं जो यह जाँचता है कि 2 तर्क इस प्रकार निर्दिष्ट हैं:

class process_login(DecBase):
    def __call__(self, *args):
        if len(args) != 2:
            raise Exception("You can only specify two arguments")

        return self.func(*args)

7
जैसा कि डॉक्स @wrapsकहते हैं, @wrapsसिर्फ एक सुविधा समारोह है functools.update_wrapper()। क्लास डेकोरेटर के मामले में, आप update_wrapper()सीधे अपने __init__()तरीके से कॉल कर सकते हैं । तो, आप को बनाने के लिए की जरूरत नहीं है DecBaseसब पर, तुम बस पर शामिल कर सकते हैं __init__()की process_loginलाइन: update_wrapper(self, func)। बस इतना ही।
फैबियानो

14

अजगर 3.5+ के रूप में:

@functools.wraps(f)
def g():
    pass

के लिए एक उपनाम है g = functools.update_wrapper(g, f)। यह तीन काम करता है:

  • यह प्रतियां __module__, __name__, __qualname__, __doc__, और __annotations__की विशेषताओं fपर g। यह डिफ़ॉल्ट सूची में है WRAPPER_ASSIGNMENTS, आप इसे देख सकते हैं फंक्शंस के स्रोत
  • यह सभी तत्वों से अद्यतन करता __dict__है । (देखgf.__dict__WRAPPER_UPDATES स्रोत में )
  • यह एक नई __wrapped__=fविशेषता सेट करता हैg

परिणाम यह है कि gएक ही नाम, डॉकस्ट्रिंग, मॉड्यूल नाम और हस्ताक्षर होने के रूप में प्रकट होता है f। एकमात्र समस्या यह है कि हस्ताक्षर के विषय में यह वास्तव में सच नहीं है: यह सिर्फ इतना है कि inspect.signatureडिफ़ॉल्ट रूप से आवरण श्रृंखलाओं का अनुसरण करता है। डॉकinspect.signature(g, follow_wrapped=False) में बताए अनुसार आप इसका उपयोग कर सकते हैं । इसके कष्टप्रद परिणाम हैं:

  • प्रदान किए गए तर्क अमान्य होने पर भी आवरण कोड निष्पादित होगा।
  • रैपर कोड प्राप्त किए गए * आर्ग्स, ** कवर्स से, अपने नाम का उपयोग करके आसानी से एक तर्क तक नहीं पहुंच सकता है। वास्तव में सभी मामलों (स्थिति, कीवर्ड, डिफ़ॉल्ट) को संभालना होगा और इसलिए कुछ का उपयोग करना होगा Signature.bind()

अब functools.wrapsसजावट करने वालों के बीच थोड़ा सा भ्रम है , क्योंकि डेकोरेटर्स को विकसित करने के लिए एक बहुत ही लगातार उपयोग किया जाता है। लेकिन दोनों पूरी तरह से स्वतंत्र अवधारणाएं हैं। : यदि आप अंतर को समझने में रुचि रखते हैं, मैं दोनों के लिए हेल्पर लाइब्रेरी कार्यान्वित decopatch आसानी से लिखने सज्जाकार के लिए, और makefun के लिए एक हस्ताक्षर के संरक्षण प्रतिस्थापन प्रदान करने के लिए @wraps। ध्यान दें कि makefunप्रसिद्ध decoratorपुस्तकालय की तुलना में एक ही सिद्ध चाल पर निर्भर करता है ।


3

यह रेप्स के बारे में सोर्स कोड है:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')

WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

   Returns a decorator that invokes update_wrapper() with the decorated
   function as the wrapper argument and the arguments to wraps() as the
   remaining arguments. Default arguments are as for update_wrapper().
   This is a convenience function to simplify applying partial() to
   update_wrapper().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

2
  1. शर्त: आपको पता होना चाहिए कि सज्जाकार और विशेष रूप से लपेट के साथ कैसे उपयोग करें। यह टिप्पणी इसे थोड़ा स्पष्ट करती है या यह लिंक इसे बहुत अच्छी तरह से समझाती है।

  2. जब भी हम उदाहरण के लिए उपयोग करते हैं: @wraps अपने स्वयं के आवरण फ़ंक्शन के बाद। इस लिंक में दिए गए विवरण के अनुसार , यह कहता है कि

फंक्शनलबुलस्वैप्स एक फंक्शन डेकोरेटर के रूप में update_wrapper () को इनवॉइस करने के लिए सुविधा फ़ंक्शन है, जब एक आवरण फ़ंक्शन को परिभाषित करता है।

यह आंशिक (update_wrapper, लिपटे = लिपटे, असाइन = असाइन, अपडेट = अपडेट) के बराबर है।

तो @wraps डेकोरेटर वास्तव में फंक्शनलसेक्शुअल (func [, * args] [, ** keywords]) को कॉल देता है।

फंक्शनलबुलस्पर्शियल () परिभाषा कहती है कि

आंशिक () आंशिक फ़ंक्शन अनुप्रयोग के लिए उपयोग किया जाता है जो एक फ़ंक्शन के तर्कों और / या कीवर्ड के कुछ हिस्से को "फ्रीज" करता है जिसके परिणामस्वरूप एक सरलीकृत हस्ताक्षर के साथ एक नई वस्तु होती है। उदाहरण के लिए, आंशिक () का उपयोग एक कॉल करने योग्य बनाने के लिए किया जा सकता है जो इंट () फ़ंक्शन की तरह व्यवहार करता है जहां आधार तर्क दो को परिभाषित करता है:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

जो मुझे इस निष्कर्ष पर पहुंचाता है कि, @wraps आंशिक () को कॉल करता है और यह आपके रैपर फ़ंक्शन को एक पैरामीटर के रूप में पास करता है। अंत में आंशिक () सरलीकृत संस्करण लौटाता है अर्थात आवरण फ़ंक्शन के अंदर क्या है और स्वयं आवरण फ़ंक्शन नहीं है।


-4

संक्षेप में, फंक्शनलबेल्व्वैप्स केवल एक नियमित कार्य है। आइए इस आधिकारिक उदाहरण पर विचार करेंस्रोत कोड की मदद से , हम कार्यान्वयन और चलने के चरणों के बारे में अधिक जानकारी देख सकते हैं:

  1. O1 कहते हैं कि wraps (एफ) एक वस्तु देता है । यह वर्ग की एक वस्तु है आंशिक
  2. अगला चरण @ O1 है ... जो अजगर में डेकोरेटर नोटेशन है। इसका मतलब

आवरण = O1 .__ कॉल __ (आवरण)

के कार्यान्वयन की जांच कर रहा __call__ , हम इस कदम है, (बाएं हाथ की ओर) के बाद कि देखने के आवरण बन जाता है वस्तु से हुई self.func (* self.args, * args, ** newkeywords) के निर्माण की जांच कर रहा O1 में __new__ , हम पता self.func समारोह है update_wrapper । यह अपने 1 पैरामीटर के रूप में , * आर्ग्स , राइट हैंड साइड रैपर का उपयोग करता है । के अंतिम चरण की जांच कर रहा update_wrapper , एक दाहिने हाथ की ओर देख सकते हैं आवरण रूप में की जरूरत संशोधित विशेषताओं में से कुछ के साथ वापस आ गया है।

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