|
| 1 | +#Python Object Memory Allocation |
| 2 | +a = "Hello, World!" # The memory is allocated for this string object |
| 3 | + |
| 4 | +# Assigning the same object to another variable |
| 5 | +b = a # No new memory is allocated; 'b' references the same object as 'a' |
| 6 | + |
| 7 | +# Printing memory addresses of 'a' and 'b' |
| 8 | +print(f"Memory address of 'a': {id(a)}") # Output: Memory address of 'a' |
| 9 | +print(f"Memory address of 'b': {id(b)}") # Output: Memory address of 'b' (same as 'a') |
| 10 | + |
| 11 | +# Creating a new object |
| 12 | +c = "Python" # New memory is allocated for this different string object |
| 13 | + |
| 14 | +# Printing memory address of 'c' |
| 15 | +print(f"Memory address of 'c': {id(c)}") # Output: Memory address of 'c' (different from 'a' and 'b') |
| 16 | + |
| 17 | +#output |
| 18 | +# Memory address of 'a': 126707795390512 |
| 19 | +# Memory address of 'b': 126707795390512 |
| 20 | +# Memory address of 'c': 11043008 |
| 21 | + |
| 22 | + |
| 23 | +# Reference Counting in Python |
| 24 | +import sys # Importing sys module to access reference count |
| 25 | + |
| 26 | +# Creating an object (a list in this case) |
| 27 | +my_list = [1, 2, 3] |
| 28 | + |
| 29 | +# Checking the reference count of 'my_list' |
| 30 | +print(f"Initial reference count of 'my_list': {sys.getrefcount(my_list)}") # Output: Reference count |
| 31 | + |
| 32 | +# Creating additional references to the same object |
| 33 | +ref1 = my_list |
| 34 | +ref2 = my_list |
| 35 | + |
| 36 | +# Checking the updated reference count of 'my_list' |
| 37 | +print(f"Updated reference count of 'my_list': {sys.getrefcount(my_list)}") # Output: Increased reference count |
| 38 | + |
| 39 | +# Deleting one reference |
| 40 | +del ref1 |
| 41 | + |
| 42 | +# Checking the reference count after deletion |
| 43 | +print(f"Reference count after deleting 'ref1': {sys.getrefcount(my_list)}") # Output: Decreased reference count |
| 44 | + |
| 45 | + |
| 46 | +#Python Garbage Collection |
| 47 | +import gc # Importing garbage collector module |
| 48 | + |
| 49 | +# Defining a simple class to create objects |
| 50 | +class MyClass: |
| 51 | + def __init__(self, name): |
| 52 | + self.name = name |
| 53 | + |
| 54 | +# Creating an object instance |
| 55 | +obj1 = MyClass("Object1") |
| 56 | + |
| 57 | +# Checking if the object is tracked by garbage collector |
| 58 | +print(f"Is 'obj1' tracked by GC?: {gc.is_tracked(obj1)}") # Output: True |
| 59 | + |
| 60 | +# Deleting the object reference |
| 61 | +del obj1 |
| 62 | + |
| 63 | +# Forcing garbage collection |
| 64 | +gc.collect() |
| 65 | + |
| 66 | +# Checking the status after collection |
| 67 | +print("Garbage collection completed.") |
| 68 | + |
| 69 | +#Memory Leaks and Circular References |
| 70 | + |
| 71 | +import gc |
| 72 | + |
| 73 | +# Defining two classes to create a circular reference |
| 74 | +class A: |
| 75 | + def __init__(self): |
| 76 | + self.b = None # Placeholder for an instance of B |
| 77 | + |
| 78 | +class B: |
| 79 | + def __init__(self): |
| 80 | + self.a = None # Placeholder for an instance of A |
| 81 | + |
| 82 | +# Creating instances of both classes |
| 83 | +obj_a = A() |
| 84 | +obj_b = B() |
| 85 | + |
| 86 | +# Creating a circular reference |
| 87 | +obj_a.b = obj_b |
| 88 | +obj_b.a = obj_a |
| 89 | + |
| 90 | +# Forcing garbage collection |
| 91 | +gc.collect() |
| 92 | + |
| 93 | +# Breaking the circular reference |
| 94 | +obj_a.b = None |
| 95 | +obj_b.a = None |
| 96 | + |
| 97 | +# Forcing garbage collection again |
| 98 | +gc.collect() |
| 99 | + |
| 100 | +print("Circular reference has been handled.") |
| 101 | + |
| 102 | + |
| 103 | +# Explanation: |
| 104 | + |
| 105 | +# Circular References: 'obj_a' references 'obj_b' and vice versa, forming a circular reference. |
| 106 | +# Garbage Collection Handling: Even with circular references, Python's garbage collector can detect and clean them up if references are broken ('gc.collect()'). |
| 107 | + |
| 108 | + |
| 109 | +# Optimizing Memory Usage with '__slots__' |
| 110 | + |
| 111 | +# Without __slots__ |
| 112 | +class WithoutSlots: |
| 113 | + def __init__(self, name, age): |
| 114 | + self.name = name |
| 115 | + self.age = age |
| 116 | + |
| 117 | +# With __slots__ |
| 118 | +class WithSlots: |
| 119 | + __slots__ = ['name', 'age'] # Define allowed attributes |
| 120 | + |
| 121 | + def __init__(self, name, age): |
| 122 | + self.name = name |
| 123 | + self.age = age |
| 124 | + |
| 125 | +# Creating instances |
| 126 | +obj1 = WithoutSlots("Alice", 30) |
| 127 | +obj2 = WithSlots("Bob", 25) |
| 128 | + |
| 129 | +# Checking the memory sizes |
| 130 | +import sys |
| 131 | +print(f"Memory usage without __slots__: {sys.getsizeof(obj1)} bytes") # Output: Memory size |
| 132 | +print(f"Memory usage with __slots__: {sys.getsizeof(obj2)} bytes") # Output: Reduced memory size |
| 133 | + |
| 134 | + |
| 135 | +# Explanation: |
| 136 | + |
| 137 | +# '__slots__' Usage: Reduces memory usage by preventing the creation of a default '__dict__' for each instance. |
| 138 | +# Memory Optimization: The memory footprint is smaller for 'WithSlots' than for 'WithoutSlots'. |
| 139 | + |
| 140 | +# Using Weak References |
| 141 | + |
| 142 | +import weakref # Importing weakref module |
| 143 | + |
| 144 | +# Defining a class to create objects |
| 145 | +class MyClass: |
| 146 | + def __init__(self, name): |
| 147 | + self.name = name |
| 148 | + |
| 149 | +# Creating a strong reference |
| 150 | +obj = MyClass("MyObject") |
| 151 | + |
| 152 | +# Creating a weak reference to the object |
| 153 | +weak_obj = weakref.ref(obj) |
| 154 | + |
| 155 | +# Checking if weak reference is alive |
| 156 | +print(f"Is weak reference alive?: {weak_obj() is not None}") # Output: True |
| 157 | + |
| 158 | +# Deleting the strong reference |
| 159 | +del obj |
| 160 | + |
| 161 | +# Checking if weak reference is still alive |
| 162 | +print(f"Is weak reference alive after deleting strong reference?: {weak_obj() is not None}") # Output: False |
| 163 | + |
| 164 | + |
| 165 | +# Monitoring Memory Usage with psutil |
| 166 | + |
| 167 | +# psutil (process and system utilities) is a cross-platform library for retrieving information on running processes and system utilization (CPU, memory, disks, network, sensors) in Python. It is useful mainly for system monitoring, profiling and limiting process resources and management of running processes. |
| 168 | + |
| 169 | +import psutil # Importing the psutil module to monitor system performance |
| 170 | + |
| 171 | +# Function to check memory usage |
| 172 | +def memory_usage(): |
| 173 | + process = psutil.Process() # Get the current process |
| 174 | + mem_info = process.memory_info() # Get memory information |
| 175 | + print(f"Memory usage: {mem_info.rss / (1024 * 1024):.2f} MB") # Print memory usage in MB |
| 176 | + |
| 177 | +# Creating a large list |
| 178 | +large_list = [i for i in range(1000000)] |
| 179 | + |
| 180 | +# Checking memory usage |
| 181 | +memory_usage() # Output: Memory usage before clearing the list |
| 182 | + |
| 183 | +# Clearing the list to free memory |
| 184 | +large_list.clear() |
| 185 | + |
| 186 | +# Checking memory usage after clearing the list |
| 187 | +memory_usage() # Output: Reduced memory usage after clearing |
| 188 | + |
| 189 | + |
| 190 | +# Explanation: |
| 191 | + |
| 192 | +# 'psutil' Library: Provides utilities to monitor memory and system performance. |
| 193 | +# Memory Monitoring: Demonstrates how memory usage changes before and after freeing up resources. |
0 commit comments