Generic Typing with `Iterable[str]` or `Collection[str]`: Should you use it?
from wasabi@feddit.de to python@programming.dev on 01 Feb 2024 15:57
https://feddit.de/post/8488106

I often find myself defining function args with list[SomeClass] type and think “do I really care that it’s a list? No, tuple or Generator is fine, too”. I then tend to use Iterable[SomeClass] or Collection[SomeClass]. But when it comes to str, I really don’t like that solution, because if you have this function:

def foo(bar: Collection[str]) -> None:
    pass

Then calling foo(“hello”) is fine, too, because “hello” is a collection of strings with length 1, which would not be fine if I just used list[str] in the first place. What would you do in a situation like this?

#python

threaded - newest

troyunrau@lemmy.ca on 01 Feb 2024 17:01 next collapse

Kids these days and their type hinting. Back in my day, all objects were ducks, and we liked it!

wasabi@feddit.de on 01 Feb 2024 18:00 collapse

🦆

sugar_in_your_tea@sh.itjust.works on 01 Feb 2024 17:24 next collapse

I’d leave a docstring:

def foo(bor: Iterable[str]) -> None:
    """foos bars by doing x and y to each bar"""

Type hinting isn’t intended to prevent all classes of errors, it’s intended to provide documentation to the caller. Iterable[str] provides that documentation, and a docstring gives additional context if needed. If you want strict typing assurances, Python probably isn’t the tool you’re looking for.

wasabi@feddit.de on 02 Feb 2024 07:17 collapse

This + an assert seems like the way to go. I think that str should never have fulfilled these contracts in the first place and should have a .chars property that returns a list of one-character-strings. But this change would break existing code, so it is not going to happen.

sugar_in_your_tea@sh.itjust.works on 02 Feb 2024 09:16 collapse

IDK, I think strings being simple lists is less surprising than having a unique type. Most other languages model them that way, and it’s nice to be able to use regular list actions to interact with them.

It’s really not something I’m likely to run into in practice. The only practical way I see messing this up is with untrusted inputs, but I sanitize those anyway.

wasabi@feddit.de on 02 Feb 2024 10:13 collapse

Yes, you’re right. It also a lot of benefits.

[deleted] on 01 Feb 2024 17:45 next collapse
.
wasabi@feddit.de on 01 Feb 2024 18:06 collapse

I know that Iterable and Collection aren’t the same. My point is, that if you use Iterable[str] or Collection[str] as a more flexible alternative to list[str] you no longer have any type-hinting support protecting against passing in a plain string and you could end up with a subtle bug by unexpectedly looping over [‘f’, ‘o’, ‘o’] instead of [‘foo’].

sloppy_diffuser@sh.itjust.works on 01 Feb 2024 19:46 collapse

Strings are just a pain… Common solution is a runtime guard or doc comment. Can maybe try an overload and annotate a string param with NoReturn and an @throws doc comment.

Been awhile since doing python, but even that might not work since string satisfies both overloads. I recall having issues with unions where types were not discrete. I may have solved by ordering the overloads differently so string is considered first, but again, its been awhile.

stackoverflow.com/…/python-type-annotation-for-se…

GammaGames@beehaw.org on 01 Feb 2024 18:36 next collapse

I’m rusty on my type hints because I’ve been living in lua land lately, but from ye olde PEP 20

Explicit is better than implicit.

I’d combine them so the hint was something like Union[Collection[str], str]

wasabi@feddit.de on 01 Feb 2024 19:10 collapse

But what if you actually don’t want str to be valid?

GammaGames@beehaw.org on 01 Feb 2024 19:19 next collapse

Oh, I had it backwards! I tried to mess with the hint and couldn’t find anything, maybe an assert?

from typing import Collection

def foo(bar: Collection[str]):
    assert not isinstance(bar, str)
    print(bar)
m_f@midwest.social on 01 Feb 2024 20:49 collapse

If you’re writing code that generic, why wouldn’t you want str to be passed in? For example, Counter(‘hello’) is perfectly valid and useful. OTOH, average_length(‘hello’) would always be 1 and not be useful. OTOOH, maybe there’s a valid reason for someone to do that. If I’ve got a list of items of various types and want to find the highest average length, I’d want to do max(map(average_length, items)) and not have that blow up just because there’s a string in there that I know will have an average length of 1.

So this all depends on the specifics of the function you’re writing at the time. If you’re really sure that someone shouldn’t be passing in a str, I’d probably raise a ValueError or a warning, but only if you’re really sure. For the most part, I’d just use appropriate type hints and embrace the phrase “we’re all consenting adults here”.

wasabi@feddit.de on 02 Feb 2024 10:25 collapse

Maybe something like passing in a list of patterns which should match some data, or a list of files/urls to download would be examples of where I would like to be generic, but taking in a string would be bad.

But the real solution be to convert it to foo(*args: str). But maybe if you take 2 Container[str] as input so you can’t use *args. But no real world example comes to mind.

sloppy_diffuser@sh.itjust.works on 01 Feb 2024 19:35 next collapse

Look at the official docs. There is a table part way down stating which methods are available for each. I pick the one closest to how I use it. So if I’m not mutating I’ll use Sequence over List to inform the caller I’m treating as immutable and to safe guard myself from mutating it in my implementation via static type analysis.

docs.python.org/3/library/collections.abc.html

wasabi@feddit.de on 02 Feb 2024 07:11 collapse

str matches most of these contracts, though, requiring additional checks if a str was passed or one of these collections containing strings.

pythonoob@programming.dev on 03 Feb 2024 18:19 collapse

I’m not sure why you wouldn’t just use packing to pass in a list of some objects that you need iterate over? Isn’t it normally bad form to pass lists as arguments? I feel like I’ve read this somewhere but can’t cite it

wasabi@feddit.de on 03 Feb 2024 18:38 collapse

Yes, that’s a good alternative for Collection[str] but not so much for Iterable[str] as you lose the lazyness of Generators.