typeshed icon indicating copy to clipboard operation
typeshed copied to clipboard

Improve documentation for decisions around certain `list`, `dict`, `Mapping`, and `Sequence` methods

Open ktbarrett opened this issue 3 years ago • 6 comments

list.index and list.count currently only accepts values of the element type and Sequence.index and Sequence.count use Any for the type of the argument. In my opinion all of these functions could safely take object.

dict.get and Mapping.get currently only accepts values of the key type. This could be safely extended to be any Hashable object. Non-Hashable values will raise TypeError, but runtime safely accepts values of any other type. I ran into someone on the gitter who needed this a few weeks ago, but he never opened an issue.

I would be willing to make a PR for this.

ktbarrett avatar Jan 24 '22 03:01 ktbarrett

It's true that these functions will take any argument at runtime and return a result, but I'm not convinced that that's what we should be doing in the stub. list[int].index("str") may work, but it's almost certainly not what the user wants, so I'd like type checkers to be able to tell that this code is suspicious.

JelleZijlstra avatar Jan 24 '22 03:01 JelleZijlstra

These kinds of methods are typeshed top hits. E.g., just last week: https://github.com/python/typeshed/pull/6922 I think it'd be helpful to document these decisions, at least as comments in the stubs.

hauntsaninja avatar Jan 24 '22 03:01 hauntsaninja

This diverges what is valid according to typing and what is valid at runtime in core libraries, which makes annotating existing code bases that might utilize this behavior correctly to be considered incorrect. I didn't know it was in typing's scope to decide that certain core Python behavior isn't valid, are you sure that isn't overreaching?

ktbarrett avatar Jan 24 '22 04:01 ktbarrett

typeshed's purpose is to help developers to find mistakes in their code. As @JelleZijlstra pointed out, these methods are typed to find likely problems, not necessarily to accept everything that doesn't crash at runtime.

srittau avatar Jan 24 '22 08:01 srittau

Ideally we would have a way to say "this method accepts any type that overlaps with this type", so you could do list_of_strings.index(string_or_none) or list_of_strings_or_nones.index(string), but not list_of_ints.index(string).

Akuli avatar Jan 24 '22 09:01 Akuli

All of the examples I could come up with (see one below) would be serviced by a Super, denoting that any supertype is accepted, not subtype. Something that checks for Union overlapping would be even less strict, but would have very limited use cases otherwise. Something like Super could be used in many cases.

This is just an stupid example of a pattern I have used and seen others use before. You handle some cases using a dict and .get() and if .get() returns the sentinel you continue on and do something else. In these cases the type of the argument is usually a supertype/superset of the type of the dict.

def stringify(value: object) -> str:
    # handle a few of the values first
    a = ({None: "None", True: "True", False: "False", ...: "..."}).get(value)
    if a is not None:
        return a
    # rest are handled by arbitrary logic
    ...

ktbarrett avatar Jan 24 '22 14:01 ktbarrett