ডেকোরেটর

ডেকোরেটর #

ফাংশন চাপ্টারে আমরা ফাংশন নিয়ে অল্প-বিস্তর ব্যাপক জ্ঞান লাভ করেছি। পাইথনে ফাংশন হল ফার্স্ট-ক্লাস অবজেক্ট। এর মানে হল আমরা ফাংশনকে নরমাল ভ্যালু বা ভ্যারিয়েবলের মত করে পাস করতে পারি, প্যারামিটার বা আর্গুমেন্ট হিসাবে ব্যবহার করতে পারি। বুঝলাম না মনে হয়। একটা উদাহরণ দেখা যাক।

def get_int_as_str(number):
    return str(number)

def print_int(my_function, number):
    print(my_function(number))
    return

print_int(get_int_as_str, 130)

আউটপুট

130

এখানে আমরা print_int() ফাংশনের ভিতর get_int_as_str() ফাংশনকে আর্গুমেন্ট হিসাবে পাস করেছি। পরে সেটা দিয়ে কোডিং কারিশমা দেখাইছি। এ তো গেল ফাংশনকে আর্গুমেন্ট হিসাবে পাস করার কথা। যদি ফাংশনকেই রিটার্ন করতে চাই? এও কি সম্ভব! হুম, সব সম্ভবের পাইথনে সবই সম্ভব।

def get_int_as_str(number):
    print(str(number))
    return

def print_int(my_function, number):
    return my_function(number)

print_int(get_int_as_str, 130)

আউটপুট

130

এখানে আমরা print_int() ফাংশনের ভিতর get_int_as_str() ফাংশনকে ও একটা ইন্টিজার ভ্যালুকে আর্গুমেন্ট হিসাবে পাস করেছি। print_int() ফাংশনের রিটার্ন অংশে আবার get_int_as_str() ফাংশনকে রিটার্ন করেছি। এইক্ষেত্রে একটা কাহিনি আছে। get_int_as_str() ফাংশনটা রিটার্ন হবার সময় এক্সিকিউট হওয়া শুরু করে। তাই দিনশেষে get_int_as_str() ফাংশন যা রিটার্ন করে print_int() ফাংশনও তাই রিটার্ন করে।

আমরা কিন্তু চাইলে get_int_as_str() ফাংশনটাকে print_int() ফাংশনের ভিতরেই ডিফাইন করতে পারতাম।

def print_int(number):

    def get_int_as_str(number):
        print(str(number))
        return

    get_int_as_str(number)
    return

print_int(130)

আউটপুট

130

উপরের প্রোগ্রামটাকে আরেকটু মডিফাই করে নিচের চেহারা দিতে পারি আমরা।

def print_int(number):

    def get_int_as_str(number):
        print(str(number))
        return

    return get_int_as_str(number)

print_int(130)

আউটপুট

130

নিজেদের অজান্তেই কিন্তু আমরা একটা ডেকোরেটর লিখে ফেলেছি। এইক্ষেত্রে print_int() হল ডেকোরেটর। পাইথনের ভাষায়, ডেকোরেটর হল এমন একটা ফাংশন যা অন্য কোন ফাংশনের কার্যপরিধি কোন প্রকার মডিফিকেশন ছাড়াই এক্সটেন্ড করে। ডেকোরেটর লেখার সিনট্যাক্স হল @decorator_name। উপরের প্রোগ্রামটাকে এখন আমরা এভাবে লিখতে পারি।

def print_int(my_function):

    def any_function():
        return my_function

    return any_function()

@print_int
def get_int_as_str(number):
    print(str(number))
    return

get_int_as_str(130)

আউটপুট

130

বুঝলাম সবাই? ডেকোরেটর তো বুঝলাম। কিন্তু এর সুবিধা তো বোধগম্য হল না। এরজন্য আমরা আরেকটা প্রোগ্রাম দেখব। আমরা এখন একটা ডেকোরেটর ফাংশন লিখব যেটা কোন ফাংশন এক্সিকিউট হতে কত সময় নেয় তা জাতিরে জানান দিবে। এইক্ষেত্রে পাইথন স্টান্ডার্ড মডিউল time এর time() ফাংশনের সহায়তা নেব আমরা।

from time import time

def timer(any_function):
    def count_time():
        start = time()
        any_function()
        stop = time()
        print(stop-start, 'seconds')
        return
    return count_time

@timer
def hello():
    print('Hello World!')
    return

@timer
def another_function():
    for item in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
        print(item)
    return

hello()
another_function()

আউটপুট

Hello World!
0.006817340850830078 seconds
1
2
3
4
5
6
7
8
9
10
0.041167497634887695 seconds

বিল্ট-ইন ডেকোরেটর #

কাস্টম ডেকোরেটর নিয়ে বেশ আলোচনা হয়েছে। এবার আমরা পাইথনের শক্তিশালী তিনটি বিল্ট-ইন ডেকোরেটর সম্পর্কে জানব।

ক্লাস মেথড - @classmethod #

এই ডেকোরেটরটা সম্পর্কে জানার আগে আমরা এর একটা প্রয়োগ দেখব:

class MyClass:
    """Simple Class to define something"""

    def __init__(self):
        pass
    def square(self, x):
        return x**2

    @classmethod
    def cube(self, x):
        return x**3

if __name__ == "__main__":
    myclass = MyClass()
    print(myclass.square(3))
    print(myclass.cube(3))
    print(MyClass.cube(3))
    print(MyClass.square(3))

আউটপুট

9
27
27
Traceback (most recent call last):
  File "/home/maateen/Desktop/test.py", line 19, in <module>
    print(MyClass.square(3))
TypeError: square() missing 1 required positional argument: 'x'

আমরা একটা নিরীহ গোবেচারা ধরনের ক্লাস লিখেছি। এই ক্লাসে square() এবং cube() নামের দুটি মেথডও লিখেছি। আর বিশেষায়িত দিকটা হলো cube () মেথডে ক্লাসমেথড ডেকোরেটর ব্যবহার করেছি। আউটপুটে আমরা TypeError দেখতে পাচ্ছি। square() ফাংশনের একটা পজিশনাল আর্গুমেন্ট মিসিং। কাহিনিটা ময়নাতদন্ত করা যাক।

১৫ নম্বর লাইনে আমরা MyClass-এর একটা ইনস্ট্যান্স বা অবজেক্ট তৈরি করেছি এবং ১৬ ও ১৭ নম্বর লাইনে এই অবজেক্টটা ব্যবহার করে মেথড দুটো কল করেছি। আর তারা ঠিকমতো কাজও করেছে। ১৮ নম্বর লাইনে আমরা সরাসরি ক্লাসের নাম ধরে মেথড কল করেছি। এবারও কোনো সমস্যা হয়নি। কিন্তু ১৯ নম্বর লাইনে একই কাজ করতে গিয়ে এরর দেখাল। ঘটনা কী?

ঘটনা হলো, cube() মেথডে আমরা ক্লাসমেথড ডেকোরেটর ব্যবহার করেছি কিন্তু square() মেথডে তা করিনি। যখন ক্লাসমেথড ডেকোরেটর ব্যবহার করা হয় তখন ওই ফাংশন অবজেক্ট তৈরি না করেও কল করা যায়।

স্ট্যাটিক মেথড - @staticmethod #

সবার আগে আমরা স্ট্যাটিকমেথড ডেকোরেটরের একটা প্রয়োগ দেখব :

class MyClass:
    """Simple Class to define something"""

    def __init__(self):
        pass

    def square(self, x):
        return x**2

    @staticmethod
    def cube(x):
        return x**3

if __name__ == "__main__":
    myclass = MyClass()
    print(myclass.square(3))
    print(myclass.cube(3))
    print(MyClass.cube(3))
    print(MyClass.square(3))

আউটপুট

9
27
27
Traceback (most recent call last):
  File "test.py", line 19, in <module>
    print(MyClass.square(3))
TypeError: square() missing 1 required positional argument: 'x'

ঘটনা কিন্তু আগেরটাই। আর ঘটনার কারণও সেই আগেরটাই। তাহলে আমাদের মনে দুটি প্রশ্ন উঠতে পারে। প্রথমত, cube() মেথডে প্রথম আর্গুমেন্ট হিসেবে self ব্যবহার করা হয়নি কেন? দ্বিতীয়ত, দুটি ডেকোরেটরের মধ্যে তাহলে পার্থক্য কী?

স্ট্যাটিক মেথড ডেকোরেটরের ক্ষেত্রে মেথডের প্রথম আর্গুমেন্ট ক্লাস ইনস্ট্যান্স বা self হবে না। সাধারণ ফাংশন লেখার মতো করে লিখতে হবে। ক্লাসমেথড ডেকোরেটরের ক্ষেত্রে প্রথম আর্গুমেন্ট হবে ক্লাস বা ক্লাস ইনস্ট্যান্স। দুটোর মধ্যে এটাই হলো অমিল। আর দুজনের কাজই কিন্তু এক। একটা মেথডকে সরাসরি ক্লাস থেকে কল করতে পারা যায়। এই কাজই কিন্তু আমরা ওপরে করেছি।

প্রোপার্টি - @property #

পাইথনের অস্থির কনসেপ্টগুলোর মধ্যে প্রোপার্টি একটা। এই কনসেপ্ট অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিংকে করেছে আরও পোক্ত। যাহোক, পাইথন শেলে একটা প্রোগ্রাম লিখি:

>>> class Weather:
...     
...     def __init__(self, temperature=0):
...         self.temperature = temperature
...     
...     def to_fahrenheit(self):
...         return self.temperature * 1.8
...
>>> weather = Weather()
>>> weather.temperature
0
>>> weather.temperature =25
>>> weather.temperature
25
>>> weather.to_fahrenheit()
45.0

এখানে আমরা ক্লাসের ইনস্ট্যান্স থেকে এর প্রোপার্টি অ্যাক্সেস করেছি। এবার প্রোপার্টি ডেকোরেটর ব্যবহার করে আমরা একটা উদাহরণ দেখব :

class MyClass:
    """Simple Class to define something"""

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        return self.first_name + ' ' + self.last_name

if __name__ == "__main__":
    myclass = MyClass('Maksudur', 'Rahman')
    print(myclass.full_name)
    myclass.full_name = 'New Name'

আউটপুট

Maksudur Rahman
Traceback (most recent call last):
  File "test.py", line 15, in <module>
    myclass.full_name = 'New Name'
AttributeError: can't set attribute

প্রোপার্টি ডেকোরটের ব্যবহার করে আমরা full_name() মেথডকে রিড-অনলি অ্যাট্রিবিউটে পরিণত করেছি। তাই সাধারণভাবে ডট চিহ্ন (.) দিয়ে full_name-কে অ্যাক্সেস করতে পারছি। আর রিড-অনলি হওয়ায় এই অ্যাট্রিবিউটে নতুন কিছু দেওয়া যাচ্ছে না।

আশা করি বুঝেছি সবাই। তাহলে এরকম আরো কিছু ডেকোরেটর লিখে প্রাকটিস চালিয়ে যাই।

মন্তব্য করুন