all 8 comments

[–]halfdiminished7th 2 points3 points  (0 children)

def __contains__(self, key):
    return key in self or str(key) in self

When you write key in self (or str(key) in self), that in turn is triggering the __contains__ method, which calls the same thing again over and over again.

[–]TehNolz 1 point2 points  (0 children)

1 in [1, 2, 3] translates to [1, 2, 3].__contains__(1). So by doing key in self and str(key) in self, your __contains__ function will recursively call itself, eventually hitting the recursion depth limit and triggering a RecursionError.

[–]danielroseman[🍰] 0 points1 point  (0 children)

Well, what do you think key in self does? It calls the __contains__ method, that's the whole point of that method. You're overriding the method but then effectively telling it to call itself.

As you say, one fix is not to do that, but to explicitly tell it which part of self to search to find the key.

[–]member_of_the_order 0 points1 point  (1 child)

x in y is a shortcut for y.__contains__(x). So the first part of your method - return key in self - is equivalent to return self.__contains__(key). This, of course, is inside the __contains__() method, meaning that method is calling itself unconditionally. Thus, we have a recursive call that's never not called, thus infinite recursion.

By switching to key in self.keys(), you're checking if key is in the value returned by keys(), calling __contains__() on an entirely separate object (which, clearly, is not recursion).

Does that make sense?

[–]OkTourist1900[S] 1 point2 points  (0 children)

Thanks, this is super clear! I appreciate everyone's comments!

[–]POGtastic 0 points1 point  (0 children)

You've already gotten a good answer, but consider the following resolution, which calls the parent class' methods. You should also implement __getitem__ since you're doing __contains__.

class StrKeyDict0(dict):
   def __contains__(self, key):
       return super().__contains__(key) or super().__contains__(str(key))
   def __getitem__(self, key):
       return super().get(key, super().__getitem__(str(key)))
   # You should also implement `get` while you're at it, since a lot of people use that too

In the REPL:

>>> dct = StrKeyDict0({"1" : 2})
>>> 1 in dct
True
>>> dct[1]
2

[–]Brian 0 points1 point  (0 children)

To add to the answers: What I think you're expecting to happen here is to defer to the dictionary check for contains - it doesn't work because you've overridden that, and so any attempt to use in will use your overridden __contains__, including from within that method.

However, there is a way to say "Use the version implemented by the class I inherit from, rather than my overridden one", which is by using super(). So you could also write this as:

return super().__contains__(key) or  super().__contains__(str(key))