Reputation: 621
I was implementing a __getitem__
method for a class and found that obj[key]
worked as expected, but key in obj
always transformed key
into 0
:
class Mapper:
def __getitem__(self, key):
print(f'Retrieving {key!r}')
if key == 'a':
return 1
else:
raise KeyError('This only contains a')
>>> mapper['a']
Retrieving 'a'
1
>>> 'a' in mapper
Retrieving 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __getitem__
KeyError: 'This only contains a'
I didn't find a __hasitem__
method, so I thought the in
check worked by just calling __getitem__
and checking if it throws a KeyError
. I couldn't figure out how the key gets transformed into an integer, of all things!
I couldn't find an answer here, so I started writing this question. I figured out the answer before I posted, but in the interest of saving other people some time, I'll post my question and solution.
Upvotes: 1
Views: 341
Reputation: 621
It's been a while, so I totally forgot the method I was looking for is called __contains__
, not __hasitem__
!
What's more, the fallback isn't the same as other, similar dunder methods in Python! Usually I'd expect that if __contains__
is missing, it would just use __getitem__
. Instead, the in
syntax uses a special series of fallbacks:
__contains__
exists, use that.__iter__
exists, use it to iterate over the items in the object and check if any of them match the key.__getitem__
exists, use it to iterate over the items in the object as if it were a sequence (e.g. a list): give __getitem__
every integer, starting at 0
, and either stop when it throws an IndexError
or it returns something matching the key.In my case, I was raising a KeyError
when __getitem__
received 0
, which got passed up to the caller.
Upvotes: 3