Python Magic Methods: Python objects වලට අලුත් බලයක් SC Guide

Python Magic Methods: Python objects වලට අලුත් බලයක් SC Guide

හායි යාළුවනේ! කොහොමද ඉතින්, සැපට නේද ඉන්නේ? අද අපි Python වල තියෙන සුපිරිම concept එකක් ගැන කතා කරන්නයි යන්නේ – ඒ තමයි "Magic Methods" කියන ඒවා. මේවා "Dunder Methods" කියලත් හඳුන්වනවා, මොකද __ (double underscore) දෙකක් අතර තියෙන නිසා. මේවා මොකටද? අපේ custom objects වලට, ඒ කියන්නේ අපි හදන class වලින් හදන objects වලට, Python වල built-in types (list, dict, str වගේ) වගේ හැසිරෙන්න පුළුවන් බලය දෙන විදිය තමයි මේ.

හිතන්නකෝ, ඔයාලා හදන object එකක් len() function එකෙන් length එක දෙන්න පුළුවන් නම්, නැත්නම් [] bracket එකකින් item එකක් access කරන්න පුළුවන් නම්? පට්ට නේද? මේකෙන් අපේ code එක ගොඩක් කියවන්න ලේසි වෙනවා වගේම, Pythonic විදියට ලියන්නත් පුළුවන්. අද අපි මේ ගැන ගැඹුරින් කතා කරමු.

"Magic Methods" කියන්නේ මොනවාද?

ඉතින්, මේ Magic Methods කියන්නේ මොනවාද? සරලව කිව්වොත්, මේවා Python වල විශේෂ method ටිකක්. මේවාට __init__, __str__, __add__ වගේ නම් තියෙනවා. මේවා Python interpreter එකෙන් automatically call කරන methods, යම්කිසි අවස්ථාවකදී. උදාහරණයක් විදියට, ඔයාලා class එකකින් object එකක් හදනකොට __init__ method එක call වෙනවා. print() කරනකොට __str__ method එක call වෙනවා. + operator එක පාවිච්චි කරනකොට __add__ call වෙනවා.

මේ methods පාවිච්චි කරලා අපිට පුළුවන් අපේ custom objects වලට Python operators, functions, සහ built-in behaviors වලට "respond" කරන්න සලස්වන්න. මේකට තමයි Operator Overloading කියන්නේ. ඒ කියන්නේ, එකම operator එකක් (උදා: +) විවිධ data types වලට විවිධ විදියට වැඩ කරන්න සලස්වන එක. උදාහරණයක් විදියට, numbers දෙකක් එකතු කරනකොට + කියන්නේ addition, ඒත් strings දෙකක් එකතු කරනකොට + කියන්නේ concatenation.

අපි අද විශේෂයෙන්ම බලන්නේ අපේ objects list එකක් වගේ, නැත්නම් sequence එකක් වගේ හැසිරෙන්න සලස්වන Magic Methods ගැනයි. මේවා __len__, __getitem__, __setitem__, __delitem__ කියන ඒවා. අපි බලමු මේවා කොහොමද වැඩ කරන්නේ කියලා.

Object එකක් List එකක් වගේ හැසිරෙන්න සලස්වමු! (__len__, __getitem__, __setitem__, __delitem__)

අපේ object එකක් built-in list එකක් වගේ වැඩ කරනවා නම්, අපිට ඒක len() වලින් length එක ගන්න පුළුවන්, [] වලින් index කරලා item එකක් ගන්න/දාන්න/අයින් කරන්න පුළුවන්. ඒකට තමයි මේ methods පාවිච්චි කරන්නේ.

__len__(self): Length එකක් දෙමු!

ඔයාලා දන්නවා len() function එක list, string, tuple වගේ දේවල් වල length එක ගන්න පාවිච්චි කරනවා කියලා. අපේ custom object එකකටත් මේ capability එක දෙන්න පුළුවන් __len__ method එක implement කරලා. මේ method එකෙන් return කරන්න ඕනේ non-negative integer එකක්, object එකේ "length" එක විදියට.

class MyCollection:
    def __init__(self, items):
        self.items = list(items) # Make sure it's a list for internal consistency

    def __len__(self):
        print("INFO: __len__ method called.")
        return len(self.items)

# Object එකක් හදමු
my_data = MyCollection(['Apple', 'Banana', 'Cherry'])

# len() function එක පාවිච්චි කරමු
print(f"Number of items in my_data: {len(my_data)}")

# හිස් Collection එකක්
empty_data = MyCollection([])
print(f"Number of items in empty_data: {len(empty_data)}")

මේ code එක run කරාම output එක මේ වගේ වෙයි:

INFO: __len__ method called.
Number of items in my_data: 3
INFO: __len__ method called.
Number of items in empty_data: 0

මෙතනදී අපි MyCollection කියන class එක හැදුවා. ඒකේ __len__ method එක, self.items කියන list එකේ length එක return කරනවා. දැන් අපිට පුළුවන් len(my_data) කියලා call කරලා ඒකේ length එක ගන්න. හරිම සරලයි නේද? මේකෙන් අපේ object එක Sized protocol එකට අනුකූල වෙනවා.

__getitem__(self, key): Index කරලා item ගන්න!

මේක තමයි පට්ටම method එකක්. මේකෙන් අපිට පුළුවන් අපේ object එක obj[key] වගේ index කරලා item එකක් access කරන්න. key එක integer එකක් (index එකක්) වෙන්න පුළුවන්, නැත්නම් string එකක් (dictionary key එකක්) වෙන්න පුළුවන්, නැත්නම් slice object එකක් (obj[start:end:step]) වෙන්න පුළුවන්.

මේ __getitem__ method එක implement කරාම, අපේ object එක automatically "iterable" වෙනවා. ඒ කියන්නේ අපිට for loop එකක පාවිච්චි කරන්න පුළුවන්. Python internaly for loop එකක් දුවනකොට, __getitem__ එක 0 ඉඳලා index කරගෙන යනවා, IndexError එකක් එනකම්. ඒක නිසා, __getitem__ implement කරනකොට IndexError හරියට raise වෙනවාද කියලා බලන්න ඕනේ. අපේ base list එක (self.songs) ඒක automatically handle කරන නිසා ඒ ගැන වැඩිය හිතන්න ඕනේ නැහැ.

class Playlist:
    def __init__(self, songs):
        self.songs = list(songs)

    def __len__(self):
        return len(self.songs)

    def __getitem__(self, index):
        print(f"INFO: Accessing song at index/slice: {index}")
        # if isinstance(index, slice):
        #     # Handle slicing. This will return a new Playlist object with the sliced songs.
        #     return Playlist(self.songs[index])
        return self.songs[index] # list's __getitem__ handles both int and slice automatically

my_playlist = Playlist(['Master Blaster', 'Thriller', 'Bohemian Rhapsody', 'Hotel California'])

print(f"First song: {my_playlist[0]}")
print(f"Last song: {my_playlist[-1]}")

# Slicing functionality
sliced_playlist_songs = my_playlist[1:3]
print(f"Songs 2 to 3: {sliced_playlist_songs}") # This will be a list, not a Playlist object in this implementation

Output එක මේ වගේ වෙයි:

INFO: Accessing song at index/slice: 0
First song: Master Blaster
INFO: Accessing song at index/slice: -1
Last song: Hotel California
INFO: Accessing song at index/slice: slice(1, 3, None)
Songs 2 to 3: ['Thriller', 'Bohemian Rhapsody']

මේ Playlist එකේ අපි __getitem__ implement කරලා තියෙන්නේ. දැන් අපිට පුළුවන් my_playlist[0] වගේ කියලා song එකක් ගන්න. ඒ වගේම, list එකක වගේ my_playlist[1:3] කියලා slice කරන්නත් පුළුවන්. අපි self.songs[index] කියලා return කරන නිසා, underlying list එකේ __getitem__ method එකම slice handling එකත් කරනවා.

මේ __getitem__ implement කරාම අපේ object එක automatically "iterable" වෙනවා. ඒ කියන්නේ අපිට for loop එකක පාවිච්චි කරන්න පුළුවන්.

print("\nIterating through playlist:")
for song in my_playlist:
    print(f"Now playing: {song}")

Output එක මේ වගේ වෙයි:

Iterating through playlist:
INFO: Accessing song at index/slice: 0
Now playing: Master Blaster
INFO: Accessing song at index/slice: 1
Now playing: Thriller
INFO: Accessing song at index/slice: 2
Now playing: Bohemian Rhapsody
INFO: Accessing song at index/slice: 3
Now playing: Hotel California
INFO: Accessing song at index/slice: 4

for loop එක ඇතුලේ __getitem__ method එක sequentially call කරනවා, index 0, 1, 2... විදියට. Index එකක් නැති උනාම (list එකේ length එකට වඩා වැඩි වුනාම), IndexError එකක් raise කරනවා (ඒක list එකේ __getitem__ එකෙන් automatically වෙනවා), ඒ වෙලාවට for loop එක නවතිනවා. මේක තමයි Python වල default iterable protocol එක, __iter__ method එක නැතිකොට. ඒත් __iter__ implement කරන එක තමයි recommended practice එක.

__setitem__(self, key, value): Index කරලා item replace කරන්න!

අපි obj[key] = value වගේ කියලා item එකක් modify කරන්න ඕනේ නම්, __setitem__ method එක implement කරන්න ඕනේ. මේකෙන් අපිට පුළුවන් අපේ object එක mutable, ඒ කියන්නේ වෙනස් කරන්න පුළුවන් එකක් කරන්න.

class MutablePlaylist(Playlist): # Inheriting from Playlist for __len__ and __getitem__
    def __setitem__(self, index, new_song_or_songs):
        print(f"INFO: Setting song(s) at index/slice {index} to '{new_song_or_songs}'")
        # The underlying list's __setitem__ handles both single item and slice assignment
        self.songs[index] = new_song_or_songs

mutable_playlist = MutablePlaylist(['Master Blaster', 'Thriller', 'Bohemian Rhapsody'])
print(f"Original playlist: {mutable_playlist.songs}")

# Single item assignment
mutable_playlist[1] = 'Smooth Criminal'
print(f"Modified playlist (after single item change): {mutable_playlist.songs}")

# Slice assignment
mutable_playlist[0:2] = ['New Pop Song', 'Another Hit']
print(f"Playlist after slice assignment: {mutable_playlist.songs}")

# Replacing with a different number of items (if allowed by slice assignment)
mutable_playlist[2:] = ['Classical Piece', 'Jazz Tune', 'Blues Melody']
print(f"Playlist after replacing last elements with more items: {mutable_playlist.songs}")

Output එක මේ වගේ වෙයි:

Original playlist: ['Master Blaster', 'Thriller', 'Bohemian Rhapsody']
INFO: Setting song(s) at index/slice 1 to 'Smooth Criminal'
Modified playlist (after single item change): ['Master Blaster', 'Smooth Criminal', 'Bohemian Rhapsody']
INFO: Setting song(s) at index/slice slice(0, 2, None) to '['New Pop Song', 'Another Hit']'
Playlist after slice assignment: ['New Pop Song', 'Another Hit', 'Bohemian Rhapsody']
INFO: Setting song(s) at index/slice slice(2, None, None) to '['Classical Piece', 'Jazz Tune', 'Blues Melody']'
Playlist after replacing last elements with more items: ['New Pop Song', 'Another Hit', 'Classical Piece', 'Jazz Tune', 'Blues Melody']

දැන් අපිට පුළුවන් mutable_playlist[1] = 'Smooth Criminal' කියලා දෙවෙනි song එක වෙනස් කරන්න. __setitem__ එකට index එකයි, new_song_or_songs එකයි එනවා. අපි ඒකෙන් self.songs list එක update කරනවා. slice assignment එකත් මෙතනදී support කරනවා.

__delitem__(self, key): Index කරලා item remove කරන්න!

del obj[key] කියලා item එකක් delete කරන්න ඕනේ නම්, __delitem__ method එක implement කරන්න වෙනවා. මේකත් __setitem__ වගේම, object එක mutable කරන්න උදව් වෙනවා.

class DeletablePlaylist(MutablePlaylist): # Inheriting from MutablePlaylist
    def __delitem__(self, index):
        print(f"INFO: Deleting song(s) at index/slice: {index}")
        # The underlying list's __delitem__ handles both single item and slice deletion
        del self.songs[index]

deletable_playlist = DeletablePlaylist(['Master Blaster', 'Thriller', 'Bohemian Rhapsody', 'Hotel California'])
print(f"Original playlist: {deletable_playlist.songs}")

# Single item deletion
del deletable_playlist[1]
print(f"Playlist after deleting index 1: {deletable_playlist.songs}")

# Slice deletion
del deletable_playlist[0:2] # Deletes 'Master Blaster' and 'Bohemian Rhapsody'
print(f"Playlist after deleting slice 0:2: {deletable_playlist.songs}")

Output එක මේ වගේ වෙයි:

Original playlist: ['Master Blaster', 'Thriller', 'Bohemian Rhapsody', 'Hotel California']
INFO: Deleting song(s) at index/slice: 1
Playlist after deleting index 1: ['Master Blaster', 'Bohemian Rhapsody', 'Hotel California']
INFO: Deleting song(s) at index/slice: slice(0, 2, None)
Playlist after deleting slice 0:2: ['Hotel California']

del deletable_playlist[1] කියලා දුන්නම, __delitem__ method එක call වෙලා දෙවෙනි song එක අයින් කරනවා. ඒ වගේම, slice එකක් දීලා එකවර items කිහිපයක් අයින් කරන්නත් පුළුවන්.

Iterable Protocols සහ Best Practices

දැන් අපි මේ methods ගැන හොඳ අවබෝධයක් ගත්තා නේද? __len__, __getitem__, __setitem__, __delitem__ මේ ටික හරියට implement කරාම, අපේ custom object එකක් built-in list එකක් වගේම වැඩ කරනවා. මේක තමයි Python වල "Sequence Protocol" කියන්නේ.

Iterable Protocols:

Python වල object එකක් "iterable" වෙන්න නම්, ඒකේ __iter__ method එකක් තියෙන්න ඕනේ (ඒක iterator object එකක් return කරනවා) නැත්නම් __getitem__ method එකක් තියෙන්න ඕනේ. අපි දැක්කා __getitem__ implement කරාම, for loop එකක පාවිච්චි කරන්න පුළුවන් කියලා. Python internaly for loop එකක් දුවනකොට, __getitem__ එක 0 ඉඳලා index කරගෙන යනවා, IndexError එකක් එනකම්. ඒක නිසා, __getitem__ implement කරනකොට IndexError හරියට raise වෙනවාද කියලා බලන්න ඕනේ. අපේ උදාහරණ වලදී, අපි internaly Python list එකක් පාවිච්චි කරන නිසා, ඒකේ default behavior එකෙන් IndexError handle වෙනවා.

__iter__ method එක implement කරන එක තමයි 일반적으로 (generally) recommended practice එක. __iter__ එකකින් return වෙන්නේ iterator object එකක්, ඒකේ __next__ method එකක් තියෙන්න ඕනේ. __next__ එකෙන් item එකක් return කරනවා, සහ items ඉවර වුනාම StopIteration exception එකක් raise කරනවා. හැබැයි, __getitem__ හරියට implement කරානම්, Python automatically for loop වලට ඒක adapt කරගන්නවා.

Troubleshooting සහ පොඩි Tips:

  • IndexError නිවැරදිව handle කිරීම: __getitem__ එක ඇතුලේ index out of bounds වුනාම IndexError raise කරන්න ඕනේ. එහෙම නොකලොත් for loops නිවැරදිව නවතින්නේ නැහැ, නැත්නම් වෙනත් unexpected behaviors වෙන්න පුළුවන්. අපි list එකක් internaly පාවිච්චි කරන නිසා මේක ගැන හිතන්න ඕනේ නැහැ, ඒත් ඔයාගේ custom data structure එකක් නම්, මේක වැදගත්.
  • Return Types: __len__ එකෙන් හැමවිටම non-negative integer එකක් return කරන්න ඕනේ. වෙනත් data type එකක් return කලොත් TypeError එකක් එන්න පුළුවන්.
  • Consistency: __len__, __getitem__, __setitem__, __delitem__ මේ හැම method එකක්ම එකිනෙකට consistent වෙන්න ඕනේ. උදාහරණයක් විදියට, __len__ එකෙන් 5ක් කියනවා නම්, __getitem__ එකෙන් 0-4 indexes handle කරන්න ඕනේ. නැත්නම් code එක confusing වෙන්න පුළුවන්.
  • Mutability: ඔයාගේ object එක mutable (වෙනස් කරන්න පුළුවන්) වෙන්න ඕනේ නම් විතරක් __setitem__ සහ __delitem__ implement කරන්න. Immutable (වෙනස් කරන්න බැරි) object එකක් නම් මේවා අවශ්‍ය නැහැ.

Best Practices:

  • Logical Sense: මේ Magic Methods implement කරන්න ඕනේ ඔයාගේ object එකට ඒ behavior එක logically ගැලපෙනවා නම් විතරයි. ඔයාගේ object එක ඇත්තටම sequence එකක්, collection එකක් වගේ දෙයක් නම්, මේ methods පාවිච්චි කරන එක හොඳයි. නිකං ඕනෙවට වඩා complex කරන්න ඕනේ නැහැ, නැත්නම් code එක තේරුම් ගන්න අමාරු වෙන්න පුළුවන්.
  • Readability: මේවා පාවිච්චි කරන එකෙන් code එකේ readability එක වැඩි වෙන්න ඕනේ. my_playlist[0] කියන එක my_playlist.get_song_at_index(0) කියන එකට වඩා කියවන්න ලේසියි. ඒ වගේම, Python developer කෙනෙක්ට [] syntax එක දැක්ක ගමන් ඒක list එකක් වගේ හැසිරෙනවා කියලා තේරෙනවා.
  • Standard Library Look and Feel: ඔයාගේ object එකට built-in types වල look and feel එක දෙන්න මේවා උදව් වෙනවා. ඒක developers ලට පහසුයි, මොකද ඒගොල්ලෝ දැනටමත් දන්න patterns පාවිච්චි කරන්න පුළුවන් නිසා.
  • Don't Overdo It: හැම operator එකකටම Magic Method එකක් implement කරන්න ඕනේ නැහැ. අවශ්‍යම ඒවා විතරක් implement කරන්න. Over-engineering වලින් avoid වෙන්න.

නිගමනය (Conclusion)

ඉතින් යාළුවනේ, අද අපි Python වල තියෙන සුපිරි Magic Methods ගැන කතා කළා. විශේෂයෙන්ම __len__, __getitem__, __setitem__, __delitem__ පාවිච්චි කරලා කොහොමද අපේ custom objects වලට list එකක් වගේ හැසිරෙන්න සලස්වන්නේ කියලා ඉගෙන ගත්තා. මේවා හරියට පාවිච්චි කරන එකෙන් ඔයාගේ Python code එක ගොඩක් elegant, කියවන්න ලේසි, සහ Pythonic විදියට ලියන්න පුළුවන්.

මේ concept එක ඔයාලට වැදගත් වෙන්නේ ඔයාලා data structures හදනකොට, data processing libraries ලියනකොට වගේ තැන් වලදී. හිතන්න, ඔයාලා database එකකින් data load කරන object එකක් හදනවා කියලා. ඒ object එක len() කරන්න පුළුවන් නම්, [] වලින් record එකක් access කරන්න පුළුවන් නම්, වැඩේ කොච්චර ලේසිද? මේවා නිවැරදිව පාවිච්චි කරන එකෙන් ඔයාගේ code base එකේ maintainability එකත් වැඩි වෙනවා.

මතක තියාගන්න, practice එක තමයි වැදගත්ම දේ. මේ concepts තනියම try කරලා බලන්න. ඔයාලගේම custom class එකක් හදලා මේ methods implement කරලා බලන්න. මොනවා හරි ප්‍රශ්න තියෙනවා නම්, comment section එකේ අහන්න. මේ වගේ තවත් Python tips and tricks ගැන දැනගන්න අපිත් එක්ක එකතු වෙලා ඉන්න!

එහෙනම්, තවත් අලුත් topic එකකින් හම්බෙමු, හැමෝටම ජය වේවා!