ಜನರೇಟರ್ ಮತ್ತು ಇಟರೇಟರ್ಗಳ ಪರಿಚಯ (Introduction to Generators and Iterators)
ಪೈಥಾನ್ನಲ್ಲಿ, ಇಟರೇಟರ್ (Iterator) ಮತ್ತು ಜನರೇಟರ್ (Generator) ದೊಡ್ಡ ಪ್ರಮಾಣದ ಡೇಟಾವನ್ನು ಸಮರ್ಥವಾಗಿ ನಿರ್ವಹಿಸಲು ಬಳಸುವ ಪ್ರಬಲ ಪರಿಕಲ್ಪನೆಗಳಾಗಿವೆ. ಇವು ಮೆಮೊರಿ ಬಳಕೆಯನ್ನು ಕಡಿಮೆ ಮಾಡಲು ಮತ್ತು ಕಾರ್ಯಕ್ಷಮತೆಯನ್ನು ಸುಧಾರಿಸಲು ಸಹಾಯ ಮಾಡುತ್ತವೆ, ವಿಶೇಷವಾಗಿ ಲಿಸ್ಟ್ಗಳಿಗೆ ಹೋಲಿಸಿದಾಗ.
ಇಟರೇಟರ್ಗಳು (Iterators)
ಇಟರೇಬಲ್ (Iterable) ಎಂದರೆ, ಅದರ ಅಂಶಗಳನ್ನು ಒಂದೊಂದಾಗಿ ಪ್ರವೇಶಿಸಬಹುದಾದ ಯಾವುದೇ ಆಬ್ಜೆಕ್ಟ್. ಉದಾಹರಣೆಗೆ: ಲಿಸ್ಟ್ಗಳು, ಟ್ಯೂಪಲ್ಗಳು, ಸ್ಟ್ರಿಂಗ್ಗಳು, ಮತ್ತು ಡಿಕ್ಷನರಿಗಳು.
ಇಟರೇಟರ್ (Iterator) ಎಂದರೆ, ಇಟರೇಬಲ್ನಿಂದ ಅಂಶಗಳನ್ನು ಒಂದೊಂದಾಗಿ ಹಿಂಪಡೆಯಲು ಬಳಸುವ ಒಂದು ಆಬ್ಜೆಕ್ಟ್. ಇದು __iter__() ಮತ್ತು __next__() ಎಂಬ ಎರಡು ಮೆಥಡ್ಗಳನ್ನು ಹೊಂದಿರುತ್ತದೆ.
* __iter__(): ಇಟರೇಟರ್ ಆಬ್ಜೆಕ್ಟ್ ಅನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ.
* __next__(): ಸರಣಿಯಲ್ಲಿನ ಮುಂದಿನ ಅಂಶವನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ. ಅಂಶಗಳು ಮುಗಿದಾಗ, StopIteration ಎಕ್ಸೆಪ್ಷನ್ ಅನ್ನು ರೈಸ್ ಮಾಡುತ್ತದೆ.
iter() ಫಂಕ್ಷನ್ ಬಳಸಿ ಇಟರೇಬಲ್ನಿಂದ ಇಟರೇಟರ್ ಅನ್ನು ಪಡೆಯಬಹುದು, ಮತ್ತು next() ಫಂಕ್ಷನ್ ಬಳಸಿ ಮುಂದಿನ ಅಂಶವನ್ನು ಪಡೆಯಬಹುದು.
# ಒಂದು ಲಿಸ್ಟ್ (ಇದು ಇಟರೇಬಲ್)
my_list = [10, 20, 30]
# ಲಿಸ್ಟ್ನಿಂದ ಇಟರೇಟರ್ ಪಡೆಯುವುದು
my_iterator = iter(my_list)
# next() ಬಳಸಿ ಅಂಶಗಳನ್ನು ಒಂದೊಂದಾಗಿ ಪಡೆಯುವುದು
print(next(my_iterator)) # Output: 10
print(next(my_iterator)) # Output: 20
print(next(my_iterator)) # Output: 30
# print(next(my_iterator)) # ಇಲ್ಲಿ StopIteration ಎರರ್ ಬರುತ್ತದೆ, ಏಕೆಂದರೆ ಅಂಶಗಳು ಮುಗಿದಿವೆ.
for ಲೂಪ್ ಆಂತರಿಕವಾಗಿ ಇದೇ ರೀತಿ ಕೆಲಸ ಮಾಡುತ್ತದೆ. ಅದು ಮೊದಲು iter() ಅನ್ನು ಕಾಲ್ ಮಾಡಿ ಇಟರೇಟರ್ ಪಡೆಯುತ್ತದೆ, ನಂತರ StopIteration ಎರರ್ ಬರುವವರೆಗೆ next() ಅನ್ನು ಪದೇ ಪದೇ ಕಾಲ್ ಮಾಡುತ್ತದೆ.
ಜನರೇಟರ್ಗಳು (Generators)
ಜನರೇಟರ್ ಎನ್ನುವುದು ಇಟರೇಟರ್ಗಳನ್ನು ರಚಿಸಲು ಒಂದು ಸರಳ ಮತ್ತು ಸುಲಭವಾದ ಮಾರ್ಗವಾಗಿದೆ. ಇದು yield ಕೀವರ್ಡ್ ಬಳಸುವ ಒಂದು ವಿಶೇಷ ಫಂಕ್ಷನ್.
return ಬದಲಿಗೆ yield ಬಳಸಿದಾಗ, ಫಂಕ್ಷನ್ ಒಂದು ಜನರೇಟರ್ ಆಬ್ಜೆಕ್ಟ್ ಅನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ. yield ಸ್ಟೇಟ್ಮೆಂಟ್ ಫಂಕ್ಷನ್ನ ಕಾರ್ಯಗತಗತೆಯನ್ನು ತಾತ್ಕಾಲಿಕವಾಗಿ ನಿಲ್ಲಿಸಿ (pause), ಮೌಲ್ಯವನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ ಮತ್ತು ಅದರ ಸ್ಥಿತಿಯನ್ನು (state) ಉಳಿಸಿಕೊಳ್ಳುತ್ತದೆ. ಮುಂದಿನ ಬಾರಿ next() ಅನ್ನು ಕಾಲ್ ಮಾಡಿದಾಗ, ಅದು ನಿಲ್ಲಿಸಿದ ಸ್ಥಳದಿಂದಲೇ ಮುಂದುವರಿಯುತ್ತದೆ.
yield ಬಳಕೆ (Using yield)
# ಸರಳ ಜನರೇಟರ್ ಫಂಕ್ಷನ್
def my_generator():
print("ಮೊದಲ ಅಂಶವನ್ನು yield ಮಾಡಲಾಗುತ್ತಿದೆ...")
yield 1
print("ಎರಡನೇ ಅಂಶವನ್ನು yield ಮಾಡಲಾಗುತ್ತಿದೆ...")
yield 2
print("ಮೂರನೇ ಅಂಶವನ್ನು yield ಮಾಡಲಾಗುತ್ತಿದೆ...")
yield 3
# ಜನರೇಟರ್ ಆಬ್ಜೆಕ್ಟ್ ರಚಿಸುವುದು
gen = my_generator()
# next() ಬಳಸಿ ಮೌಲ್ಯಗಳನ್ನು ಪಡೆಯುವುದು
print(next(gen))
# Output:
# ಮೊದಲ ಅಂಶವನ್ನು yield ಮಾಡಲಾಗುತ್ತಿದೆ...
# 1
print(next(gen))
# Output:
# ಎರಡನೇ ಅಂಶವನ್ನು yield ಮಾಡಲಾಗುತ್ತಿದೆ...
# 2
print(next(gen))
# Output:
# ಮೂರನೇ ಅಂಶವನ್ನು yield ಮಾಡಲಾಗುತ್ತಿದೆ...
# 3
ಜನರೇಟರ್ಗಳು vs. ಲಿಸ್ಟ್ಗಳು: ಯಾವುದು ಉಪಯುಕ್ತ?
ಜನರೇಟರ್ಗಳು ಮತ್ತು ಲಿಸ್ಟ್ಗಳ ನಡುವಿನ ಮುಖ್ಯ ವ್ಯತ್ಯಾಸವೆಂದರೆ ಅವು ಮೆಮೊರಿಯನ್ನು ಹೇಗೆ ಬಳಸುತ್ತವೆ ಎಂಬುದು.
| ವೈಶಿಷ್ಟ್ಯ | ಲಿಸ್ಟ್ (List) | ಜನರೇಟರ್ (Generator) |
|---|---|---|
| ಮೆಮೊರಿ ಬಳಕೆ | ಎಲ್ಲಾ ಅಂಶಗಳನ್ನು ಒಂದೇ ಬಾರಿಗೆ ಮೆಮೊರಿಯಲ್ಲಿ ಸಂಗ್ರಹಿಸುತ್ತದೆ. | ಅಂಶಗಳನ್ನು ಒಂದೊಂದಾಗಿ, ಅಗತ್ಯವಿದ್ದಾಗ ಮಾತ್ರ ಉತ್ಪಾದಿಸುತ್ತದೆ. |
| ಕಾರ್ಯಕ್ಷಮತೆ | ದೊಡ್ಡ ಲಿಸ್ಟ್ ರಚಿಸಲು ಹೆಚ್ಚು ಸಮಯ ಮತ್ತು ಮೆಮೊರಿ ಬೇಕು. | ತಕ್ಷಣವೇ ಇಟರೇಟ್ ಮಾಡಲು ಪ್ರಾರಂಭಿಸಬಹುದು, ಏಕೆಂದರೆ ಅಂಶಗಳು ಆನ್-ದಿ-ಫ್ಲೈ ರಚನೆಯಾಗುತ್ತವೆ. |
| ಬಳಕೆ | ನೀವು ಅಂಶಗಳನ್ನು ಪದೇ ಪದೇ ಬಳಸಬೇಕಾದರೆ ಅಥವಾ ಇಂಡೆಕ್ಸ್ ಮೂಲಕ ಪ್ರವೇಶಿಸಬೇಕಾದರೆ ಸೂಕ್ತ. | ದೊಡ್ಡ ಡೇಟಾಸೆಟ್ಗಳು, ಸ್ಟ್ರೀಮಿಂಗ್ ಡೇಟಾ, ಅಥವಾ ಅನಂತ ಸರಣಿಗಳಿಗೆ (infinite sequences) ಸೂಕ್ತ. |
ಉದಾಹರಣೆ: ಮೆಮೊರಿ ಬಳಕೆಯಲ್ಲಿನ ವ್ಯತ್ಯಾಸ
ದೊಡ್ಡ ಸಂಖ್ಯೆಯ ಸರಣಿಯನ್ನು ರಚಿಸುವಾಗ ಮೆಮೊರಿ ವ್ಯತ್ಯಾಸವನ್ನು ಗಮನಿಸೋಣ.
import sys
# ಲಿಸ್ಟ್ ಬಳಸಿ
my_list = [i for i in range(100000)]
print(f"ಲಿಸ್ಟ್ನ ಮೆಮೊರಿ ಗಾತ್ರ: {sys.getsizeof(my_list)} ಬೈಟ್ಗಳು")
# ಜನರೇಟರ್ ಬಳಸಿ
my_generator = (i for i in range(100000)) # ಇದು ಜನರೇಟರ್ ಎಕ್ಸ್ಪ್ರೆಶನ್
print(f"ಜನರೇಟರ್ನ ಮೆಮೊರಿ ಗಾತ್ರ: {sys.getsizeof(my_generator)} ಬೈಟ್ಗಳು")
ನೈಜ-ಪ್ರಪಂಚದ ಬಳಕೆ: ದೊಡ್ಡ ಫೈಲ್ ಓದುವುದು
ಒಂದು ದೊಡ್ಡ ಲಾಗ್ ಫೈಲ್ (ಹಲವಾರು GB ಗಾತ್ರ) ಅನ್ನು ಓದುವಾಗ, ಸಂಪೂರ್ಣ ಫೈಲ್ ಅನ್ನು ಮೆಮೊರಿಗೆ ಲೋಡ್ ಮಾಡುವುದು ಅಸಾಧ್ಯ. ಇಲ್ಲಿ ಜನರೇಟರ್ ಅತ್ಯಂತ ಉಪಯುಕ್ತ.
def read_large_file(file_path):
"""ದೊಡ್ಡ ಫೈಲ್ ಅನ್ನು ಲೈನ್-ಬೈ-ಲೈನ್ ಓದಲು ಜನರೇಟರ್."""
with open(file_path, 'r') as file:
for line in file:
yield line
# logfile.txt ಎಂಬ ದೊಡ್ಡ ಫೈಲ್ ಇದೆ ಎಂದು ಭಾವಿಸೋಣ
# for line in read_large_file('logfile.txt'):
# if 'ERROR' in line:
# print(line, end='')
read_large_file ಜನರೇಟರ್ ಪ್ರತಿ ಬಾರಿ ಒಂದು ಲೈನ್ ಅನ್ನು ಮಾತ್ರ ಮೆಮೊರಿಯಲ್ಲಿ ಇಡುತ್ತದೆ, ಇದರಿಂದಾಗಿ ದೊಡ್ಡ ಫೈಲ್ಗಳನ್ನು ಸುಲಭವಾಗಿ ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಬಹುದು.
ಸಾರಾಂಶದಲ್ಲಿ, ನೀವು ಮೆಮೊರಿ-ಸಮರ್ಥ ಮತ್ತು ವೇಗದ ಇಟರೇಷನ್ ಬಯಸಿದಾಗ, ವಿಶೇಷವಾಗಿ ದೊಡ್ಡ ಡೇಟಾಸೆಟ್ಗಳೊಂದಿಗೆ ಕೆಲಸ ಮಾಡುವಾಗ, ಲಿಸ್ಟ್ಗಳ ಬದಲು ಜನರೇಟರ್ಗಳನ್ನು ಬಳಸುವುದು ಉತ್ತಮ ಅಭ್ಯಾಸವಾಗಿದೆ.