পাইথনের `copy` মডিউল

পাইথনের `copy` মডিউল

এই প্রবন্ধটি পাইথনের copy মডিউল ব্যাখ্যা করে।

শ্যালো এবং ডিপ কপির পার্থক্যের ওপর কেন্দ্রীভূত থেকে, অবজেক্ট ডুপ্লিকেশনের মৌলিক মেকানিজম থেকে শুরু করে কাস্টম ক্লাসে প্রয়োগ পর্যন্ত স্পষ্ট ব্যাখ্যা—সাথে ব্যবহারিক উদাহরণ—দেওয়া হয়েছে।

YouTube Video

পাইথনের copy মডিউল

Python-এর copy মডিউল অবজেক্ট ডুপ্লিকেশন (কপি) পরিচালনার জন্য স্ট্যান্ডার্ড মডিউল। লক্ষ্য হলো শ্যালো ও ডিপ কপির পার্থক্য বোঝা এবং কাস্টম অবজেক্টের কপি করার আচরণ নিয়ন্ত্রণ করতে পারা।

বুনিয়াদি: শ্যালো কপি কী?

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

 1# Example: shallow copy with lists and dicts
 2import copy
 3
 4# A nested list
 5original = [1, [2, 3], 4]
 6shallow = copy.copy(original)
 7
 8# Mutate nested element
 9shallow[1].append(99)
10
11print("original:", original)  # nested change visible in original
12print("shallow: ", shallow)

এই কোডে, টপ-লেভেলের তালিকাটি অনুলিপি করা হয়, কিন্তু ভেতরের উপতালিকাগুলো রেফারেন্সের মাধ্যমে শেয়ার থাকে, তাই shallow[1].append(99)-এর প্রভাব original-এও প্রতিফলিত হয়। এটি শ্যালো কপির একটি প্রচলিত আচরণ।

ডিপ কপি কী?

ডিপ কপি অবজেক্টটি এবং এর সকল অভ্যন্তরীণ রেফারেন্টকে রিকার্সিভভাবে ডুপ্লিকেট করে, ফলে সম্পূর্ণ স্বতন্ত্র একটি অবজেক্ট পাওয়া যায়। জটিল নেস্টেড স্ট্রাকচার নিরাপদে ডুপ্লিকেট করতে চাইলে এটি ব্যবহার করুন।

 1# Example: deep copy with nested structures
 2import copy
 3
 4original = [1, [2, 3], {'a': [4, 5]}]
 5deep = copy.deepcopy(original)
 6
 7# Mutate nested element of the deep copy
 8deep[1].append(100)
 9deep[2]['a'].append(200)
10
11print("original:", original)  # original remains unchanged
12print("deep:    ", deep)

এই উদাহরণে, deep-এ পরিবর্তন করলে original-এ কোনো প্রভাব পড়ে না। deepcopy পুরো অবজেক্ট গ্রাফ কপি করে, ফলে একটি স্বতন্ত্র অনুলিপি পাওয়া যায়।

কোনটি ব্যবহার করবেন তা কীভাবে নির্ধারণ করবেন

কোন ধরনের কপি ব্যবহার করবেন তা অবজেক্টের গঠন ও আপনার উদ্দেশ্যের ওপর নির্ভর করা উচিত। অভ্যন্তরের মিউটেবল উপাদানগুলোর পরিবর্তন যেন মূল অবজেক্টে প্রভাব না ফেলে, এমনটি চাইলে deepcopy উপযুক্ত। কারণ এটি পুরো অবজেক্টকে রিকার্সিভভাবে ডুপ্লিকেট করে সম্পূর্ণ স্বতন্ত্র কপি তৈরি করে।

অন্যদিকে, যদি শুধু টপ-লেভেল অবজেক্ট ডুপ্লিকেট করলেই চলে এবং গতি ও মেমরির দক্ষতা গুরুত্বপূর্ণ হয়, তবে copy (শ্যালো কপি) বেশি উপযোগী।

  • অভ্যন্তরীণ মিউটেবল উপাদান বদলালেও মূলটি বদলাক না চান → deepcopy ব্যবহার করুন।
  • শুধু টপ-লেভেল ডুপ্লিকেট করলেই যথেষ্ট এবং পারফরম্যান্স (গতি/মেমরি) অগ্রাধিকার → copy (শ্যালো কপি) ব্যবহার করুন।

বিল্ট-ইন ও ইম্যুটেবল অবজেক্টের হ্যান্ডলিং

ইম্যুটেবল অবজেক্ট (যেমন int, str, এবং যাদের কন্টেন্ট ইম্যুটেবল এমন tuple) সাধারণত কপি করার প্রয়োজন হয় না। copy.copy প্রয়োজনমতো একই অবজেক্ট ফেরত দিতে পারে।

 1# Example: copying immutables
 2import copy
 3
 4a = 42
 5b = copy.copy(a)
 6print(a is b)  # True for small ints (implementation detail)
 7
 8s = "hello"
 9t = copy.deepcopy(s)
10print(s is t)  # True (strings are immutable)

ইম্যুটেবল অবজেক্ট কপির দক্ষতা-সংক্রান্ত লাভ খুব কম হওয়ায়, একই অবজেক্ট পুনঃব্যবহার করা হতে পারে। অ্যাপ্লিকেশন ডিজাইনে এ নিয়ে সাধারণত চিন্তার প্রয়োজন পড়ে না।

কাস্টম ক্লাস: __copy__ এবং __deepcopy__ সংজ্ঞায়ন

আপনার ক্লাসের জন্য ডিফল্ট কপি করা প্রত্যাশিত আচরণ না হলে, আপনি নিজস্ব কপি লজিক দিতে পারেন। আপনি যদি __copy__ এবং __deepcopy__ সংজ্ঞায়িত করেন, তবে copy.copy এবং copy.deepcopy সেগুলোই ব্যবহার করবে।

 1# Example: customizing copy behavior
 2import copy
 3
 4class Node:
 5    def __init__(self, value, children=None):
 6        self.value = value
 7        self.children = children or []
 8
 9    def __repr__(self):
10        return f"Node({self.value!r}, children={self.children!r})"
11
12    def __copy__(self):
13        # Shallow copy: create new Node, but reuse children list reference
14        new = self.__class__(self.value, self.children)
15        return new
16
17    def __deepcopy__(self, memo):
18        # Deep copy: copy value and deepcopy each child
19        new_children = [copy.deepcopy(child, memo) for child in self.children]
20        new = self.__class__(copy.deepcopy(self.value, memo), new_children)
21        memo[id(self)] = new
22        return new
  • __copy__ এবং __deepcopy__ ইমপ্লিমেন্ট করলে, আপনি ক্লাস-নির্দিষ্ট কপি করার আচরণটি নমনীয়ভাবে নিয়ন্ত্রণ করতে পারেন। উদাহরণস্বরূপ, কিছু অ্যাট্রিবিউট একই অবজেক্টকে রেফার (শেয়ার) করা উচিত, আর অন্য অ্যাট্রিবিউটগুলো সম্পূর্ণ নতুন অবজেক্ট হিসেবে ডুপ্লিকেট করা উচিত—এমন পরিস্থিতি আপনি সামলাতে পারেন।
  • __deepcopy__-এ পাঠানো memo আর্গুমেন্টটি একটি ডিকশনারি, যা রিকার্সিভ কপির সময় আগেই প্রক্রিয়াকৃত অবজেক্টগুলোকে নথিভুক্ত করে এবং সার্কুলার রেফারেন্সের কারণে সৃষ্ট ইনফিনিট লুপ প্রতিরোধ করে।
 1# Build tree
 2root_node = Node('root', [Node('child1'), Node('child2')])
 3
 4shallow_copy = copy.copy(root_node)   # shallow copy
 5deep_copy = copy.deepcopy(root_node)  # deep copy
 6
 7# Modify children
 8# affects both root_node and shallow_copy
 9shallow_copy.children.append(Node('child_shallow'))
10# affects deep_copy only
11deep_copy.children.append(Node('child_deep'))
12
13# Print results
14print("root_node:", root_node)
15print("shallow_copy:", shallow_copy)
16print("deep_copy:", deep_copy)
  • এই কোডে, shallow_copy চলকটি 'shallow copy' এর মাধ্যমে তৈরি করা হয়েছে, ফলে অবজেক্টের কেবল টপ-লেভেলটাই অনুলিপি হয়, আর ভেতরে যেসব অবজেক্টের রেফারেন্স রয়েছে (এই ক্ষেত্রে children তালিকা) সেগুলো আসল root_node-এর সঙ্গে শেয়ার থাকে। ফলে, আপনি যখন shallow_copy-এ একটি নতুন নোড যোগ করেন, তখন শেয়ার করা children তালিকাটি আপডেট হয় এবং এই পরিবর্তন root_node-এর বিষয়বস্তুতেও প্রতিফলিত হয়।
  • অন্যদিকে, deep_copy চলকটি 'deep copy' এর মাধ্যমে তৈরি করা হয়, তাই অভ্যন্তরীণ কাঠামোটি রিকার্সিভভাবে অনুলিপি হয় এবং root_node থেকে সম্পূর্ণ স্বতন্ত্র একটি ট্রি তৈরি হয়। অতএব, আপনি deep_copy-এ নতুন নোড যোগ করলেও, তা root_node-কে প্রভাবিত করে না।

চক্রাকার রেফারেন্স (রিকার্সিভ অবজেক্ট) এবং memo-এর গুরুত্ব

কোনো জটিল অবজেক্ট গ্রাফ নিজেকেই রেফার করে (চক্রাকার রেফারেন্স) এমন হলে, deepcopy ইতিমধ্যে কপি হওয়া অবজেক্টগুলো ট্র্যাক করতে এবং অসীম লুপ রোধ করতে memo নামের একটি ডিকশনারি ব্যবহার করে।

 1# Example: recursive list and deepcopy memo demonstration
 2import copy
 3
 4a = []
 5b = [a]
 6a.append(b)  # a -> [b], b -> [a]  (cycle)
 7
 8# deepcopy can handle cycles
 9deep = copy.deepcopy(a)
10print("deep copy succeeded, length:", len(deep))
  • অভ্যন্তরীণভাবে, deepcopy memo ব্যবহার করে ইতিমধ্যে ডুপ্লিকেট হওয়া অবজেক্টগুলোর রেফারেন্স ধরে রাখে, ফলে সাইকেল থাকলেও নিরাপদভাবে কপি করা সম্ভব হয়। আপনি রিকার্সন হাতে করলে অনুরূপ একটি মেকানিজম প্রয়োজন হবে।

copyreg এবং সিরিয়ালাইজেশন-ধাঁচের কাস্টম কপি (অ্যাডভান্সড)

লাইব্রেরির সাথে সমন্বয়ে বিশেষ কপির আচরণ নিবন্ধন করতে চাইলে, অবজেক্ট কীভাবে (পুনঃ)নির্মিত হবে তা রেজিস্টার করার জন্য স্ট্যান্ডার্ড copyreg মডিউল ব্যবহার করতে পারেন। এটি জটিল অবজেক্ট এবং C এক্সটেনশন টাইপের জন্য উপকারী।

 1# Example: using copyreg to customize pickling/copy behavior (brief example)
 2import copy
 3import copyreg
 4
 5class Wrapper:
 6    def __init__(self, data):
 7        self.data = data
 8
 9def reduce_wrapper(obj):
10    # Return callable and args so object can be reconstructed
11    return (Wrapper, (obj.data,))
12
13copyreg.pickle(Wrapper, reduce_wrapper)
14
15w = Wrapper([1, 2, 3])
16w_copy = copy.deepcopy(w)
17print("w_copy.data:", w_copy.data)
  • copyreg ব্যবহার করে আপনি এমন পুনর্গঠন নিয়ম দিতে পারেন যা pickle এবং deepcopy উভয়ের ওপরই প্রভাব ফেলে। এটি একটি অ্যাডভান্সড API, এবং অধিকাংশ ক্ষেত্রে __deepcopy__-ই যথেষ্ট।

ব্যবহারিক সতর্কতা ও ফাঁদসমূহ

copy মডিউল ব্যবহার করে সঠিক আচরণ নিশ্চিত করতে কয়েকটি গুরুত্বপূর্ণ বিষয় আছে। নিচে ডেভেলপমেন্টে প্রায়ই দেখা দেওয়া কিছু সমস্যার ফাঁদ এবং সেগুলো এড়ানোর উপায় ব্যাখ্যা করা হয়েছে।

  • পারফরম্যান্স deepcopy উল্লেখযোগ্য মেমরি ও CPU ব্যবহার করতে পারে, তাই এটি সত্যিই দরকার কি না ভেবে দেখুন।
  • শেয়ার করতে চান এমন উপাদানের হ্যান্ডলিং বড় cache-এর মতো কিছু অ্যাট্রিবিউট শেয়ার অবস্থায় রাখতে চাইলে, __deepcopy__-এর ভেতরে ইচ্ছাকৃতভাবে সেগুলোর রেফারেন্স কপি করা এড়িয়ে যান।
  • ইম্যুটেবল অভ্যন্তরীণ অবস্থা ভেতরে যদি ইম্যুটেবল ডেটা থাকে, তবে কপি করা অপ্রয়োজনীয় হতে পারে।
  • থ্রেড ও এক্সটার্নাল রিসোর্স যেসব রিসোর্স কপি করা যায় না, যেমন socket ও file handle, সেগুলো কপি করা হয়তো অর্থহীন বা ত্রুটি সৃষ্টি করবে; তাই ডিজাইনেই এগুলোকে কপির লক্ষ্য থেকে বাদ দিতে হবে।

ব্যবহারিক উদাহরণ: ডিকশনারি নিরাপদে আপডেট করার একটি প্যাটার্ন

জটিল কনফিগারেশন অবজেক্ট আপডেট করার সময়, এই উদাহরণে মূল সেটিংস সুরক্ষিত রাখতে deepcopy ব্যবহার করা হয়েছে।

 1# Example: safely update a nested configuration using deepcopy
 2import copy
 3
 4default_config = {
 5    "db": {"host": "localhost", "ports": [5432]},
 6    "features": {"use_cache": True, "cache_sizes": [128, 256]},
 7}
 8
 9# Create a working copy to modify without touching defaults
10working = copy.deepcopy(default_config)
11working["db"]["ports"].append(5433)
12working["features"]["cache_sizes"][0] = 512
13
14print("default_config:", default_config)
15print("working:", working)

deepcopy ব্যবহার করে আপনি ডিফল্ট সেটিংসের ক্ষতির ঝুঁকি ছাড়াই নিরাপদে ডেরাইভড কনফিগারেশন তৈরি করতে পারেন। এটি বিশেষ করে কনফিগারেশনের মতো নেস্টেড মিউটেবল স্ট্রাকচারের জন্য উপকারী।

সেরা চর্চা

copy মডিউলটি নিরাপদ ও কার্যকরভাবে ব্যবহার করতে, নিচের ব্যবহারিক নির্দেশনাগুলো মনে রাখা গুরুত্বপূর্ণ।

  • পরিবর্তন হবে কি না এবং তার বিস্তার কতটা—এ ভিত্তিতে শ্যালো ও ডিপ কপির মধ্যে নির্বাচন করুন।
  • প্রত্যাশিত আচরণ পেতে কাস্টম ক্লাসে __copy____deepcopy__ বাস্তবায়ন করুন।
  • ডিপ কপি ব্যয়বহুল হওয়ায়, সম্ভব হলে ডিজাইনের মাধ্যমেই কপির প্রয়োজনীয়তা কমান। অন্যান্য কৌশলের পাশাপাশি ইম্যুটেবিলিটি ও স্পষ্ট ক্লোন মেথড বিবেচনা করুন।
  • চক্রাকার রেফারেন্স সামলাতে হলে, deepcopy ব্যবহার করুন অথবা হাতে memo-সদৃশ কোনো মেকানিজম দিন।
  • এভাবে নকশা করুন যেন ফাইল হ্যান্ডেল ও থ্রেডের মতো বাহ্যিক সম্পদগুলো অনুলিপি না হয়।

উপসংহার

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

আপনি আমাদের ইউটিউব চ্যানেলে ভিজ্যুয়াল স্টুডিও কোড ব্যবহার করে উপরের নিবন্ধটি অনুসরণ করতে পারেন। দয়া করে ইউটিউব চ্যানেলটিও দেখুন।

YouTube Video