Parameters that can be either a thing or an iterable or things are a code smell. It’s even worse when the thing is a string, because a string is an iterable, and even a sequence (so your test for isinstance(names, Iterable) would do the wrong thing).
The Python stdlib does have a few such cases—most infamously, str.__mod__—but most of those err in the other direction, explicitly requiring a tuple rather than any iterable, and most of them are considered to be mistakes, or at least things that wouldn’t be added to the language today. Sometimes it is still the best answer, but the smell should make you think before doing it.
I don’t know exactly what your use case is, but I suspect this will be a lot nicer:
def spam(*names):
namestr = ','.join(names)
dostuff(namestr)
Now the user can call it like this:
spam('eggs')
spam('eggs', 'cheese', 'beans')
Or, if they happen to have a list, it’s still easy:
spam(*ingredients)
If that’s not appropriate, another option is keywords, maybe even keyword-only params:
def spam(*, name=None, names=None):
if name and names:
raise TypeError('Not both!')
if not names: names = [name]
But if the best design really is a string or a (non-string) iterable of strings, or a string or a tuple of strings, the standard way to do that is type switching. It may look a bit ugly, but it calls attention to the fact that you’re doing exactly what you’re doing, and it does it in the most idiomatic way.
def spam(names):
if isinstance(names, str):
names = [names]
dostuff(names)
name.*namesin the params? Then the user can send you one name, or five names, without needing brackets—or, if he has a list handy, he can just*lstit at you.