Functools -পাইথনে উচ্চ -অর্ডার ফাংশনের শক্তি

উত্স নোড: 1865357

এই নিবন্ধটি একটি অংশ হিসাবে প্রকাশিত হয়েছিল ডেটা সায়েন্স ব্লগাথন

ভূমিকা

পাইথন স্ট্যান্ডার্ড লাইব্রেরিতে আপনার কোড ক্লিনার এবং সহজতর রাখতে সাহায্য করার জন্য অনেকগুলি দুর্দান্ত মডিউল রয়েছে এবং নিশ্চিতভাবে এক

ক্যাশিং

চলুন শুরু করা যাক মডিউল ফাংক্টুলের কিছু সহজ কিন্তু শক্তিশালী ফাংশন দিয়ে। চলুন শুরু করা যাক ক্যাশিং ফাংশন (পাশাপাশি ডেকোরেটর) দিয়ে - lru_cache,cache এবং cached_property. তাদের মধ্যে প্রথমটি- lru_cache ফাংশন সম্পাদনের শেষ ফলাফলের একটি ক্যাশে সরবরাহ করে, বা অন্য কথায়, তাদের কাজের ফলাফল মনে রাখে:

from functools import lru_cache
import requests @lru_cache(maxsize=32)
def get_with_cache(url): try: r = requests.get(url) return r.text except: return "Not Found" for url in ["https://google.com/", "https://reddit.com/", "https://google.com/", "https://google.com/"]: get_with_cache(url) print(get_with_cache.cache_info())
# CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)
print(get_with_cache.cache_parameters())
# {'maxsize': 32, 'typed': False}

এই উদাহরণে, আমরা একটি ডেকোরেটর ব্যবহার করে GET অনুরোধ করি এবং তাদের ফলাফল (32 ফলাফল পর্যন্ত) ক্যাশে করি @lru_cache. ক্যাশিং আসলে কাজ করে কিনা তা দেখতে, আপনি একটি পদ্ধতি ব্যবহার করে ফাংশন ক্যাশে তথ্য পরীক্ষা করতে পারেন cache_infoযে ক্যাশে হিট এবং হিট সংখ্যা দেখায়. একটি ডেকোরেটর পদ্ধতি প্রদান করে clear_cacheএবং cache_parametersযথাক্রমে ক্যাশে ফলাফল এবং পরীক্ষার পরামিতি বাতিল করার জন্য।

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

Functools মধ্যে ক্যাশিং জন্য আরেকটি ডেকোরেটর একটি ফাংশন সহজভাবে বলা হয় cache. এটি একটি সাধারণ মোড়ক lru_cacheযে আর্গুমেন্ট max_size বাদ দেয়, এটি হ্রাস করে এবং পুরানো মানগুলি সরিয়ে দেয় না।

আরেকটি ডেকোরেটর আপনি ক্যাশিং জন্য ব্যবহার করতে পারেন cached_property. নাম অনুসারে, এটি ক্লাস অ্যাট্রিবিউটের ফলাফল ক্যাশে করতে ব্যবহৃত হয়। এই মেকানিকটি খুব দরকারী যদি আপনার কাছে এমন একটি সম্পত্তি থাকে যা গণনা করা ব্যয়বহুল, তবে এটি একই থাকে।

from functools import cached_property class Page: @cached_property def render(self, value): # Do something with supplied value... # Long computation that renders HTML page... return html

এই সহজ উদাহরণ দেখায়. যেমন ব্যবহার করা যেতে পারে, ক্যাশড প্রপার্টি, উদাহরণস্বরূপ, একটি রেন্ডার করা HTML পৃষ্ঠা ক্যাশে করার জন্য যা ব্যবহারকারীকে বারবার দেখানো প্রয়োজন। নির্দিষ্ট ডাটাবেস প্রশ্ন বা দীর্ঘ গণিত গণনার জন্য একই কাজ করা যেতে পারে।

অন্য সৌন্দর্য cached_propertyএটি শুধুমাত্র লুকআপে চলে, তাই এটি আমাদের অ্যাট্রিবিউটের মান পরিবর্তন করতে দেয়। বৈশিষ্ট্য পরিবর্তন করার পরে, পূর্বে ক্যাশে করা মান পরিবর্তন হবে না, পরিবর্তে, নতুন মান গণনা করা হবে এবং ক্যাশে করা হবে। আপনি ক্যাশেও সাফ করতে পারেন এবং আপনাকে যা করতে হবে তা হল অ্যাট্রিবিউট অপসারণ।

আমি উপরের সমস্ত ডেকোরেটর সম্পর্কে সতর্কতার সাথে এই বিভাগটি শেষ করতে চাই - যদি আপনার ফাংশনের কিছু পার্শ্বপ্রতিক্রিয়া থাকে বা এটি প্রতিবার কল করার সময় পরিবর্তনযোগ্য বস্তু তৈরি করে তবে সেগুলি ব্যবহার করবেন না কারণ এগুলি স্পষ্টতই আপনি ক্যাশে করতে চান এমন ফাংশন নয় .

তুলনা এবং আদেশ

আপনি সম্ভবত ইতিমধ্যেই জানেন যে আপনি পাইথনে তুলনা অপারেটরগুলি প্রয়োগ করতে পারেন যেমন <, >=or ==, সঙ্গে lt, gtor eq. যাইহোক, প্রতিটি উপলব্ধি করা বেশ হতাশাজনক হতে পারে eq, lt, le, gtor ge. সৌভাগ্যবশত, functools একটি ডেকোরেটর আছে @total_orderingএটি আমাদের সাহায্য করতে পারে, কারণ আমাদের যা বাস্তবায়ন করতে হবে তা হল eqঅবশিষ্ট পদ্ধতিগুলির মধ্যে একটি, এবং বাকি ডেকোরেটর স্বয়ংক্রিয়ভাবে তৈরি হবে।

from functools import total_ordering @total_ordering
class Number: def __init__(self, value): self.value = value def __lt__(self, other): return self.value Number(3))
# True
print(Number(1) = Number(15))
# True
print(Number(10) <= Number(2))
# False

এইভাবে, শুধুমাত্র eq এবং হাতে lt থাকা সত্ত্বেও আমরা সমস্ত বর্ধিত তুলনা অপারেশনগুলি বাস্তবায়ন করতে পারি। সবচেয়ে সুস্পষ্ট সুবিধা হল সুবিধা, যা আপনাকে এই সমস্ত অতিরিক্ত যাদু পদ্ধতি লিখতে হবে না, তবে কোডের পরিমাণ এবং এর আরও ভাল পঠনযোগ্যতা হ্রাস করা সম্ভবত আরও গুরুত্বপূর্ণ।

অত্যধিক বোঝাই

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

আংশিক

আমরা সকলেই বিভিন্ন বাহ্যিক লাইব্রেরি বা ফ্রেমওয়ার্কের সাথে কাজ করি, যার মধ্যে অনেকগুলি ফাংশন এবং ইন্টারফেস প্রদান করে যার জন্য আমাদের কলব্যাক পাস করতে হয়, যেমন অ্যাসিঙ্ক্রোনাস অপারেশন বা ইভেন্ট শোনার জন্য। এটি নতুন কিছু নয়, তবে আমাদের যদি কলব্যাকের সাথে কিছু আর্গুমেন্ট পাস করতে হয় তবে কী হবে। এখানেই এটি কার্যকর ফাংক্টুলগুলিতে আসে.partial. ব্যবহার করা যেতে পারে partialএকটি সরলীকৃত ফাংশন স্বাক্ষর সহ একটি নতুন বস্তু তৈরি করে একটি ফাংশনে আর্গুমেন্টের কিছু (বা সমস্ত) হিমায়িত করা। বিভ্রান্ত? আসুন কিছু ব্যবহারিক উদাহরণ দেখে নেওয়া যাক:

def output_result(result, log=None): if log is not None: log.debug(f"Result is: {result}") def concat(a, b): return a + b import logging
from multiprocessing import Pool
from functools import partial logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("default") p = Pool()
p.apply_async(concat, ("Hello ", "World"), callback=partial(output_result, log=logger))
p.close()
p.join()

উপরের কোডটি দেখায় কিভাবে আপনি এটি ব্যবহার করতে পারেন partialএকটি ফাংশন পাস করতে ( output_resultএকটি যুক্তি সহ ( log=logger) একটি কলব্যাক হিসাবে। এই ক্ষেত্রে, আমরা multiprocessing.apply_async ব্যবহার করব, যা অ্যাসিঙ্ক্রোনাসভাবে ফাংশনের ফলাফল গণনা করে ( concat) এবং কলব্যাকের ফলাফল প্রদান করে। যাহোক, apply_asyncএটি সর্বদা প্রথম আর্গুমেন্ট হিসাবে ফলাফলটি পাস করবে, এবং যদি আমরা কোনো অতিরিক্ত আর্গুমেন্ট অন্তর্ভুক্ত করতে চাই, যেমন log=logger এর ক্ষেত্রে, আমাদের আংশিক ব্যবহার করতে হবে।

আমরা একটি মোটামুটি উন্নত ব্যবহারের ক্ষেত্রে বিবেচনা করেছি, এবং একটি সহজ উদাহরণ হ'ল একটি ফাংশনের স্বাভাবিক সৃষ্টি যা stdout এর পরিবর্তে stderr এ লিখে:

import sys
from functools import partial print_stderr = partial(print, file=sys.stderr)
print_stderr("This goes to standard error output")

এই সহজ কৌশলটি দিয়ে, আমরা একটি নতুন কলযোগ্য ফাংশন তৈরি করেছি যা সর্বদা পাস হবে file=sys.stderrআউটপুট করার জন্য একটি নামযুক্ত যুক্তি হিসাবে, যা আমাদের কোডকে সরলীকরণ করতে দেয় এবং প্রতিবার নামযুক্ত আর্গুমেন্টের মান নির্দিষ্ট করতে হয় না।

এবং একটি শেষ ভাল উদাহরণ. আমরা ব্যবহার করতে পারি partialএকটি সামান্য পরিচিত ফাংশন সঙ্গে একযোগে iterএকটি কলযোগ্য বস্তুতে পাস করে একটি পুনরাবৃত্তিকারী তৈরি করতে এবং sentinelin iter, যা এই মত প্রয়োগ করা যেতে পারে:

from functools import partial RECORD_SIZE = 64 # Read binary file...
with open("file.data", "rb") as file: records = iter(partial(file.read, RECORD_SIZE), b'') for r in records: # Do something with the record...

সাধারণত, একটি ফাইল পড়ার সময়, আমরা লাইনের উপর পুনরাবৃত্তি করতে চাই, কিন্তু বাইনারি ডেটার ক্ষেত্রে, আমাদের একটি নির্দিষ্ট আকারের রেকর্ডের উপর পুনরাবৃত্তি করতে হতে পারে। আপনি ব্যবহার করে একটি কলযোগ্য বস্তু তৈরি করে এটি করতে পারেন partialযে ডেটার নির্দিষ্ট অংশ পড়ে এবং এটি পাস করে iterএকটি পুনরাবৃত্তিকারী তৈরি করতে। এই পুনরাবৃত্তিকারীটি ফাইলের শেষ পর্যন্ত না পৌঁছানো পর্যন্ত রিড ফাংশনটিকে কল করে, সর্বদা শুধুমাত্র নির্দিষ্ট খণ্ড আকার নেয় ( RECORD_SIZE) অবশেষে, ফাইল শেষ হলে, মান পৌঁছেছে sentinel(খ”) ফিরে আসে এবং পুনরাবৃত্তি বন্ধ হয়।

সজ্জা

আমরা ইতিমধ্যে পূর্ববর্তী বিভাগে কিছু ডেকোরেটর সম্পর্কে কথা বলেছি, কিন্তু আরো ডেকোরেটর তৈরি করার জন্য ডেকোরেটর নয়। এই ধরনের একটি ডেকোরেটর হল functools.wraps. আপনার কেন এটি প্রয়োজন তা বোঝার জন্য, আসুন কেবল একটি উদাহরণ দেখি:

def decorator(func): def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") greet("Martin")
# Before Calling greet
# Hello, Martin!
# After Calling greet

এই উদাহরণটি দেখায় কিভাবে একটি সাধারণ ডেকোরেটর বাস্তবায়ন করা যেতে পারে। আমরা একটি ফাংশন মোড়ানো যা একটি নির্দিষ্ট কাজ সম্পাদন করে ( actual_func) একটি বাহ্যিক ডেকোরেটরের সাথে, এবং এটি নিজেই একটি ডেকোরেটর হয়ে যায়, যা তারপরে অন্যান্য ফাংশনে প্রয়োগ করা যেতে পারে, উদাহরণস্বরূপ, যেমনটি ক্ষেত্রে greet. আপনি যখন ফাংশন কল করেন, greet আপনি দেখতে পাবেন যে এটি উভয় থেকে বার্তা প্রিন্ট করে actual_funcএবং তার নিজের উপর. ঠিক আছে, তাই না? কিন্তু আমরা যদি এটা করি তাহলে কি হবে:

print(greet.__name__)
# actual_func
print(greet.__doc__)
# Inner function within decorator, which does the actual work

যখন আমরা সজ্জিত ফাংশনের নাম এবং ডকুমেন্টেশন কল করি, তখন আমরা বুঝতে পারি যে সেগুলি ডেকোরেটর ফাংশন থেকে মান দিয়ে প্রতিস্থাপিত হয়েছে। এটি খারাপ কারণ আমরা যখন কিছু ডেকোরেটর ব্যবহার করি তখন আমরা আমাদের সমস্ত ফাংশনের নাম এবং ডকুমেন্টেশন পুনরায় লিখতে পারি না। এই সমস্যা কিভাবে সমাধান করা যেতে পারে? অবশ্যই, functools সঙ্গে.wraps:

from functools import wraps def decorator(func): @wraps(func) def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") print(greet.__name__)
# greet
print(greet.__doc__)
# Says hello to somebody

ফাংশন এর একমাত্র উদ্দেশ্য wrapsনাম, ডকুমেন্টেশন, আর্গুমেন্ট লিস্ট, ইত্যাদি কপি করা, যাতে ওভাররাইটিং রোধ করা যায়। সেই বিবেচনায় wrapsএটি একটি ডেকোরেটরও, আপনি এটিকে আমাদের বাস্তব_ফাঙ্কে যোগ করতে পারেন এবং সমস্যাটি সমাধান করা হয়েছে!

হ্রাস করা

শেষ কিন্তু মডিউল functools মধ্যে অন্তত না এই হ্রাস. সম্ভবত অন্যান্য ভাষা থেকে, আপনি এটি হিসাবে জানতে পারেন fold(হাস্কেল)। এই ফাংশনটি একটি পুনরাবৃত্তিযোগ্য নেয় এবং এর সমস্ত মানকে একটিতে ভাঁজ করে (যোগ করে)। এই জন্য অনেক অ্যাপ্লিকেশন আছে, উদাহরণস্বরূপ:

from functools import reduce
import operator def product(iterable): return reduce(operator.mul, iterable, 1) def factorial(n): return reduce(operator.mul, range(1, n)) def sum(numbers): # Use `sum` function from standard library instead return reduce(operator.add, numbers, 1) def reverse(iterable): return reduce(lambda x, y: y+x, iterable) print(product([1, 2, 3]))
# 6
print(factorial(5))
# 24
print(sum([2, 6, 8, 3]))
# 20
print(reverse("hello"))
# olleh

আপনি কোড থেকে দেখতে পারেন, reduce কোডটিকে একটি লাইনে সরল বা ঘনীভূত করতে পারে, যা অন্যথায় অনেক দীর্ঘ হবে। এটি বলার সাথে সাথে, কোড সঙ্কুচিত করার জন্য এই ফাংশনটির অপব্যবহার করা সাধারণত একটি খারাপ ধারণা, এটিকে "স্মার্ট" করে তোলে, কারণ এটি দ্রুত ভীতিকর এবং অপঠনযোগ্য হয়ে ওঠে। এই কারণে, আমার মতে, এটি সংক্ষিপ্তভাবে ব্যবহার করা উচিত।

এবং যদি আপনি এটি মনে রাখবেন reduce প্রায়শই সবকিছুকে এক লাইনে সংক্ষিপ্ত করে, এটির সাথে পুরোপুরি মিলিত হতে পারে partial:

product = partial(reduce, operator.mul) print(product([1, 2, 3]))
# 6

এবং অবশেষে, যদি আপনার শুধুমাত্র চূড়ান্ত "পতন" ফলাফলের চেয়ে বেশি প্রয়োজন হয়, তাহলে আপনি ব্যবহার করতে পারেন accumulate- অন্য একটি মহান মডিউল থেকে itertools. সর্বাধিক গণনা করতে, এটি নিম্নলিখিত হিসাবে ব্যবহার করা যেতে পারে:

from itertools import accumulate data = [3, 4, 1, 3, 5, 6, 9, 0, 1] print(list(accumulate(data, max)))
# [3, 4, 4, 4, 5, 6, 9, 9, 9]

উপসংহার

আপনি যেমন ফাংক্টুল দেখতে পাচ্ছেন, সেখানে অনেক দরকারী ফাংশন এবং ডেকোরেটর রয়েছে যা আপনার জীবনকে সহজ করে তুলতে পারে, তবে এটি আইসবার্গের টিপ মাত্র। আমি শুরুতেই বলেছিলাম, পাইথন স্ট্যান্ডার্ড লাইব্রেরিতে অনেক ফাংশন রয়েছে যা আপনাকে আরও ভাল কোড লিখতে সাহায্য করে, তাই আমরা এখানে যে ফাংশনগুলি কভার করেছি তা ছাড়াও আপনি অন্যান্য মডিউলগুলিতে মনোযোগ দিতে পারেন, যেমন operatoror itertool. যেকোনো প্রশ্নের জন্য, আপনি আমার মন্তব্য বক্সে আঘাত করতে পারেন। আমি আপনার ইনপুটগুলি সমাধান করার জন্য যথাসাধ্য চেষ্টা করব এবং আশা করি আপনাকে পছন্দসই আউটপুট দিতে পারব। এছাড়াও আপনি LinkedIn এ আমার সাথে যোগাযোগ করতে পারেন:-

https://www.linkedin.com/in/shivani-sharma-aba6141b6/

এই নিবন্ধে প্রদর্শিত মিডিয়াগুলি অ্যানালিটিক্স বিদ্যা মালিকানাধীন নয় এবং এটি লেখকের বিবেচনার ভিত্তিতে ব্যবহৃত হয়।

সূত্র: https://www.analyticsvidhya.com/blog/2021/08/functools-the-power-of-higher-order-functions-in-python/

সময় স্ট্যাম্প:

থেকে আরো বিশ্লেষণ বিদ্যা