Should I prefer 'asking for permission' in this case? - python

Into my function I'm expecting an argument that may either be a tuple or a str. Being new to Python, I have learned that 'begging forgiveness' is better than 'asking for permission'. So instead of checking the type of the argument, I'm doing this:
def f(a):
try: # to handle as tuple
e1, e2, e3 = a
...
except ValueError: # unpacking failed, so must be a string.
pass
# handle as string
However, this won't always work. What if a is a str of length 3? The code would treat it as a tuple.
What should I do in this case? Resort to type checking? Is it less 'Pythonic'? Please explain the most 'Pythonic' solution and why it is so.

Python has the isinstance() function for a reason. Sometimes checking the type of an argument is the right thing to do; it would be perfectly acceptable here.

It’s perfectly fine to make type checks in Python, especially when you want to do this for the purpose of function overloading.
But I would suggest you to make a type check on the string, instead of the tuple. While it may seem like you would always pass a tuple, there are a lot of cases where passing a different sequence may make a lot of sense too, for example a list is perfectly fine too.
So you should leave the logic the way it is, but first check for a string input:
def f(a):
if isinstance(a, str): # in Python 2, check against `basestring`
# handle as string
# …
else:
# handle as tuple
e1, e2, e3 = a
# …
That way you get a proper exception bubbled up in case when the unpacking does not succeed properly, for example if someone passes a 2-element tuple. In your original code, you would have to handle this yourself in some way (to avoid running the code that thinks it’s a string).

Explicit is better than implicit: define two functions, one for tuples, the other for strings. The second best choice would be, to have one function with three arguments, two of them are optional:
def f(e1, e2=None, e3=None):
if e2 is None and e3 is None:
# a string
else:
# three strings
The least best possibility is to check the type of the argument:
def f(a):
if isinstance(a, basestring):
# a string
else:
# perhaps a tuple

Related

Python, allowing passing single string and list of string to a function

I have a function that makes a call to a REST API. One of the parameter is a comma-separated list of names. To generate it, I do this:
','.join(names)
However, I also want to allow the user to provide a single name. The problem is that if names = ERII, for instance, it results in ['E', 'R', 'I', 'I'], but I need it to be ['ERII'] instead.
Now, I could force the user to enter a list with only one value (names=['ERRI'] or names=('ERII',). I would prefer to allow the user to provide a single String. Is there a clever way to do that without a if else statement checking if the provided value is an Iterable?
Also, I am uncertain as what would be the best practice here, force to provide a list, or allow a single string?
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)
Using isinstance() to identify the type of input may provide a solution:
def generate_names(input_term):
output_list = []
if isinstance(input_term,str):
output_list.append(','.join(input_term.split()))
elif isinstance(input_term,list):
output_list.append(','.join(input_term))
else:
print('Please provide a string or a list.')
return(output_list)
This will allow you to input list, string of a single name, as well as string containing several names(separated by space):
name = 'Eric'
name1 = 'Alice John'
namelist = ['Alice', 'Elsa', 'George']
Apply this function:
print(generate_names(name))
print(generate_names(name1))
print(generate_names(namelist))
Get:
['Eric']
['Alice,John']
['Alice,Elsa,George']
I'd allow both list and single strings as arguments. Also, I don't think there's anything wrong with the if/else solution, except that you could directly check if the argument is an instance of the str class (rather then checking if the argument is iterable):
def foo(arg1):
if isinstance(arg1, str):
print("It's a string!")
else:
print("It's not a string!")
Be careful if checking whether the argument is iterable - both strings and lists are iterable.

Convenient way to make new tuples/strings extended by 1

In part of a project, given a single integer/character x and a tuple/string a, I need to create a new tuple/string that's the original tuple/string extended by that element. My current code looks like:
def extend(x, a):
if type(a) is tuple:
return a + (x,)
if type(a) is str:
return a + x
Is there a better way to code this, to either make it shorter or more generalizable to data types?
It is not clear why you want to extend tuples and string at the same part of code. May be you need refactoring. list seems to be the correct type for such operations and it has already .append(x) for it.
If you are sure you need different types, you function seems OK. But just add
raise TypeError()
at the end of it. So you'll be sure not to miss unpredicted data type.
How about this way:
def extend(x, a):
return a + {tuple: (x,), str: x, list: [x]}[type(a)]
Of course, keep in mind that the number of datatypes is really big and a one-size-fits-all approach does not exist.
So, take another hard look on whatever code comes before this and if you really need it, use this dictionary approach.
EDIT
If you need to do this many many times, do it with an if block. As #chepner says, building a dictionary every time will render this approach too smart for its own good.
If subclassing is also an issue, you should change from type to isinstance as #Jean-FrancoisFabre says.
Ev Kounis use of a dictionary is original but if the aim was to gain speed, it doesn't really work, because the dictionary is rebuilt each time (x varies)
A slight modification would be to use a dictionary of conversion functions. This one is fixed.
Pass the parameter to the proper conversion function and it's done:
# this is built once
d = {tuple: lambda x:(x,), str: lambda x:x, list: lambda x:[x]}
def extend(x, a):
return a + d[type(a)](x)
(which still doesn't work is passing a subclassed type of str, tuple, whatever, but if you're sure you won't, this works)
Honestly, using a dictionary for 3 keys won't be that fast, and a chain of ifs is just as efficient.

Python specific data type check

What can be the best to check if var is having str, unicode or None; but not anything else?
I tried with
if not isinstance(ads['number'], (str, unicode, None)):
....
but got below exception:
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
`
You have the right idea, but None is not a class. It's type is NoneType, or alternatively type(None). The latter works out of the box in both Python 2 and 3. The former requires an import: from types import NoneType and only works in Python 2.
you can use
if not isinstance(a, (str, unicode, type(None))):
....
and in python 2 (which you seem to be using) this also works:
from types import NoneType
if not isinstance(a, (str, unicode, NoneType)):
....
You can use type to find the method and check that in a list
Ex:
if type(None) in [str, unicode, type(None)]:
print "ok"
In complement to the other answers (which are correct), I would add a comment on the typology of cases where one would need such a test.
Distinguishing between a string and a scalar type
If the question is distinguishing between a string and a number, then asking for forgiveness instead of permission works just as well:
a = 1
try:
b = a.upper()
except AttributeError:
...
Since that's the mindset of Python, it often makes things simpler, and it also takes care of the case where a is None. If one is trying to prevent such an error, then it might be easier to let it instead happen and then catch it. It could also be more reliable, because it could also take care of cases one had not thought of.
Distinguishing between a string and other iterables
One case where it won't work, however, is when one wants to distinguish between a string and a list (several libraries do that with function arguments). The problem is that both a string and a list are iterables, so the string might happily run through the code for the list without any error... except that now you have your string cut into pieces of one character.
>>> for el in a: print(el.upper())
...
H
E
L
L
O
(And if the code fails, the error could be confusing.) To avoid that effect:
a = "hello"
if isinstance(a, str):
...
else:
...
In my experience, it's the only case where it's really advisable to use a test with isinstance(x, (str,...)). I am curious to know whether someone knows others?

Parameter names in Python functions that take single object or iterable

I have some functions in my code that accept either an object or an iterable of objects as input. I was taught to use meaningful names for everything, but I am not sure how to comply here. What should I call a parameter that can a sinlge object or an iterable of objects? I have come up with two ideas, but I don't like either of them:
FooOrManyFoos - This expresses what goes on, but I could imagine that someone not used to it could have trouble understanding what it means right away
param - Some generic name. This makes clear that it can be several things, but does explain nothing about what the parameter is used for.
Normally I call iterables of objects just the plural of what I would call a single object. I know this might seem a little bit compulsive, but Python is supposed to be (among others) about readability.
I have some functions in my code that accept either an object or an iterable of objects as input.
This is a very exceptional and often very bad thing to do. It's trivially avoidable.
i.e., pass [foo] instead of foo when calling this function.
The only time you can justify doing this is when (1) you have an installed base of software that expects one form (iterable or singleton) and (2) you have to expand it to support the other use case. So. You only do this when expanding an existing function that has an existing code base.
If this is new development, Do Not Do This.
I have come up with two ideas, but I don't like either of them:
[Only two?]
FooOrManyFoos - This expresses what goes on, but I could imagine that someone not used to it could have trouble understanding what it means right away
What? Are you saying you provide NO other documentation, and no other training? No support? No advice? Who is the "someone not used to it"? Talk to them. Don't assume or imagine things about them.
Also, don't use Leading Upper Case Names.
param - Some generic name. This makes clear that it can be several things, but does explain nothing about what the parameter is used for.
Terrible. Never. Do. This.
I looked in the Python library for examples. Most of the functions that do this have simple descriptions.
http://docs.python.org/library/functions.html#isinstance
isinstance(object, classinfo)
They call it "classinfo" and it can be a class or a tuple of classes.
You could do that, too.
You must consider the common use case and the exceptions. Follow the 80/20 rule.
80% of the time, you can replace this with an iterable and not have this problem.
In the remaining 20% of the cases, you have an installed base of software built around an assumption (either iterable or single item) and you need to add the other case. Don't change the name, just change the documentation. If it used to say "foo" it still says "foo" but you make it accept an iterable of "foo's" without making any change to the parameters. If it used to say "foo_list" or "foo_iter", then it still says "foo_list" or "foo_iter" but it will quietly tolerate a singleton without breaking.
80% of the code is the legacy ("foo" or "foo_list")
20% of the code is the new feature ("foo" can be an iterable or "foo_list" can be a single object.)
I guess I'm a little late to the party, but I'm suprised that nobody suggested a decorator.
def withmany(f):
def many(many_foos):
for foo in many_foos:
yield f(foo)
f.many = many
return f
#withmany
def process_foo(foo):
return foo + 1
processed_foo = process_foo(foo)
for processed_foo in process_foo.many(foos):
print processed_foo
I saw a similar pattern in one of Alex Martelli's posts but I don't remember the link off hand.
It sounds like you're agonizing over the ugliness of code like:
def ProcessWidget(widget_thing):
# Infer if we have a singleton instance and make it a
# length 1 list for consistency
if isinstance(widget_thing, WidgetType):
widget_thing = [widget_thing]
for widget in widget_thing:
#...
My suggestion is to avoid overloading your interface to handle two distinct cases. I tend to write code that favors re-use and clear naming of methods over clever dynamic use of parameters:
def ProcessOneWidget(widget):
#...
def ProcessManyWidgets(widgets):
for widget in widgets:
ProcessOneWidget(widget)
Often, I start with this simple pattern, but then have the opportunity to optimize the "Many" case when there are efficiencies to gain that offset the additional code complexity and partial duplication of functionality. If this convention seems overly verbose, one can opt for names like "ProcessWidget" and "ProcessWidgets", though the difference between the two is a single easily missed character.
You can use *args magic (varargs) to make your params always be iterable.
Pass a single item or multiple known items as normal function args like func(arg1, arg2, ...) and pass iterable arguments with an asterisk before, like func(*args)
Example:
# magic *args function
def foo(*args):
print args
# many ways to call it
foo(1)
foo(1, 2, 3)
args1 = (1, 2, 3)
args2 = [1, 2, 3]
args3 = iter((1, 2, 3))
foo(*args1)
foo(*args2)
foo(*args3)
Can you name your parameter in a very high-level way? people who read the code are more interested in knowing what the parameter represents ("clients") than what their type is ("list_of_tuples"); the type can be defined in the function documentation string, which is a good thing since it might change, in the future (the type is sometimes an implementation detail).
I would do 1 thing,
def myFunc(manyFoos):
if not type(manyFoos) in (list,tuple):
manyFoos = [manyFoos]
#do stuff here
so then you don't need to worry anymore about its name.
in a function you should try to achieve to have 1 action, accept the same parameter type and return the same type.
Instead of filling the functions with ifs you could have 2 functions.
Since you don't care exactly what kind of iterable you get, you could try to get an iterator for the parameter using iter(). If iter() raises a TypeError exception, the parameter is not iterable, so you then create a list or tuple of the one item, which is iterable and Bob's your uncle.
def doIt(foos):
try:
iter(foos)
except TypeError:
foos = [foos]
for foo in foos:
pass # do something here
The only problem with this approach is if foo is a string. A string is iterable, so passing in a single string rather than a list of strings will result in iterating over the characters in a string. If this is a concern, you could add an if test for it. At this point it's getting wordy for boilerplate code, so I'd break it out into its own function.
def iterfy(iterable):
if isinstance(iterable, basestring):
iterable = [iterable]
try:
iter(iterable)
except TypeError:
iterable = [iterable]
return iterable
def doIt(foos):
for foo in iterfy(foos):
pass # do something
Unlike some of those answering, I like doing this, since it eliminates one thing the caller could get wrong when using your API. "Be conservative in what you generate but liberal in what you accept."
To answer your original question, i.e. what you should name the parameter, I would still go with "foos" even though you will accept a single item, since your intent is to accept a list. If it's not iterable, that is technically a mistake, albeit one you will correct for the caller since processing just the one item is probably what they want. Also, if the caller thinks they must pass in an iterable even of one item, well, that will of course work fine and requires very little syntax, so why worry about correcting their misapprehension?
I would go with a name explaining that the parameter can be an instance or a list of instances. Say one_or_more_Foo_objects. I find it better than the bland param.
I'm working on a fairly big project now and we're passing maps around and just calling our parameter map. The map contents vary depending on the function that's being called. This probably isn't the best situation, but we reuse a lot of the same code on the maps, so copying and pasting is easier.
I would say instead of naming it what it is, you should name it what it's used for. Also, just be careful that you can't call use in on a not iterable.

Way to Treat Python single vals and lists of vals identically?

I'm running into this problem often: I'm creating a function that needs to perform a series of operations on a value, whether that value be a single value or a list of values.
Is there an elegant way to do this:
def convert_val(val):
do a series of things to each value, whether list or single val
return answer or list of answers
rather than what I've been doing?:
def convert_val(val):
if isinstance(val, list):
... do a series of things to each list item,
return a list of answers
else:
... do the same series, just on a single value
return a single answer
One solution would be to create a sub_convert() that would do the series of actions, and then just call it once or iteratively, depending on the type passed in to convert().
Another would be to create a single convert() that would accept the arguments (value, sub_convert()).
Other suggestions that would be more compact, elegant and preferably all in one function?
(I've done several searches here to see if my issue has already been addressed. My appologies if it has.)
Thanks,
JS
You need to fix your design to make all uses of the function actually correct.
Ralph Waldo Emerson. "A foolish consistency is the hobgoblin of little minds, adored by little statesmen and philosophers and divines."
We're not talking about a foolish consistency. You have what might be a design problem based on inconsistent use of this function.
Option 1. Don't call convert_val( x ) where x is a non-list. Do this. convert_val( [x] ). Don't fix your function, fix all the places that use your function. Consistency helps reduce bugs.
Option 2. Change the design of convert_val to use multiple positional arguments. This doesn't generalize well.
def convert_val( *args ):
whatever it's supposed to do to the arguments.
Then fix all the places you provide a list to be convert_val( *someList ). That's okay, and may be closer to your intent.
Note.
You can find your design errors using the warnings module.
def convert_val( arg ):
if isinstance( arg, collections.Sequence ):
return convert_val_list( arg )
else:
warnings.warn( "Fix this" )
return convert_val_list( [arg] )[0]
def convert_val_list( arg ):
assert isinstance( arg, collections.Sequence )
the original processing
Once you've fixed all the design problems, you can then do this
convert_val = convert_val_list
And delete the original function.
If the function makes sense for a single value, as well as for a list, then logically the function's result for a certain list item will not depend on the other items in the list.
For example, a and b should end up identical:
items = [1, 2]
a = convert_val(items)
b = map(convert_val, items)
This example already hints at the solution: the caller knows whether a list or a single value is passed in. When passing a single value, the function can be used as-is. When passing a list, a map invocation is easily added, and makes it clearer what's happening on the side of the caller.
Hence, the function you describe should not exist in the first place!
I'm late to the party here and I'm not sure if this is what OP wants.
I much prefer to keep the implementation details hidden inside the function. The caller shouldn't care about what happens inside.
def convert_val(val):
values = []
values.extend(val)
for value in values:
# do things to each list item,
return a list of answers
This would make the convert_val put val into the values list (if not a list) or all values of val into the values list.
In addition should predictably get a list back (since you'd be using the same logic).
In the end:
assert convert_val([1]) == convert_val(1)

Categories

Resources