python - फ़ंक्शन सजावट की श्रृंखला कैसे बनाएं?




decorator python-decorators (11)

मैं पाइथन में दो सजावट कैसे बना सकता हूं जो निम्न कार्य करेंगे?

आप निम्न फ़ंक्शन चाहते हैं, जब कॉल किया जाता है:

@makebold
@makeitalic
def say():
    return "Hello"

वापस देना:

<b><i>Hello</i></b>

सरल समाधान

सबसे अधिक सरलता से ऐसा करने के लिए, सजावटी बनाने वाले जो लम्बदास (अनाम कार्य) लौटाते हैं जो फ़ंक्शन (बंद) पर बंद होते हैं और इसे कॉल करते हैं:

def makeitalic(fn):
    return lambda: '<i>' + fn() + '</i>'

def makebold(fn):
    return lambda: '<b>' + fn() + '</b>'

अब वांछित के रूप में उनका उपयोग करें:

@makebold
@makeitalic
def say():
    return 'Hello'

और अब:

>>> say()
'<b><i>Hello</i></b>'

सरल समाधान के साथ समस्याएं

लेकिन ऐसा लगता है कि हमने लगभग मूल कार्य खो दिया है।

>>> say
<function <lambda> at 0x4ACFA070>

इसे खोजने के लिए, हमें प्रत्येक भेड़ के बच्चे को बंद करने की आवश्यकता होगी, जिसमें से एक को दूसरे में दफनाया जाता है:

>>> say.__closure__[0].cell_contents
<function <lambda> at 0x4ACFA030>
>>> say.__closure__[0].cell_contents.__closure__[0].cell_contents
<function say at 0x4ACFA730>

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

पूर्ण विशेष समाधान - इनमें से अधिकतर समस्याओं का सामना करना

हमारे पास मानक पुस्तकालय में मॉड्यूल wrapsसे सजावटी है functools!

from functools import wraps

def makeitalic(fn):
    # must assign/update attributes from wrapped function to wrapper
    # __module__, __name__, __doc__, and __dict__ by default
    @wraps(fn) # explicitly give function whose attributes it is applying
    def wrapped(*args, **kwargs):
        return '<i>' + fn(*args, **kwargs) + '</i>'
    return wrapped

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return '<b>' + fn(*args, **kwargs) + '</b>'
    return wrapped

यह दुर्भाग्यपूर्ण है कि अभी भी कुछ बॉयलरप्लेट है, लेकिन यह उतना आसान है जितना हम इसे बना सकते हैं।

पायथन 3 में, आप डिफ़ॉल्ट रूप से भी प्राप्त __qualname__और __annotations__असाइन करते हैं।

तो अब:

@makebold
@makeitalic
def say():
    """This function returns a bolded, italicized 'hello'"""
    return 'Hello'

और अब:

>>> say
<function say at 0x14BB8F70>
>>> help(say)
Help on function say in module __main__:

say(*args, **kwargs)
    This function returns a bolded, italicized 'hello'

निष्कर्ष

तो हम देखते हैं कि wrapsरैपिंग फ़ंक्शन लगभग सबकुछ करता है सिवाय इसके कि फ़ंक्शन को तर्क के रूप में क्या लगता है।

ऐसे अन्य मॉड्यूल हैं जो समस्या से निपटने का प्रयास कर सकते हैं, लेकिन समाधान अभी तक मानक पुस्तकालय में नहीं है।

https://code.i-harness.com

मैं पाइथन में दो सजावट कैसे बना सकता हूं जो निम्न कार्य करेंगे?

@makebold
@makeitalic
def say():
   return "Hello"

... जो वापस आना चाहिए:

"<b><i>Hello</i></b>"

मैं इस तरह से वास्तविक एप्लिकेशन में HTML बनाने की कोशिश नहीं कर रहा हूं - सिर्फ यह समझने की कोशिश कर रहा हूं कि सजावटी और सजावटी श्रृंखला कैसे काम करती है।


विभिन्न तर्कों के साथ कार्यों को सजाने के लिए:

def frame_tests(fn):
    def wrapper(*args):
        print "\nStart: %s" %(fn.__name__)
        fn(*args)
        print "End: %s\n" %(fn.__name__)
    return wrapper

@frame_tests
def test_fn1():
    print "This is only a test!"

@frame_tests
def test_fn2(s1):
    print "This is only a test! %s" %(s1)

@frame_tests
def test_fn3(s1, s2):
    print "This is only a test! %s %s" %(s1, s2)

if __name__ == "__main__":
    test_fn1()
    test_fn2('OK!')
    test_fn3('OK!', 'Just a test!')

परिणाम:

Start: test_fn1  
This is only a test!  
End: test_fn1  


Start: test_fn2  
This is only a test! OK!  
End: test_fn2  


Start: test_fn3  
This is only a test! OK! Just a test!  
End: test_fn3  

और निश्चित रूप से आप एक सजावट समारोह से लैम्ब्स भी लौट सकते हैं:

def makebold(f): 
    return lambda: "<b>" + f() + "</b>"
def makeitalic(f): 
    return lambda: "<i>" + f() + "</i>"

@makebold
@makeitalic
def say():
    return "Hello"

print say()

पायथन सजावट दूसरे समारोह में अतिरिक्त कार्यक्षमता जोड़ते हैं

एक इटालिक्स सजावट की तरह हो सकता है

def makeitalic(fn):
    def newFunc():
        return "<i>" + fn() + "</i>"
    return newFunc

ध्यान दें कि फ़ंक्शन के अंदर एक फ़ंक्शन परिभाषित किया गया है। यह मूल रूप से क्या करता है एक नए कार्यों के साथ एक समारोह को प्रतिस्थापित करता है। उदाहरण के लिए, मेरे पास यह कक्षा है

class foo:
    def bar(self):
        print "hi"
    def foobar(self):
        print "hi again"

अब कहें, मैं चाहता हूं कि दोनों कार्य पूरा होने के पहले और बाद में "---" प्रिंट करें। मैं प्रत्येक प्रिंट स्टेटमेंट के पहले और बाद में एक प्रिंट "---" जोड़ सकता था। लेकिन क्योंकि मुझे खुद को दोहराना पसंद नहीं है, मैं एक सजावटी बनाउंगा

def addDashes(fn): # notice it takes a function as an argument
    def newFunction(self): # define a new function
        print "---"
        fn(self) # call the original function
        print "---"
    return newFunction
    # Return the newly defined function - it will "replace" the original

तो अब मैं अपनी कक्षा को बदल सकता हूं

class foo:
    @addDashes
    def bar(self):
        print "hi"

    @addDashes
    def foobar(self):
        print "hi again"

सजावटी पर अधिक जानकारी के लिए, http://www.ibm.com/developerworks/linux/library/l-cpdecor.html


वैकल्पिक रूप से, आप एक फैक्ट्री फ़ंक्शन लिख सकते हैं जो एक सजावटी को वापस लाता है जो फैक्ट्री फ़ंक्शन में दिए गए टैग में सजाए गए फ़ंक्शन के रिटर्न वैल्यू को लपेटता है। उदाहरण के लिए:

from functools import wraps

def wrap_in_tag(tag):
    def factory(func):
        @wraps(func)
        def decorator():
            return '<%(tag)s>%(rv)s</%(tag)s>' % (
                {'tag': tag, 'rv': func()})
        return decorator
    return factory

यह आपको लिखने में सक्षम बनाता है:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say():
    return 'hello'

या

makebold = wrap_in_tag('b')
makeitalic = wrap_in_tag('i')

@makebold
@makeitalic
def say():
    return 'hello'

व्यक्तिगत रूप से मैंने कुछ हद तक सजावटी लिखा होगा:

from functools import wraps

def wrap_in_tag(tag):
    def factory(func):
        @wraps(func)
        def decorator(val):
            return func('<%(tag)s>%(val)s</%(tag)s>' %
                        {'tag': tag, 'val': val})
        return decorator
    return factory

जो उपज होगा:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say(val):
    return val
say('hello')

निर्माण को न भूलें जिसके लिए सजावटी वाक्यविन्यास एक शॉर्टेंड है:

say = wrap_in_tag('b')(wrap_in_tag('i')(say)))

सजावटी कैसे काम करते हैं यह देखने के लिए प्रलेखन देखें। यहां आपने जो पूछा है वह यहां है:

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def makeitalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped

@makebold
@makeitalic
def hello():
    return "hello world"

print hello() ## returns "<b><i>hello world</i></b>"

एक सजावटी फ़ंक्शन परिभाषा लेता है और एक नया फ़ंक्शन बनाता है जो इस फ़ंक्शन को निष्पादित करता है और परिणाम को बदल देता है।

@deco
def do():
    ...

के लिए अभिभावक है:

do = deco(do)

उदाहरण:

def deco(func):
    def inner(letter):
        return func(letter).upper()  #upper
    return inner

इस

@deco
def do(number):
    return chr(number)  # number to letter

इस def do2 (संख्या) के लिए eqivalent है: वापसी chr (संख्या)

do2 = deco(do2)

65 <=> 'ए'

print(do(65))
print(do2(65))
>>> B
>>> B

सजावट को समझने के लिए, यह ध्यान रखना महत्वपूर्ण है कि सजावटी ने एक नया कार्य किया जो आंतरिक है जो func निष्पादित करता है और परिणाम को बदल देता है।


एक ही काम करने का एक और तरीका:

class bol(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<b>{}</b>".format(self.f())

class ita(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<i>{}</i>".format(self.f())

@bol
@ita
def sayhi():
  return 'hi'

या, अधिक लचीलापन:

class sty(object):
  def __init__(self, tag):
    self.tag = tag
  def __call__(self, f):
    def newf():
      return "<{tag}>{res}</{tag}>".format(res=f(), tag=self.tag)
    return newf

@sty('b')
@sty('i')
def sayhi():
  return 'hi'

सजावट को सरल तरीके से समझाने के लिए:

साथ में:

@decor1
@decor2
def func(*args, **kwargs):
    pass

जब करते हैं:

func(*args, **kwargs)

आपने सचमुच किया:

decor1(decor2(func))(*args, **kwargs)

आप कर सकता है दो अलग-अलग सज्जाकार है कि के रूप में सीधे नीचे बताया गया है कि आप क्या चाहते हैं। फ़ंक्शन *args, **kwargsकी घोषणा में उपयोग करें wrapped()जो सजाए गए फ़ंक्शन को कई तर्कों का समर्थन करता है (जो कि उदाहरण say()फ़ंक्शन के लिए वास्तव में आवश्यक नहीं है , लेकिन सामान्यता के लिए शामिल है)।

इसी कारण से, functools.wrapsसजावटी को लपेटा हुआ फ़ंक्शन के मेटा विशेषताओं को बदलने के लिए उपयोग किया जाता है। इससे त्रुटि संदेश और एम्बेडेड फ़ंक्शन प्रलेखन ( func.__doc__) के बजाय सजाए गए फ़ंक्शन के होते हैं wrapped()

from functools import wraps

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<b>" + fn(*args, **kwargs) + "</b>"
    return wrapped

def makeitalic(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<i>" + fn(*args, **kwargs) + "</i>"
    return wrapped

@makebold
@makeitalic
def say():
    return 'Hello'

print(say())  # -> <b><i>Hello</i></b>

शोधन

जैसा कि आप देख सकते हैं कि इन दो सजावट में बहुत सारे डुप्लिकेट कोड हैं। इस समानता को देखते हुए यह बेहतर होगा कि आप एक सामान्य व्यक्ति बनें जो वास्तव में एक सजावटी कारखाना था -दूसरे शब्दों में, एक सजावटी जो अन्य सजावटी बनाता है। इस तरह कम कोड पुनरावृत्ति होगी-और DRY सिद्धांत का पालन करने की अनुमति दें ।

def html_deco(tag):
    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return '<%s>' % tag + fn(*args, **kwargs) + '</%s>' % tag
        return wrapped
    return decorator

@html_deco('b')
@html_deco('i')
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

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

makebold = html_deco('b')
makeitalic = html_deco('i')

@makebold
@makeitalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

या यहां तक ​​कि उन्हें इस तरह गठबंधन करें:

makebolditalic = lambda fn: makebold(makeitalic(fn))

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

दक्षता

जबकि उपर्युक्त उदाहरण सभी काम करते हैं, तब उत्पन्न कोड में अतिरिक्त सजावट के रूप में एक अतिरिक्त मात्रा में ओवरहेड होता है जब कई सजावटी एक बार में लागू होते हैं। इससे कोई फर्क नहीं पड़ता, सटीक उपयोग के आधार पर (उदाहरण के लिए, I / O-bound हो सकता है)।

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

इसके लिए सजावट में स्वयं को अधिक कोड की आवश्यकता होती है, लेकिन यह तब चलता है जब इसे फ़ंक्शन परिभाषाओं से जोड़ा जा रहा है, बाद में जब उन्हें स्वयं कहा जाता है। यह lambdaपहले भी सचित्र किए गए कार्यों का उपयोग करके अधिक पठनीय नाम बनाते समय भी लागू होता है । नमूना:

def multi_html_deco(*tags):
    start_tags, end_tags = [], []
    for tag in tags:
        start_tags.append('<%s>' % tag)
        end_tags.append('</%s>' % tag)
    start_tags = ''.join(start_tags)
    end_tags = ''.join(reversed(end_tags))

    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return start_tags + fn(*args, **kwargs) + end_tags
        return wrapped
    return decorator

makebolditalic = multi_html_deco('b', 'i')

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

#decorator.py
def makeHtmlTag(tag, *args, **kwds):
    def real_decorator(fn):
        css_class = " class='{0}'".format(kwds["css_class"]) \
                                 if "css_class" in kwds else ""
        def wrapped(*args, **kwds):
            return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">"
        return wrapped
    # return decorator dont call it
    return real_decorator

@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello():
    return "hello world"

print hello()

आप कक्षा में सजावट भी लिख सकते हैं

#class.py
class makeHtmlTagClass(object):
    def __init__(self, tag, css_class=""):
        self._tag = tag
        self._css_class = " class='{0}'".format(css_class) \
                                       if css_class != "" else ""

    def __call__(self, fn):
        def wrapped(*args, **kwargs):
            return "<" + self._tag + self._css_class+">"  \
                       + fn(*args, **kwargs) + "</" + self._tag + ">"
        return wrapped

@makeHtmlTagClass(tag="b", css_class="bold_css")
@makeHtmlTagClass(tag="i", css_class="italic_css")
def hello(name):
    return "Hello, {}".format(name)

print hello("Your name")




python-decorators