डेकोरेटर ऐसे फंक्शन होते हैं जो किसी दूसरे फंक्शन को आर्गुमेंट के रूप में इसलिए लेते हैं ताकि पास किए गए फंक्शन के व्यवहार को बढ़ाया जा सके या उसकी जगह कुछ और रखा जा सके।
अगर फंक्शन A एक डेकोरेटर है और फंक्शन B उसका आर्गुमेंट है, तो फंक्शन A फंक्शन B का व्यवहार बदलता है, बढ़ाता है या उसकी जगह कुछ और रखता है, फंक्शन B के कोड को बदले बिना।
हम कहते हैं कि डेकोरेटर फंक्शन A, फंक्शन B को रैप करता है।
हालाँकि हम व्यवहार को "बदलने" की बात करते हैं, रैप किए गए फंक्शन में असल में कोई बदलाव नहीं होता।
या तो रैप किए गए फंक्शन के आस-पास व्यवहार जोड़ा जाता है (और उसके रिटर्न वैल्यू के आस-पास भी), या फिर रैप किए गए फंक्शन के व्यवहार की जगह कोई दूसरा व्यवहार रख दिया जाता है।
हायर-ऑर्डर फंक्शन ऐसा फंक्शन होता है जो एक या अधिक फंक्शन को आर्गुमेंट के रूप में लेता है और/या एक या अधिक फंक्शन लौटाता है। जो फंक्शन आर्गुमेंट के रूप में इस्तेमाल होता है या किसी दूसरे फंक्शन से लौटाया जाता है, वह फर्स्ट-क्लास फंक्शन होता है। एक Python फंक्शन, कॉलेबल ऑब्जेक्ट होने के नाते, एक फर्स्ट-क्लास फंक्शन है, जिसे किसी वेरिएबल में संग्रहीत किया जा सकता है या किसी भी दूसरी वैल्यू या ऑब्जेक्ट की तरह आर्गुमेंट के रूप में इस्तेमाल किया जा सकता है। हायर-ऑर्डर फंक्शन और फर्स्ट-क्लास फंक्शन मिलकर डेकोरेटर को संभव बनाते हैं।
@ चिह्न डेकोरेटर फंक्शन के नाम से पहले लगाया जाता है और जिस फंक्शन को डेकोरेट करना है, उसके ठीक ऊपर रखा जाता है, इस तरह:
@decorator
def decorated_function():
pass
कुछ डेकोरेटर आर्गुमेंट भी लेते हैं:
@decorator_with_arg(name="Bob")
def decorated_function2():
pass
अगर किसी डेकोरेटर के लिए डिफॉल्ट आर्गुमेंट तय किए गए हैं, तो डेकोरेटर के काम करने के लिए @decorator() कॉल में ब्रैकेट लगाना ज़रूरी है, ठीक वैसे ही जैसे किसी भी फंक्शन को कॉल करते समय लगाते हैं:
@decorator_with_default_arg()
def decorated_function3():
pass
अगर डेकोरेटर पोज़िशनल आर्गुमेंट लेता है, तो आर्गुमेंट न देने पर एक एरर मिलेगी, जो कुछ ऐसी दिखेगी:
TypeError: decorator_with_pos_arg() missing 1 required positional argument: 'name'
@decorator सिंटैक्स, डेकोरेट करने वाले फंक्शन को कॉल करने और डेकोरेट किए जाने वाले फंक्शन को उसे आर्गुमेंट के रूप में पास करने का सिंटैक्टिक शुगर या शॉर्टहैंड है।
डेकोरेटर को कॉल करने के कुछ वैकल्पिक तरीकों के उदाहरण नीचे दिए गए हैं:
def function():
pass
function = decorator(function)
def function2():
pass
function2 = decorator_with_arg(name="Bob")(function2)
def function3():
pass
function3 = decorator_with_default_arg()(function3)
अधिकतर डेकोरेटर किसी दूसरे फंक्शन के व्यवहार को बढ़ाने या बदलने के लिए बनाए जाते हैं, लेकिन कुछ डेकोरेटर सिर्फ अपने रैप किए गए फंक्शन को लौटाने के अलावा कुछ नहीं करते।
डेकोरेटर ऐसे फंक्शन हैं जो कम से कम एक आर्गुमेंट लेते हैं, यानी वह फंक्शन जिसे वे रैप कर रहे हैं। ये आम तौर पर या तो रैप किया गया फंक्शन लौटाते हैं, या ऐसे एक्सप्रेशन का परिणाम लौटाते हैं जिसमें रैप किया गया फंक्शन इस्तेमाल होता है।
एक आसान डेकोरेटर, यानी ऐसा डेकोरेटर जो सिर्फ अपना रैप किया गया फंक्शन लौटाता है, इस तरह लिखा जा सकता है:
>>> def do_nothing(func):
... return func
...
... @do_nothing
... def function4():
... return 4
...
>>> print(function4())
4
कोई डेकोरेटर सिर्फ साइड इफेक्ट जोड़ सकता है, जैसे लॉगिंग के लिए इस्तेमाल होने वाली अतिरिक्त जानकारी:
>>> def my_logger(func):
... print(f"Entering {func.__name__}")
... return func
...
... @my_logger
... def my_func():
... print("Hello")
...
>>> my_func()
Entering my_func
Hello
डेकोरेटर खुद को नहीं लौटाता। वह अपने फंक्शन आर्गुमेंट लौटा सकता है, कोई दूसरा फंक्शन लौटा सकता है, या एक या अधिक वैल्यू लौटा सकता है जो पास किए गए या डेकोरेट किए गए फंक्शन के रिटर्न की जगह लेती हैं। अगर डेकोरेटर कोई दूसरा फंक्शन लौटाता है, तो आम तौर पर वह एक इनर फंक्शन लौटाता है।
एक फंक्शन को किसी दूसरे फंक्शन के अंदर बनाया जा सकता है। ऐसे नेस्टेड फंक्शन को इनर फंक्शन कहते हैं। डेकोरेटर अपने फंक्शन आर्गुमेंट को रैप करने के लिए एक इनर फंक्शन इस्तेमाल कर सकता है। फिर डेकोरेटर अपना इनर फंक्शन लौटाता है। और इनर फंक्शन आगे चलकर मूल फंक्शन आर्गुमेंट लौटा सकता है।
नीचे एक ऐसे डेकोरेटर का उदाहरण है जिसका इस्तेमाल जाँच के लिए किया जा रहा है:
def my_validator(func):
def my_wrapper(world):
print(f"Entering {func.__name__} with {world} argument")
if world == "Pluto":
print("Pluto is not a planet!")
else:
return func(world)
return my_wrapper
@my_validator
def my_func(planet):
print(f"Hello, {planet}!")
my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!
my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Pluto is not a planet!
पहली लाइन में डेकोरेटर के func आर्गुमेंट की परिभाषा है।
अगली लाइन में डेकोरेटर के इनर फंक्शन की परिभाषा है, जो func आर्गुमेंट को रैप करता है।
चूँकि इनर फंक्शन डेकोरेटर के func आर्गुमेंट को रैप करता है, इसलिए उसे वही आर्गुमेंट पास किया जाता है जो func को पास किया जाता है।
ध्यान दीजिए कि रैपर के लिए यह ज़रूरी नहीं है कि वह आर्गुमेंट के लिए वही नाम इस्तेमाल करे जो func में तय किया गया था।
मूल फंक्शन planet इस्तेमाल करता है और डेकोरेटर world, फिर भी डेकोरेटर काम करता है।
इनर फंक्शन या तो func लौटाता है, या अगर world == "Pluto" हो, तो छापता है कि Pluto एक ग्रह नहीं है।
इसके बजाय इसे ऐसा भी लिखा जा सकता था कि ValueError आ जाए।
तो, इनर फंक्शन func को रैप करता है और या तो func लौटाता है या ऐसा कुछ करता है जो func के काम की जगह ले लेता है।
डेकोरेटर अपना इनर फंक्शन लौटाता है।
इनर फंक्शन मूल, पास किए गए फंक्शन को लौटा भी सकता है और नहीं भी।
रैपर फंक्शन या इनर फंक्शन में शर्त के हिसाब से जो कोड चलता है, उसके आधार पर func लौटाया जा सकता है, एरर आ सकती है, या func के रिटर्न टाइप की कोई वैल्यू लौटाई जा सकती है।
*args और **kwargs सिंटैक्स का इस्तेमाल करके ऐसे फंक्शन के लिए डेकोरेटर लिखे जा सकते हैं जो किसी भी संख्या में आर्गुमेंट लेते हैं।
ऐसे फंक्शन के लिए डेकोरेटर का उदाहरण नीचे दिया गया है जो किसी भी संख्या में आर्गुमेंट लेता है:
def double(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) * 2
return wrapper
@double
def add(*args):
return sum(args)
print(add(2, 3, 4))
#-> 18
print(add(2, 3, 4, 5, 6))
#-> 40
यह फंक्शन आर्गुमेंट के रिटर्न वैल्यू को दोगुना करने का काम करता है। अगर हम रिटर्न वैल्यू को तिगुना, चौगुना वगैरह करना चाहें, तो हम डेकोरेटर में ही एक पैरामीटर जोड़ सकते हैं, जैसा हम अगले भाग में दिखाते हैं।
नीचे एक ऐसे डेकोरेटर का उदाहरण है जिसे ऐसा बनाया जा सकता है कि वह डेकोरेट किए गए फंक्शन के रिटर्न वैल्यू को किसी भी संख्या से गुणा कर दे:
def multi(factor=1):
if factor == 0:
raise ValueError("factor must not be 0")
def outer_wrapper(func):
def inner_wrapper(*args, **kwargs):
return func(*args, **kwargs) * factor
return inner_wrapper
return outer_wrapper
@multi(factor=3)
def add(*args):
return sum(args)
print(add(2, 3, 4))
#-> 27
print(add(2, 3, 4, 5, 6))
#-> 60
पहली लाइनें जाँचती हैं कि factor 0 नहीं है।
इसके बाद आउटर रैपर बनाया जाता है।
इसका सिग्नेचर वही है जो हम बिना पैरामीटर वाले डेकोरेटर के लिए चाहते हैं।
आउटर रैपर में एक इनर फंक्शन होता है जिसका सिग्नेचर मूल फंक्शन जैसा ही होता है।
इनर रैपर, मूल फंक्शन से लौटी वैल्यू को डेकोरेटर को पास किए गए आर्गुमेंट से गुणा करने का काम करता है।
आउटर रैपर इनर रैपर को लौटाता है, और डेकोरेटर आउटर रैपर को लौटाता है।
नीचे एक पैरामीटर वाले डेकोरेटर का उदाहरण है, जो यह तय करता है कि मूल फंक्शन को पास किए गए आर्गुमेंट की जाँच होगी या नहीं:
def check_for_pluto(check=True):
def my_validator(func):
def my_wrapper(world):
print(f"Entering {func.__name__} with {world} argument")
if check and world == "Pluto":
print("Pluto is not a planet!")
else:
return func(world)
return my_wrapper
return my_validator
@check_for_pluto(check=False)
def my_func(planet):
print(f"Hello, {planet}!")
my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!
my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Hello, Pluto!
इससे Pluto की जाँच करने और न करने के बीच आसानी से टॉगल किया जा सकता है, और यह my_func को बदले बिना ही हो जाता है।