this post was submitted on 01 Feb 2024
23 points (100.0% liked)

Python

6366 readers
3 users here now

Welcome to the Python community on the programming.dev Lemmy instance!

📅 Events

PastNovember 2023

October 2023

July 2023

August 2023

September 2023

🐍 Python project:
💓 Python Community:
✨ Python Ecosystem:
🌌 Fediverse
Communities
Projects
Feeds

founded 1 year ago
MODERATORS
 

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?

you are viewing a single comment's thread
view the rest of the comments
[–] [email protected] 2 points 9 months ago* (last edited 9 months ago) (1 children)

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]

[–] [email protected] 3 points 9 months ago (2 children)

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

[–] [email protected] 1 points 9 months ago* (last edited 9 months ago)

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)
[–] [email protected] 0 points 9 months ago* (last edited 9 months ago) (1 children)

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".

[–] [email protected] 3 points 9 months ago

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.