मेमरी वापर मोठ्या प्रमाणात कमी करण्यासाठी आणि ॲट्रिब्यूट ॲक्सेस स्पीड वाढवण्यासाठी पायथनच्या __slots__ चा वापर करा. बेंचमार्क, फायदे-तोटे आणि सर्वोत्तम पद्धतींसह एक विस्तृत मार्गदर्शक.
पायथनचे __slots__: मेमरी ऑप्टिमायझेशन आणि ॲट्रिब्यूट स्पीडचा सखोल अभ्यास
सॉफ्टवेअर डेव्हलपमेंटच्या जगात, कार्यक्षमतेला खूप महत्त्व आहे. पायथन डेव्हलपर्ससाठी, यात भाषेची अविश्वसनीय लवचिकता आणि संसाधनांची कार्यक्षमतेची आवश्यकता यांच्यात नाजूक संतुलन साधावे लागते. विशेषत: डेटा-इंटेंसिव्ह ॲप्लिकेशन्समध्ये, मेमरी वापराचे व्यवस्थापन करणे हे सर्वात सामान्य आव्हान आहे. जेव्हा तुम्ही लाखो किंवा अब्जावधी लहान ऑब्जेक्ट्स तयार करत असता, तेव्हा प्रत्येक बाइट महत्त्वाचा असतो.
येथे पायथनचे कमी ज्ञात पण शक्तिशाली वैशिष्ट्य उपयोगात येते: __slots__. हे बर्याचदा मेमरी ऑप्टिमायझेशनसाठी जादूची किल्ली म्हणून ओळखले जाते, परंतु त्याचे खरे स्वरूप अधिक सूक्ष्म आहे. हे फक्त मेमरी वाचवण्याबद्दल आहे का? हे तुमच्या कोडला खरोखरच वेगवान करते का? आणि ते वापरण्याचे छुपे धोके काय आहेत?
हे सर्वसमावेशक मार्गदर्शक तुम्हाला पायथनच्या __slots__ चा सखोल अभ्यास करण्यासाठी घेऊन जाईल. आम्ही मानक पायथन ऑब्जेक्ट्स पडद्यामागे कसे कार्य करतात, मेमरी आणि स्पीडवर __slots__ च्या वास्तविक जगातील परिणामांचे बेंचमार्क, त्याची आश्चर्यकारक गुंतागुंत आणि फायदे-तोटे शोधू आणि हे शक्तिशाली ऑप्टिमायझेशन टूल कधी वापरावे आणि कधी वापरू नये हे ठरवण्यासाठी एक स्पष्ट फ्रेमवर्क प्रदान करू.
डीफॉल्ट: पायथन ऑब्जेक्ट्स __dict__ सह ॲट्रिब्यूट्स कसे स्टोअर करतात
__slots__ काय करते हे समजून घेण्यापूर्वी, ते कशाची जागा घेते हे आपण समजून घेतले पाहिजे. डीफॉल्टनुसार, पायथनमध्ये कस्टम क्लासच्या प्रत्येक इंस्टन्समध्ये __dict__ नावाचे एक विशेष ॲट्रिब्यूट असते. हे अक्षरशः एक डिक्शनरी आहे जी इंस्टन्सचे सर्व ॲट्रिब्यूट स्टोअर करते.
एक साधे उदाहरण पाहूया: 2D पॉइंट दर्शवणारा क्लास.
import sys
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
#Create an instance
p1 = Point2D(10, 20)
# Attributes are stored in __dict__
print(p1.__dict__) # Output: {'x': 10, 'y': 20}
# Let's check the size of the __dict__ itself
print(f"Size of the Point2D instance's __dict__: {sys.getsizeof(p1.__dict__)} bytes")
तुमच्या पायथन व्हर्जन आणि सिस्टम आर्किटेक्चरनुसार (उदा. लहान डिक्शनरीसाठी पायथन 3.10+ वर 64 बाइट्स) आउटपुट थोडा बदलू शकतो, परंतु महत्त्वाचा मुद्दा हा आहे की या डिक्शनरीचा स्वतःचा मेमरी फूटप्रिंट आहे, जो इंस्टन्स ऑब्जेक्ट आणि त्यात असलेल्या व्हॅल्यूजपेक्षा वेगळा आहे.
लवचिकतेची शक्ती आणि किंमत
हा __dict__ दृष्टिकोन पायथनच्या डायनॅमिझमचा आधारस्तंभ आहे. हे तुम्हाला कोणत्याही वेळी इंस्टन्समध्ये नवीन ॲट्रिब्यूट जोडण्याची परवानगी देते, ज्याला बर्याचदा "मंकी-पॅचिंग" म्हणतात:
# Add a new attribute on the fly
p1.z = 30
print(p1.__dict__) # Output: {'x': 10, 'y': 20, 'z': 30}
ही लवचिकता जलद डेव्हलपमेंट आणि विशिष्ट प्रोग्रामिंग पॅटर्नसाठी उत्कृष्ट आहे. तथापि, याची एक किंमत आहे: मेमरी ओव्हरहेड.
पायथनमध्ये डिक्शनरीज अत्यंत ऑप्टिमाइज्ड आहेत परंतु साध्या डेटा स्ट्रक्चरपेक्षा अधिक कॉम्प्लेक्स आहेत. त्यांना जलद की लुकअप प्रदान करण्यासाठी हॅश टेबल मेंटेन ठेवण्याची आवश्यकता आहे, ज्यासाठी संभाव्य हॅश टकराव व्यवस्थापित करण्यासाठी आणि कार्यक्षम आकार बदलण्याची परवानगी देण्यासाठी अतिरिक्त मेमरी आवश्यक आहे. जेव्हा तुम्ही Point2D चे लाखो इंस्टन्स तयार करता, तेव्हा प्रत्येकजण स्वतःची __dict__ घेऊन जातो आणि ही मेमरी ओव्हरहेड वेगाने जमा होते.
10 दशलक्ष शिरोबिंदू असलेले 3D मॉडेल प्रोसेस करणार्या ॲप्लिकेशनची कल्पना करा. जर प्रत्येक शिरोबिंदू ऑब्जेक्टमध्ये 64 बाइट्सची __dict__ असेल, तर ती 640 मेगाबाइट्स मेमरी फक्त डिक्शनरीजद्वारे वापरली जाते, त्यामध्ये असलेल्या इंटिजर किंवा फ्लोट व्हॅल्यूजचा हिशोब न करता! ही समस्या __slots__ सोडवण्यासाठी डिझाइन केले आहे.
परिचय `__slots__`: मेमरी-सेव्हिंग पर्याय
__slots__ हे एक क्लास व्हेरिएबल आहे जे तुम्हाला इंस्टन्समध्ये कोणते ॲट्रिब्यूट असतील हे स्पष्टपणे घोषित करण्यास अनुमती देते. __slots__ परिभाषित करून, तुम्ही पायथनला अनिवार्यपणे सांगत आहात: "या क्लासच्या इंस्टन्समध्ये फक्त हे विशिष्ट ॲट्रिब्यूट असतील. त्यांच्यासाठी __dict__ तयार करण्याची गरज नाही."
डिक्शनरीऐवजी, पायथन इंस्टन्ससाठी मेमरीमध्ये एक निश्चित जागा रिझर्व्ह करते, जी घोषित ॲट्रिब्यूटसाठी व्हॅल्यूजचे पॉइंटर्स स्टोअर करण्यासाठी पुरेशी असते, जसे की C स्ट्रक्चर किंवा टपल.
आपला Point2D क्लास __slots__ वापरण्यासाठी रिफॅक्टर करूया.
class SlottedPoint2D:
# Declare the instance attributes
# It can be a tuple (most common), list, or any iterable of strings.
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
वरवर पाहता, हे अगदी सारखेच दिसते. पण पडद्यामागे, सर्व काही बदलले आहे. __dict__ गायब झाली आहे.
p_slotted = SlottedPoint2D(10, 20)
# Trying to access __dict__ will raise an error
try:
print(p_slotted.__dict__)
except AttributeError as e:
print(e) # Output: 'SlottedPoint2D' object has no attribute '__dict__'
मेमरी सेव्हिंगचे बेंचमार्किंग
खरा "व्वा" क्षण तेव्हा येतो जेव्हा आपण मेमरी वापराची तुलना करतो. हे अचूकपणे करण्यासाठी, ऑब्जेक्ट साइज कशी मोजली जाते हे आपल्याला समजून घेणे आवश्यक आहे. sys.getsizeof() ऑब्जेक्टची बेस साइज रिपोर्ट करते, परंतु त्या गोष्टींची साइज नाही ज्याचा तो संदर्भ देतो, जसे की __dict__.
import sys
# --- Regular Class ---
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
# --- Slotted Class ---
class SlottedPoint2D:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
# Create one instance of each to compare
p_normal = Point2D(1, 2)
p_slotted = SlottedPoint2D(1, 2)
# The size of the slotted instance is much smaller
# It's typically the base object size plus a pointer for each slot.
size_slotted = sys.getsizeof(p_slotted)
# The size of the normal instance includes its base size and a pointer to its __dict__.
# The total size is the instance size + the __dict__ size.
size_normal = sys.getsizeof(p_normal) + sys.getsizeof(p_normal.__dict__)
print(f"Size of a single SlottedPoint2D instance: {size_slotted} bytes")
print(f"Total memory footprint of a single Point2D instance: {size_normal} bytes")
# Now let's see the impact at scale
NUM_INSTANCES = 1_000_000
# In a real application, you would use a tool like memory_profiler
# to measure the total memory usage of the process.
# We can estimate the savings based on our single-instance calculation.
size_diff_per_instance = size_normal - size_slotted
total_memory_saved = size_diff_per_instance * NUM_INSTANCES
print(f"\nCreating {NUM_INSTANCES:,} instances...")
print(f"Memory saved per instance by using __slots__: {size_diff_per_instance} bytes")
print(f"Estimated total memory saved: {total_memory_saved / (1024*1024):.2f} MB")
एका सामान्य 64-बिट सिस्टमवर, तुम्ही प्रति इंस्टन्स 40-50% मेमरी बचत अपेक्षित करू शकता. एक सामान्य ऑब्जेक्ट त्याच्या बेससाठी 16 बाइट्स + __dict__ पॉइंटरसाठी 8 बाइट्स + रिकाम्या __dict__ साठी 64 बाइट्स घेऊ शकतो, एकूण 88 बाइट्स. दोन ॲट्रिब्यूट असलेल्या स्लॉटेड ऑब्जेक्टला फक्त 32 बाइट्स लागू शकतात. प्रति इंस्टन्स ~56 बाइट्सचा हा फरक 10 लाख इंस्टन्ससाठी 56 MB च्या बचतीत रूपांतरित होतो. हे मायक्रो-ऑप्टिमायझेशन नाही; हा एक मूलभूत बदल आहे जो एक अव्यवहार्य ॲप्लिकेशन व्यवहार्य बनवू शकतो.
दुसरे वचन: वेगवान ॲट्रिब्यूट ॲक्सेस
मेमरी बचतीव्यतिरिक्त, __slots__ कार्यप्रदर्शन सुधारण्यासाठी देखील प्रसिद्ध आहे. सिद्धांत असा आहे: डिक्शनरीमध्ये हॅश लुकअप करण्यापेक्षा (ॲरे इंडेक्ससारखे) निश्चित मेमरी ऑफसेटमधून व्हॅल्यू ॲक्सेस करणे वेगवान आहे.
__dict__ॲक्सेस:obj.xमध्ये की'x'साठी डिक्शनरी लुकअप समाविष्ट आहे.__slots__ॲक्सेस:obj.xमध्ये विशिष्ट स्लॉटमध्ये डायरेक्ट मेमरी ॲक्सेस समाविष्ट आहे.
परंतु प्रत्यक्षात ते किती वेगवान आहे? हे शोधण्यासाठी पायथनचे बिल्ट-इन timeit मॉड्यूल वापरूया.
import timeit
# Setup code to be run once before timing
SETUP_CODE = """
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
class SlottedPoint2D:
__slots__ = 'x', 'y'
def __init__(self, x, y):
self.x = x
self.y = y
p_normal = Point2D(1, 2)
p_slotted = SlottedPoint2D(1, 2)
"""
# Test attribute reading
read_normal = timeit.timeit("p_normal.x", setup=SETUP_CODE, number=10_000_000)
read_slotted = timeit.timeit("p_slotted.x", setup=SETUP_CODE, number=10_000_000)
print("--- Attribute Reading ---")
print(f"Time for __dict__ access: {read_normal:.4f} seconds")
print(f"Time for __slots__ access: {read_slotted:.4f} seconds")
speedup = (read_normal - read_slotted) / read_normal * 100
print(f"Speedup: {speedup:.2f}%")
print("\n--- Attribute Writing ---")
# Test attribute writing
write_normal = timeit.timeit("p_normal.x = 3", setup=SETUP_CODE, number=10_000_000)
write_slotted = timeit.timeit("p_slotted.x = 3", setup=SETUP_CODE, number=10_000_000)
print(f"Time for __dict__ access: {write_normal:.4f} seconds")
print(f"Time for __slots__ access: {write_slotted:.4f} seconds")
speedup = (write_normal - write_slotted) / write_normal * 100
print(f"Speedup: {speedup:.2f}%")
परिणामांवरून दिसून येईल की __slots__ खरोखरच वेगवान आहे, परंतु सुधारणा सामान्यतः 10-20% च्या दरम्यान असते. हे नगण्य नसले तरी, मेमरी बचतीच्या तुलनेत ते खूपच कमी आहे.
मुख्य निष्कर्ष: __slots__ चा वापर प्रामुख्याने मेमरी ऑप्टिमायझेशनसाठी करा. स्पीड सुधारणा एक स्वागतार्ह, परंतु दुय्यम बोनस म्हणून विचारात घ्या. कार्यप्रदर्शन वाढ कम्प्युटेशनली इंटेंसिव्ह अल्गोरिदममधील टाइट लूपमध्ये सर्वाधिक संबंधित आहे जेथे ॲट्रिब्यूट ॲक्सेस लाखो वेळा होतो.
फायदे-तोटे आणि "गॉटचा": __slots__ सह तुम्ही काय गमावता
__slots__ हे फुकटचे जेवण नाही. कार्यक्षमतेतील वाढ लवचिकता गमावून आणि काही गुंतागुंत निर्माण करून येते, विशेषत: इनहेरिटन्स (Inheritance) संबंधित. __slots__ चा प्रभावीपणे वापर करण्यासाठी या फायद्या-तोट्या समजून घेणे महत्त्वाचे आहे.
1. डायनॅमिक ॲट्रिब्यूटचा लॉस
हा सर्वात महत्त्वाचा परिणाम आहे. ॲट्रिब्यूटचे पूर्वनिर्धारण करून, तुम्ही रनटाइममध्ये नवीन ॲट्रिब्यूट जोडण्याची क्षमता गमावता.
p_slotted = SlottedPoint2D(10, 20)
# This works fine
p_slotted.x = 100
# This will fail
try:
p_slotted.z = 30 # 'z' was not in __slots__
except AttributeError as e:
print(e) # Output: 'SlottedPoint2D' object has no attribute 'z'
हे वर्तन एक वैशिष्ट्य असू शकते, बग नाही. हे एक कठोर ऑब्जेक्ट मॉडेल लागू करते, ॲट्रिब्यूट तयार होण्यापासून प्रतिबंधित करते आणि क्लासचा "आकार" अधिक predictable बनवते. तथापि, जर तुमचा डिझाइन डायनॅमिक ॲट्रिब्यूट असाइनमेंटवर अवलंबून असेल, तर __slots__ हा पर्याय नाही.
2. __dict__ आणि __weakref__ ची अनुपस्थिती
आपण पाहिल्याप्रमाणे, __slots__ __dict__ तयार होण्यापासून प्रतिबंधित करते. __dict__ द्वारे इंट्रोस्पेक्शनवर अवलंबून असलेल्या लायब्ररी किंवा टूल्ससह कार्य करण्याची आवश्यकता असल्यास हे समस्याप्रधान ठरू शकते.
त्याचप्रमाणे, __slots__ __weakref__ चे ऑटोमॅटिक क्रिएशन देखील प्रतिबंधित करते, हे ॲट्रिब्यूट ऑब्जेक्टला weakly referenceable होण्यासाठी आवश्यक आहे. वीक रेफरन्स हे प्रगत मेमरी व्यवस्थापन टूल आहे जे ऑब्जेक्ट्सना कचरा जमा होण्यापासून प्रतिबंधित न करता ट्रॅक करण्यासाठी वापरले जाते.
उपाय: तुम्हाला त्यांची आवश्यकता असल्यास तुम्ही तुमच्या __slots__ व्याख्येत '__dict__' आणि '__weakref__' स्पष्टपणे समाविष्ट करू शकता.
class HybridSlottedPoint:
# We get memory savings for x and y, but still have __dict__ and __weakref__
__slots__ = ('x', 'y', '__dict__', '__weakref__')
def __init__(self, x, y):
self.x = x
self.y = y
p_hybrid = HybridSlottedPoint(5, 10)
p_hybrid.z = 20 # This works now, because __dict__ is present!
print(p_hybrid.__dict__) # Output: {'z': 20}
import weakref
w_ref = weakref.ref(p_hybrid) # This also works now
print(w_ref)
'__dict__' जोडल्याने तुम्हाला हायब्रीड मॉडेल मिळते. स्लॉटेड ॲट्रिब्यूट (x, y) अजूनही कार्यक्षमतेने हाताळले जातात, तर इतर कोणतेही ॲट्रिब्यूट __dict__ मध्ये ठेवले जातात. हे काही मेमरी बचत कमी करते परंतु सर्वात सामान्य ॲट्रिब्यूट ऑप्टिमाइझ करताना लवचिकता टिकवून ठेवण्यासाठी उपयुक्त तडजोड असू शकते.
3. इनहेरिटन्सची गुंतागुंत
येथे __slots__ अवघड होऊ शकते. त्याचे वर्तन पालक आणि चाइल्ड क्लास कसे परिभाषित केले जातात यावर अवलंबून असते.
सिंगल इनहेरिटन्स
-
जर पॅरेंट क्लासमध्ये
__slots__असेल पण चाइल्डमध्ये नसेल: चाइल्ड क्लास पॅरेंटच्या ॲट्रिब्यूटसाठी स्लॉटेड वर्तन इनहेरिट करेल परंतु त्याची स्वतःची__dict__देखील असेल. याचा अर्थ असा आहे की चाइल्ड क्लासचे इंस्टन्स पॅरेंटच्या इंस्टन्सपेक्षा मोठे असतील.class SlottedBase: __slots__ = ('a',) class DictChild(SlottedBase): # No __slots__ defined here def __init__(self): self.a = 1 self.b = 2 # 'b' will be stored in __dict__ c = DictChild() print(f"Child has __dict__: {hasattr(c, '__dict__')}") # Output: True print(c.__dict__) # Output: {'b': 2} -
जर पॅरेंट आणि चाइल्ड क्लास दोन्ही
__slots__परिभाषित करत असतील: चाइल्ड क्लासमध्ये__dict__नसेल. त्याची प्रभावी__slots__स्वतःची__slots__आणि पॅरेंटची__slots__यांचे संयोजन असेल.महत्वाचे: जर पॅरेंटच्याclass SlottedBase: __slots__ = ('a',) class SlottedChild(SlottedBase): __slots__ = ('b',) # Effective slots are ('a', 'b') def __init__(self): self.a = 1 self.b = 2 sc = SlottedChild() print(f"Child has __dict__: {hasattr(sc, '__dict__')}") # Output: False try: sc.c = 3 # Raises AttributeError except AttributeError as e: print(e)__slots__मध्ये चाइल्डच्या__slots__मध्ये सूचीबद्ध केलेले ॲट्रिब्यूट असेल, तर ते निरर्थक आहे पण सामान्यतः निरुपद्रवी आहे.
मल्टिपल इनहेरिटन्स
__slots__ सह मल्टिपल इनहेरिटन्स एक धोकादायक क्षेत्र आहे. नियम कठोर आहेत आणि अनपेक्षित त्रुटी येऊ शकतात.
-
मुख्य नियम: चाइल्ड क्लासने
__slots__प्रभावीपणे वापरण्यासाठी (म्हणजे__dict__शिवाय), त्याच्या सर्व पॅरेंट क्लासमध्ये देखील__slots__असणे आवश्यक आहे. जरी एका पॅरेंट क्लासमध्ये__slots__नसले (आणि त्यामुळे__dict__असेल), तरी चाइल्ड क्लासमध्ये देखील__dict__असेल. -
TypeErrorचा सापळा: चाइल्ड क्लास एकापेक्षा जास्त पॅरेंट क्लासकडून इनहेरिट करू शकत नाही ज्यांच्या दोघांमध्ये नॉन-एम्प्टी__slots__आहेत.हे बंधन अस्तित्वात आहे कारण स्लॉटेड ऑब्जेक्ट्ससाठी मेमरी लेआउट क्लास क्रिएशनच्या वेळी निश्चित केले जाते. पायथन एक सुसंगत आणि स्पष्ट मेमरी लेआउट तयार करू शकत नाही जे दोन स्वतंत्र पॅरेंट क्लासमधील स्लॉट एकत्र करते.class SlotParentA: __slots__ = ('x',) class SlotParentB: __slots__ = ('y',) try: class ProblemChild(SlotParentA, SlotParentB): pass except TypeError as e: print(e) # Output: multiple bases have instance lay-out conflict
निकाल: __slots__ कधी वापरावे आणि कधी वापरू नये
फायदे आणि तोटे स्पष्टपणे समजून घेतल्याने, आपण एक व्यावहारिक निर्णय घेण्याचे फ्रेमवर्क स्थापित करू शकतो.
ग्रीन फ्लॅग: __slots__ चा वापर तेव्हा करा जेव्हा...
- तुम्ही मोठ्या संख्येने इंस्टन्स तयार करत आहात. हा प्राथमिक उपयोग आहे. जर तुम्ही लाखो ऑब्जेक्ट्सशी व्यवहार करत असाल, तर मेमरी बचत ॲप्लिकेशन चालते की क्रॅश होते यातील फरक असू शकते.
-
ऑब्जेक्टचे ॲट्रिब्यूट निश्चित आहेत आणि वेळेआधीच माहित आहेत.
__slots__डेटा स्ट्रक्चर, रेकॉर्ड किंवा साध्या डेटा ऑब्जेक्टसाठी योग्य आहे ज्यांचा "आकार" बदलत नाही. - तुम्ही मेमरी-बाधित वातावरणात आहात. यात IoT डिव्हाइसेस, मोबाइल ॲप्लिकेशन्स किंवा उच्च-घनता सर्व्हरचा समावेश आहे जिथे प्रत्येक मेगाबाइट मौल्यवान आहे.
-
तुम्ही कार्यक्षमतेतील अडथळा ऑप्टिमाइझ करत आहात. जर प्रोफाइलिंग दर्शवते की टाइट लूपमधील ॲट्रिब्यूट ॲक्सेस लक्षणीय स्लोडाउन आहे, तर
__slots__पासून मिळणारा माफक स्पीड बूस्ट फायदेशीर ठरू शकतो.
सामान्य उदाहरणे:
- मोठ्या ग्राफ किंवा ट्री स्ट्रक्चरमधील नोड्स.
- फिजिकल सिम्युलेशनमधील पार्टिकल्स.
- मोठ्या डेटाबेस क्वेरीमधील पंक्ती दर्शवणारे ऑब्जेक्ट्स.
- उच्च-थ्रूपुट सिस्टममधील इव्हेंट किंवा मेसेज ऑब्जेक्ट्स.
रेड फ्लॅग: __slots__ चा वापर टाळा जेव्हा...
-
लवचिकता महत्त्वाची आहे. जर तुमचा क्लास सामान्य-उद्देशीय वापरासाठी डिझाइन केला असेल किंवा तुम्ही ॲट्रिब्यूट डायनॅमिकली (मंकी-पॅचिंग) जोडण्यावर अवलंबून असाल, तर डीफॉल्ट
__dict__वापरा. -
तुमचा क्लास इतरांनी सबक्लासिंगसाठी असलेल्या पब्लिक API चा भाग आहे. बेस क्लासवर
__slots__लादल्याने सर्व चाइल्ड क्लासवर बंधने येतात, जे तुमच्या युजर्ससाठी एक अप्रिय आश्चर्य असू शकते. -
तुम्ही पुरेसे इंस्टन्स तयार करत नाही आहात जे महत्त्वाचे आहेत. तुमच्याकडे फक्त काही शंभर किंवा हजार इंस्टन्स असल्यास, मेमरी बचत नगण्य असेल. येथे
__slots__लागू करणे अकाली ऑप्टिमायझेशन आहे जे कोणत्याही वास्तविक लाभाशिवाय गुंतागुंत वाढवते. -
तुम्ही कॉम्प्लेक्स मल्टिपल इनहेरिटन्स हायरार्कीशी व्यवहार करत आहात.
TypeErrorबंधने या परिस्थितीत__slots__ला जास्त त्रासदायक बनवू शकतात.
आधुनिक पर्याय: __slots__ अजूनही सर्वोत्तम पर्याय आहे का?
पायथन इकोसिस्टम विकसित झाला आहे आणि हलके ऑब्जेक्ट्स तयार करण्यासाठी __slots__ हे एकमेव टूल नाही. आधुनिक पायथन कोडसाठी, तुम्ही या उत्कृष्ट पर्यायांचा विचार केला पाहिजे.
collections.namedtuple आणि typing.NamedTuple
Namedtuples हे नावे असलेल्या फील्डसह टपल सबक्लास तयार करण्यासाठी एक फॅक्टरी फंक्शन आहे. ते अत्यंत मेमरी-कार्यक्षम आहेत (स्लॉटेड ऑब्जेक्ट्सपेक्षाही जास्त कारण ते खाली टपल आहेत) आणि महत्त्वाचे म्हणजे, immutable आहेत.
from typing import NamedTuple
# Creates an immutable class with type hints
class Point(NamedTuple):
x: int
y: int
p = Point(10, 20)
print(p.x) # 10
try:
p.x = 30 # Raises AttributeError: can't set attribute
except AttributeError as e:
print(e)
जर तुम्हाला इम्युटेबल डेटा कंटेनरची आवश्यकता असेल, तर स्लॉटेड क्लासपेक्षा NamedTuple हा एक चांगला आणि सोपा पर्याय आहे.
दोन्ही जगातील सर्वोत्तम: @dataclass(slots=True)
पायथन 3.7 मध्ये सादर केलेले आणि पायथन 3.10 मध्ये वर्धित केलेले, डेटाक्लास हे गेम-चेंजर आहेत. ते __init__, __repr__ आणि __eq__ यांसारख्या पद्धती आपोआप तयार करतात, ज्यामुळे बॉयलरप्लेट कोड मोठ्या प्रमाणात कमी होतो.
महत्त्वाचे म्हणजे, @dataclass डेकोरेटरमध्ये slots आर्ग्युमेंट आहे (पायथन 3.10 पासून उपलब्ध; पायथन 3.8-3.9 साठी त्याच सोयीसाठी थर्ड-पार्टी लायब्ररी आवश्यक आहे). जेव्हा तुम्ही slots=True सेट करता, तेव्हा डेटाक्लास परिभाषित फील्डवर आधारित आपोआप __slots__ ॲट्रिब्यूट तयार करेल.
from dataclasses import dataclass
@dataclass(slots=True)
class DataPoint:
x: int
y: int
dp = DataPoint(10, 20)
print(dp) # Output: DataPoint(x=10, y=20) - nice repr for free!
print(hasattr(dp, '__dict__')) # Output: False - slots are enabled!
हा दृष्टिकोन तुम्हाला सर्वोत्कृष्ट देतो:
- वाचनीयता आणि संक्षिप्तता: मॅन्युअल क्लास व्याख्येपेक्षा खूपच कमी बॉयलरप्लेट.
- सोय: ऑटो-जनरेटेड स्पेशल पद्धती तुम्हाला सामान्य बॉयलरप्लेट लिहिण्यापासून वाचवतात.
- कार्यक्षमता:
__slots__चे पूर्ण मेमरी आणि स्पीड फायदे. - टाइप सेफ्टी: पायथनच्या टाइपिंग इकोसिस्टमसह उत्तम प्रकारे इंटिग्रेट होते.
पायथन 3.10+ मध्ये लिहिलेल्या नवीन कोडसाठी, साधे, म्युटेबल, मेमरी-कार्यक्षम डेटा-होल्डिंग क्लास तयार करण्यासाठी @dataclass(slots=True) ही तुमची डीफॉल्ट निवड असावी.
निष्कर्ष: एका विशिष्ट जॉबसाठी एक शक्तिशाली टूल
__slots__ हे पायथनच्या डिझाइन तत्त्वज्ञानाचा पुरावा आहे जे डेव्हलपर्सना कार्यक्षमतेच्या सीमा ओलांडण्याची गरज आहे त्यांच्यासाठी शक्तिशाली टूल्स प्रदान करते. हे वैशिष्ट्य अंदाधुंदपणे वापरले जाऊ नये, परंतु एका विशिष्ट आणि सामान्य समस्येचे निराकरण करण्यासाठी एक तीक्ष्ण, अचूक साधन आहे: असंख्य लहान ऑब्जेक्ट्सची उच्च मेमरी किंमत.
__slots__ बद्दल आवश्यक सत्ये पुन्हा सांगूया:
- याचा प्राथमिक फायदा म्हणजे मेमरी वापरात लक्षणीय घट, बर्याचदा इंस्टन्सचा आकार 40-50% ने कमी होतो. हे त्याचे किलर वैशिष्ट्य आहे.
- हे ॲट्रिब्यूट ॲक्सेससाठी दुय्यम, अधिक माफक, स्पीड वाढ प्रदान करते, सामान्यतः सुमारे 10-20%.
- मुख्य तोटा म्हणजे डायनॅमिक ॲट्रिब्यूट असाइनमेंटचा लॉस, एक कठोर ऑब्जेक्ट स्ट्रक्चर लागू करणे.
- हे इनहेरिटन्समध्ये गुंतागुंत निर्माण करते, ज्यासाठी काळजीपूर्वक डिझाइन आवश्यक आहे, विशेषत: मल्टिपल इनहेरिटन्सच्या परिस्थितीत.
-
आधुनिक पायथनमध्ये,
@dataclass(slots=True)हा बर्याचदा एक उत्कृष्ट, अधिक सोयीस्कर पर्याय आहे, जो डेटाक्लासच्या लालित्यसह__slots__चे फायदे एकत्र करतो.
ऑप्टिमायझेशनचा गोल्डन नियम येथे लागू होतो: प्रथम प्रोफाइल करा. जादुई स्पीडअपच्या आशेने तुमच्या कोडबेसमध्ये __slots__ टाकू नका. कोणते ऑब्जेक्ट्स सर्वाधिक मेमरी वापरत आहेत हे ओळखण्यासाठी मेमरी प्रोफाइलिंग टूल्स वापरा. जर तुम्हाला असा क्लास आढळला जो लाखो वेळा इंस्टंशिएट केला जात आहे आणि तो मेमरीचा मोठा भार आहे, तरच __slots__ चा वापर करण्याची वेळ आली आहे. त्याची शक्ती आणि धोके समजून घेऊन, तुम्ही जागतिक प्रेक्षकांसाठी अधिक कार्यक्षम आणि स्केलेबल पायथन ॲप्लिकेशन्स तयार करण्यासाठी प्रभावीपणे वापर करू शकता.