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-negativeinteger
එකක් 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 එකකින් හම්බෙමු, හැමෝටම ජය වේවා!