I'm trying to understand why I'm getting IDE warnings about how I'm using the Python shelve module. All of the code here functions fine, I'm just getting IDE (PyCharm 2022.2.3 community edition) type warnings that I'd like to clean up.
A super basic test that exemplifies the issue:
import shelve
s = shelve.open("testshelf.db")
s["my_list"]: list = [1, 2, 3, 4]
print(len(s["my_list"]))
Here I get a warning trying to get len(s["my_list"]):
I don't have enough reputation to post images, but the warning highlights s["my_list"] and says:
Expected type 'Sized', got 'object' instead
I tried to get fancier with it based on another answer on here, but it just moved the issue:
import shelve
from typing import Dict
s = shelve.open("testshelf.db")
s["my_list"]: list = [1, 2, 3, 4]
def foo(s_: Dict[str, list]):
print(len(s_["my_list"]))
foo(s)
Now I get a similar warning highlighting s in foo(s):
Expected type 'dict[str, list]', got 'Shelf[object]' instead
Again, both examples run fine and produce expected output, I'd just like to get rid of the IDE warnings if possible. I am still learning about type-hinting and this is my first use of Python shelve. It seems to be a very convenient and easy-to-use solution to a use case I have, but not if I have to suffer IDE warnings or disable them or something.
The issue here is that
shelve.openreturns ashelve.Shelfinstance (specifically ashelve.DbfilenameShelf). That is aMutableMappingsubtype.A
shelve.Shelfthus behaves essentially like a regular olddict, when it comes to type safety. Since the createdShelfcan hold essentially any type of object, the inferred type isshelve.Shelf[Any]. And all values in a mapping are treated the same by a static type checker. See this related post here for more insight.The type checker can't know that a specific value has the
__len__method implemented. It must treat them all asobject. Not all objects have a concept of size. That is where the warning comes from.As for solutions, there are a few options. Here are the most practical ones IMHO.
A) Intermediary variable
The simplest one in my opinion is to use an intermediary variable.
Write like this:
Read like this: (declares the
listtype argument to beint)Or alternatively like this: (list elements will be inferred as
Any)B) Declare items as
SizedIf you know that all items in your shelf will implement the
Sizedprotocol (i.e. have the__len__method), you can cast it as such:Obviously, if you know that all items will be not just "something
Sized", but specifically lists of integers, you could cast it like this as well. Just usecast(shelve.Shelf[list[int]], ...)instead.C) Fine-grained structural subtyping
This may be overkill, but if you know that all items in your shelf will share a certain protocol (/interface) beyond just
__len__, you can explicitly define one in advance and the cast it accordingly.Say for example that all your objects in the shelf will be some generic containers with elements of type
Tand have the__len__and__iter__methods, as well as aremovemethod. You handle it like so:References
typing.casttyping.Protocol