পাইথন ৩.০-এ ফাংশন এনোটেশন পরিচয় করানো হয়েছে যা একটি ফিচার যোগ করে যা আপনাকে ফাংশন প্যারামিটার এবং রিটার্ন ভ্যালুতে proizволь মেটাডেটা যোগ করতে দেয়। পাইথন ৩ থেকে, ফাংশন এনোটেশন অফিশিয়ালি পাইথনে যোগ করা হয়েছে (PEP-3107)। প্রাথমিক উদ্দেশ্য ছিল ফাংশন প্যারামিটার এবং রিটার্ন ভ্যালুর সাথে মেটাডেটা লিঙ্ক করার একটি স্ট্যান্ডার্ড উপায় তৈরি করা।
ফাংশন এনোটেশনের বেসিক্স
ফাংশন এনোটেশনের কিছু বেসিক বুঝি:
ফাংশন এনোটেশন প্যারামিটার এবং রিটার্ন ভ্যালু উভয়ের জন্যই সম্পূর্ণ অপশনাল।
ফাংশন এনোটেশন কম্পাইল টাইমে ফাংশনের বিভিন্ন অংশের সাথে proizволь পাইথন এক্সপ্রেশন আসলেট করার একটি উপায় প্রদান করে।
PEP-3107 বিল্ট-ইন টাইপের জন্য কোনো স্ট্যান্ডার্ড সেমান্টিক্স পরিচয় করাতে চেষ্টা করে না। এই সारा কাজ থার্ড-পার্টি লাইব্রেরির ওপর ছোড়া হয়েছে।
সিনট্যাক্স
সিম্পল প্যারামিটারদের এনোটেশন
প্যারামিটারদের জন্য এনোটেশন নিচের ফর্ম নেয়:
def foo(x: expression, y: expression = 20):
...
যখন এক্সেস প্যারামিটারদের জন্য এনোটেশন নিচের মতো:
def foo(*args: expression, **kwargs: expression):
...
নেস্টেড প্যারামিটারের ক্ষেত্রে, এনোটেশন সবসময় প্যারামিটারের নামের পর থাকে, লাস্ট প্যারেনথেসিস পর্যন্ত নয়। নেস্টেড প্যারামিটারের সব প্যারামিটার এনোটেট করা আবশ্যক নয়।
def foo((x1, y1: expression), (x2: expression, y2: expression) = (None, None)):
...
এটি বুঝতে গুরুত্বপূর্ণ যে পাইথন এনোটেশনের সাথে কোনো সেমান্টিক্স দেয় না। এটি শুধু মেটাডেটা আসলেট করার জন্য নাইস সিনট্যাকটিক সাপোর্ট এবং সেটি অ্যাক্সেস করার সহজ উপায় দেয়। এছাড়া, এনোটেশন আবশ্যক নয়।
>>> def func(x:'annotating x', y: 'annotating y', z: int) -> float: print(x + y + z)
উপরোক্ত উদাহরণে, func() ফাংশন তিনটি প্যারামিটার x, y এবং z নেয়, এবং তাদের যোগফল প্রিন্ট করে। প্রথম আর্গুমেন্ট x 'annotating x' স্ট্রিং দিয়ে এনোটেট করা, দ্বিতীয় আর্গুমেন্ট y 'annotating y' স্ট্রিং দিয়ে, এবং তৃতীয় আর্গুমেন্ট z int টাইপ দিয়ে এনোটেট করা। রিটার্ন ভ্যালু float টাইপ দিয়ে এনোটেট করা। এখানে '->' সিনট্যাক্স রিটার্ন ভ্যালু এনোটেট করার জন্য।
আউটপুট
>>> func(2,3,-4)
1
>>> func('Function','-','Annotation')
Function-Annotation
উপরে আমরা func() দুবার কল করি, একবার int আর্গুমেন্টে এবং একবার স্ট্রিং আর্গুমেন্টে। দুটি কেসেই, func() সঠিকভাবে কাজ করে এবং এনোটেশনগুলো নजरअন্দাজ করা হয়। তাই আমরা দেখতে পাচ্ছি এনোটেশনগুলো func() ফাংশনের এক্সিকিউশনে কোনো প্রভাব ফেলে না।
ফাংশন এনোটেশন অ্যাক্সেস করা
সকল এনোটেশন __annotations__ নামে একটি ডিকশনারিতে সংরক্ষিত থাকে, যা নিজে ফাংশনের একটি অ্যাট্রিবিউট।
>>> def func(x:'annotating x', y: 'annotating y', z: int) -> float: print(x + y + z)
>>> func.__annotations__
{'x': 'annotating x', 'y': 'annotating y', 'z': , 'return': }
উপরের কোড উদাহরণে দেখা যাচ্ছে, এনোটেশন টাইপ ডিক্লোরেশন নয়, যদিও সেগুলো নিশ্চিতভাবেই সেজন্য ব্যবহার করা যেতে পারে এবং কিছু অন্যান্য ল্যাঙ্গুয়েজে ব্যবহৃত টাইপিং সিনট্যাক্সের সাথে সাদৃশ্য আছে, যেমন নিচে দেখানো হলো:
>>> def func(a: 'python', b: {'category: ' 'language'}) -> 'yep':
pass
>>> func.__annotations__
{'a': 'python', 'b': {'category: language'}, 'return': 'yep'}
>>>
এই প্রোদ্য অরবিট্রারি এক্সপ্রেশন, অর্থাৎ অরবিট্রারি মান __annotations__ ডিকশনারিতে সংরক্ষিত হতে পারে। যদিও পাইথন নিজে এগুলো욮 কিছুспеসিফিক তাৎপর্য না দিয়ে, শুধু মানগুলো সংরক্ষণ করা উচিত। তবুও, প্যারামিটার এবং রিটার্ন টাইপ ডিফাইন করা ফাংশন এনোটেশনের একটি সাধারণ ব্যবহার।
@no_type_check ডেকোরেটর
যদি আপনি এমন কোনো টুল ব্যবহার করেন যা অনুমান করে এনোটেশন হলো টাইপ ডিক্লোরেশন কিন্তু আপনি সেগুলো অন্য উদ্দেশ্যে ব্যবহার করতে চান, তবে স্ট্যান্ডার্ড @no_type_check ডেকোরেটর ব্যবহার করে আপনার ফাংশনকে সেই প্রসেসিং থেকে মুক্ত রাখুন, যেমন নিচে দেখানো হলো:
>>> from typing import no_type_check
>>> @no_type_check
>>> def func(a: 'python', b: {'category: ' 'language'}) -> 'yep':
pass
>>>
সাধারণত, এটি প্রয়োজন হয় না কারণ অধিকাংশ টুল যা এনোটেশন ব্যবহার করে সেগুলোর পpertiত জানতে পারে কোনগুলো তাদের জন্য। ডেকোরেটরটি কনার কেসের জন্য যেখানে জিনিসগুলো অপরিষ্কার।
ফাংশন ডেকোরেটরের ইনপুট হিসেবে এনোটেশন
এনোটেশন ডেকোরেটরসের সাথে খুব ভালো কম্বাইন করে কারণ এনোটেশন ভ্যালুগুলো ডেকোরেটরকে ইনপুট দিতে একটি ভালো উপায়, এবং ডেকোরেটর-জেনারেটেড র্যাপারগুলো এনোটেশনে অর্থ দিতে কোড রাখার একটি ভালো জায়গা।
from functools import wraps
def adapted(func):
@wraps(func)
def wrapper(**kwargs):
final_args = {}
for name, value in kwargs.items():
adapt = func.__annotations__.get(name)
if adapt is not None:
final_args[name] = adapt(value)
else:
final_args[name] = value
result = func(**final_args)
adapt = func.__annotations__.get('result')
if adapt is not None:
return adapt(result)
return result
return wrapper
@adapted
def func(a: int, b: repr) -> str:
return a
তো, adapted ডেকোরেটর ফাংশনকে একটি র্যাপারে ঘেরে দেয়। এই র্যাপার শুধু কিওয়ার্ড আর্গুমেন্ট অ্যাক্সেপ্ট করে, অর্থাৎ মূল ফাংশন পজিশনাল আর্গুমেন্ট অ্যাক্সেপ্ট করলেও, সেগুলো নাম দিয়ে স্পেসিফাই করতে হবে।
ফাংশন র্যাপ হওয়ার পর, র্যাপার ফাংশনের প্যারামিটার এনোটেশনে অ্যাডাপ্টার খুঁজে বের করে এবং আর্গুমেন্টগুলো রিয়াল ফাংশনে পাস করার আগে সেগুলো প্রয়োগ করে।
ফাংশন রিটার্ন করার পর, র্যাপার রিটার্ন ভ্যালু অ্যাডাপ্টার খুঁজে বের করে; যদি পায়, তবে ফাইনালি রিটার্ন করার আগে রিটার্ন ভ্যালুতে সেটি প্রয়োগ করে।
এখানে যা হচ্ছে তার ইমপ্লিকেশন গুলো কনসিডার করলে, সেগুলো খুব ইম্প্রেসিভ। আমরা আসলে একটি ফাংশনে প্যারামিটার পাস করার মানে বা একটি ভ্যালু রিটার্ন করার মানে মডিফাই করেছি।
কিওয়ার্ড আর্গুমেন্ট
কभी কখনো, একটি মেথডের এক বা একাধিক প্যারামিটার কোনো প্রসেসিং দরকার নয়, শুধু self-এর একটি অ্যাট্রিবিউটে অ্যাসাইন করা দরকার। কি আমরা ডেকোরেটর এবং এনোটেশন ব্যবহার করে এটাকে অটোমেটিক করতে পারি? অবশ্যই পারি।
from functools import wraps
def store_args(func):
@wraps(func)
def wrapper(self, **kwargs):
for name, value in kwargs.items():
attrib = func.__annotations__.get(name)
if attrib is True:
attrib = name
if isinstance(attrib, str):
setattr(self, attrib, value)
return func(self, **kwargs)
return wrapper
class A:
@store_args
def __init__(self, first: True, second: 'example'):
pass
a = A(first=5, second=6)
assert a.first == 5
assert a.example == 6