I have a function where sometimes a parameter can be none, and I would like to compare that with another object. However, if I am trying to call an object property, my script will throw an exception on None, even if both objects are None (see example below).
def do_animals_make_same_sound(first_animal, second_animal):
if first_animal.sound = second_animal.sound:
print('Yes they do!')
But if both animals are None, it throws an exception when instead I want it to print('Yes they do!'), but it seems I have to write a really ugly If statement:
def do_animals_make_same_sound(first_animal, second_animal):
if (first_animal is None and second_animal is None) or (first_animal is not None and first_animal.sound == second_animal.sound):
print('Yes they do!')
Is there a better way to do this?
following code is clearer IMHO:
def do_animals_make_same_sound(first_animal, second_animal):
# early return if one of the two animals is missing, ensure both exist
if not (first_animal and second_animal):
return
if first_animal.sound == second_animal.sound:
print('Yes they do!')
ref: Avoid Else, Return Early
It's not great, but one approach can be to use getattr with a default, so None (and anything else without the desired attribute) behaves as if it had the default as the value of its attribute. For example:
if first_animal.sound == second_animal.sound:
can become:
if getattr(first_animal, 'sound', None) == getattr(second_animal, 'sound', None):
I don't actually recommend this, as it silently ignores errors. In real code, I'd almost always let the AttributeError propagate; there is no reasonable scenario in which I'd consider None an acceptable stand-in for "something" where "something" has specific behaviors or attributes; if the caller is passing None, that's almost certainly an error that should not be silently ignored.
I think first you have to understand what's the meaning if one of the objects is None. There are basically three scenarios:
One or both objects are None
One or both objects does not have sound attribute
Both have sound attribute
For #1, I'm assuming it should throw an error as there is really no comparison. What your code does is print "Yes they do" if both objects are None.
For #2, you can use what ShadowRanger suggests, If both objects have None as sound property, and your think it is a normal behavior, then use ShadowRanger's solution.
For #3, just do your normal comparison
def do_animals_make_same_sound(first_animal, second_animal):
if not first_animal or not second_animal:
print("One of the objects is None")
elif getattr(first_animal, 'sound', None) == getattr(second_animal, 'sound', None):
print("Yes, they do!")
If it's a general enough pattern, I'd use a decorator to catch the None case specifically and process it. That keeps the logic out of the function. But you need to define exactly what None means here... it's a little odd that you can pass None for both, but it's not legal to just pass None for one of them. In any case, a decorator is a great way to abstract out some common logic in a clean way...
def NoNone(f):
#functools.wraps(f)
def _no_none_func(*args, **kwargs):
if args[0] == None and args[1] == None:
print('Both are None')
return
return f(*args)
return _no_none_func
#NoNone
def do_animals_make_same_sound(first_animal, second_animal):
if first_animal.sound == second_animal.sound:
print('Yes they do!')
else:
print("No they don't!")
Related
This question is specifically regarding coding convention. I know that using if or elif in this case will produce the same results. Just wondering which is the "proper" way to construct this function:
With consecutive if:
def can_take(self, selectedCourse):
if selectedCourse.hasPassed():
return False
if selectedCourse.getPrereqs() != 'none':
for prereq in selectedCourse.getPrereqs():
if not self.courses[prereq].hasPassed():
return False
return True
With elif:
def can_take(self, selectedCourse):
if selectedCourse.hasPassed():
return False
elif selectedCourse.getPrereqs() != 'none':
for prereq in selectedCourse.getPrereqs():
if not self.courses[prereq].hasPassed():
return False
return True
If I had to choose between the two, I would probably use two if statements, but that's just a matter of personal preference.
If I had a third choice, I wouldn't have any return statements with Boolean literals. I would write a single return statement that uses and and or.
return (not selected.hasPassed()
and (selected.getPrereqs() == 'none'
or all(x.hasPassed()
for x in selected.getPrereqs()))
This is close to how you would describe this in English: you can take the class if you have not passed it, and if the class either has no prerequisites or if you have passed all the prerequisites.
As John Kugelman points out, if getPrereqs returned an empty list instead of 'none', you could further reduce this to
return (not selected.hasPassed()
or all(x.hasPassed()
for x in selected.getPrereqs())
I love the early return pattern:
Get invalid cases out of the way first, either simply exiting or raising exceptions as appropriate, put a blank line in there, then add the "real" body of the method. I find it easier to read.
Returning early keeps the nesting level down, which is great way to reduce cognitive load. I would take it one step further and flip the second if statement around so it too returns early:
def can_take(self, selectedCourse):
if selectedCourse.hasPassed():
return False
if selectedCourse.getPrereqs() == 'none':
return True
for prereq in selectedCourse.getPrereqs():
if not self.courses[prereq].hasPassed():
return False
return True
That said, some other improvements I would make:
Avoid stringly typed variables. Switch that 'none' to None.
But then, when a method returns a list don't return None when there are no results. Return an empty list. Then the caller can blindly iterate over the list without checking if it's None or empty.
def can_take(self, selectedCourse):
if selectedCourse.hasPassed():
return False
for prereq in selectedCourse.getPrereqs():
if not self.courses[prereq].hasPassed():
return False
return True
If you're comfortable with generator expressions you could even convert the loop into an all(...) call, removing the need for the final return True.
def can_take(self, selectedCourse):
if selectedCourse.hasPassed():
return False
return all(self.courses[prereq].hasPassed()
for prereq in selectedCourse.getPrereqs())
I like this because it's a more direct encoding of the question: "Has the student passed all of the prereqs?"
I think I prefer the first version. Why? When you have an if...elif...elif... thing with returns in each branch, there are two "competing" control structures: the if statement and the returns. Obviously, the returns will "win", so we might as well remove the elif stuff. Either that, or have just one return statement, which returns a value computed by a preceding if...elif...elif...else structure.
We use elif but please understand it depends on your problem statement.
Note: Please do not create a big ladder out of it as then it becomes difficult to maintain and debug the code.
I often have functions that return multiple outputs which are structured like so:
def f(vars):
...
if something_unexpected():
return None, None
...
# normal return
return value1, value2
In this case, there might be a infrequent problem that something_unexpected detects (say, a empty dataframe when the routine expects at least one row of data), and so I want to return a value to the caller that says to ignore the output and skip over it. If this were a single return function then returning None once would seem fine, but when I'm returning multiple values it seems sloppy to return multiple copies of None just so the caller has the right number of arguments to unpack.
What are some better ways of coding up this construct? Is simply having the caller use a try-except block and the function raising an exception the way to go, or is there another example of good practice to use here?
Edit: Of course I could return the pair of outputs into a single variable, but then I'd have to call the function like
results = f(inputs)
if results is None:
continue
varname1, varname2 = results[0], results[1]
rather than the more clean-seeming
varname1, varname2 = f(inputs)
if varname1 is None:
continue
Depends on where you want to handle this behavior, but exceptions are a pretty standard way to do this. Without exceptions, you could still return None, None:
a, b = f(inputs)
if None in (a, b):
print("Got something bad!")
continue
Though, I think it might be better to raise in your function and catch it instead:
def f():
if unexpected:
raise ValueError("Got empty values")
else:
return val1, val2
try:
a, b = f()
except ValueError:
print("bad behavior in f, skipping")
continue
The best practice is to raise an exception:
if something_unexpected():
raise ValueError("Something unexpected happened")
REFERENCES:
Explicit is better than implicit.
Errors should never pass silently.
Unless explicitly silenced.
PEP 20 -- The Zen of Python
I'm using Spyder to create a web scraper, and things are moving smoothly so far. As a rookie, Spyder's Code Analysis function is something I find useful for improving the standard of my code. However, while I usually understand its instructions/recommendations, I've recently run into a bit of a blip. I'll post some sample code first:
def payments(): #### This is line 59 on the editor. Preceding it is another function with a a format similar to this one.
"""Obtains data on the weekly payments in the country"""
html = get(source["Payments"]).text
html = bs(html,"lxml")
location = "/home/oduduwa/Desktop/Python Projects/Financial Analyser/CBN Data/Payments.csv"
def file_check():
headings = [i.text for i in html.find_all(width="284")][:10]
headings.insert(0, "Date")
if isfile(location) is False:
with open(location,"w") as file_obj:
writer(file_obj).writerow(headings)
return
file_check()
file = open(location,"r").read()
dates = [i.text.strip()[8:] for i in html.find_all("th",colspan="2")]
values = [i.text.strip()[4:] for i in html.find_all(width="149") if i.text.strip()[4:]!=""]
values = array(values).reshape(int(len(values)/10),10)
values = insert(values,0,array(dates).transpose(),axis=1)[::-1]
for i in range(len(values)):
if values[i][0] not in file:
with open(location,"a+",newline=("\n")) as file_obj:
writer(file_obj).writerow(values[i])
return
The code runs fin and does everything it should. What I don't really understand, however, is Spyder's statement that there's a useless return call in the code block. Here's what it says specifically:
But from what I gather, every function call is necessary in this coding block. What could I have missed? Thanks for your time!
Python functions implicitly return None by default. The following function definitions are equivalent.
def foo():
pass
def foo():
return
def foo():
return None
In my opinion, it is good practice to either
have no return statement at all - this indicates that you are not supposed to assign a name to the return value when calling the function, or
explicitly return None, to indicate "no result" for a function that could return a meaningful value, or
use just return to make a function that returns no meaningful value stop execution.
Example for situation 1:
def switch_first_last_in_place(lst):
'switch the first and last elements of a list in-place'
lst[0], lst[-1] = lst[-1], lst[0]
This function implicitly returns None and you are not supposed to issue
result = switch_first_last_in_place([1, 2, 3])
Example for situation 2:
def get_user_info_from_database(username):
'fetches user info, returns None if user does not exist'
if user_exist(username):
return query_db(username)
else:
return None
This function explicitly returns None to indicate a user was not found. Assignments like
result = get_user_info_from_database('Bob')
are expected. The part
else:
return None
is unnecessary but I like being explicit in cases where None is a meaningful return value.
Example for situation 3:
def assert_exists(thing, *containers):
'raise AssertionError if thing cannot be found in any container'
for container in containers:
if thing in container:
return
raise AssertionError
Here, return is merely used to break out of the function.
I don't like the bare return at the end of the functions in your example. It is not used to end execution and those functions cannot return other values. I would remove it.
You've misunderstood. The warning isn't talking about any of the functions you are calling. It's referring to your use of the return keyword.
This function:
def print_hello():
print("Hello")
return
Is equivalent to this function:
def print_hello():
print("Hello")
return None
Which is equivalent to this function:
def print_hello():
print("Hello")
The warning is saying, that your return statements are useless, and are not required.
I know to use for loop and call my function till I get not None as return value, But I am looking for some python built in which can help here.
e.g. - iter(myfunc(), None) It will call myfunc() until it return None
I am looking to code exactly opposite to this e.g. - iter(myfunc(), not None), Call myfunc() until it returns any thing but None
Thanks in advance..
With just three lines:
x = None
while x is None:
x = f()
Do not look for a builtin for everything. In my opinion even the usual two-argument form of iter is not worth using because it's not a well known feature, and that makes it harder for most people to read. Just keep it simple and straightforward. An extra line or two will not hurt.
while True:
x = myfunc()
if x is not None:
break
There is no ready builtin, but it is easy enough to build a generator function:
def iter_while_none(f):
while True:
value = f()
if value is not None:
return
yield value
although the value yielded is not that interesting; it is, after all, None each time.
This answer is a bit of an exercise in the power of Python. I just get frustrated that iters 2-arity form doesn't take a function for its second parameter.
But it does, if you're crazy enough. See, you can redefine equality on an object, like so:
class Something:
def __eq__(self, other):
self.lastother = other
return other is not None
sentinel = Something()
myiter = iter(myfunc, sentinel)
for nope in myiter:
pass
match = sentinel.lastother
There. Enjoy. Python's pretty amazing that you can subvert the definition of equality this way. Have fun storming the castle!
I am always annoyed by this fact:
$ cat foo.py
def foo(flag):
if flag:
return (1,2)
else:
return None
first, second = foo(True)
first, second = foo(False)
$ python foo.py
Traceback (most recent call last):
File "foo.py", line 8, in <module>
first, second = foo(False)
TypeError: 'NoneType' object is not iterable
The fact is that in order to correctly unpack without troubles I have either to catch the TypeError or to have something like
values = foo(False)
if values is not None:
first, second = values
Which is kind of annoying. Is there a trick to improve this situation (e.g. to so set both first and second to None without having foo returning (None, None)) or a suggestion about the best design strategy for cases like the one I present ? *variables maybe ?
Well, you could do...
first,second = foo(True) or (None,None)
first,second = foo(False) or (None,None)
but as far as I know there's no simpler way to expand None to fill in the entirety of a tuple.
I don't see what is wrong with returning (None,None). It is much cleaner than the solutions suggested here which involve far more changes in your code.
It also doesn't make sense that you want None to automagically be split into 2 variables.
I think there is a problem of abstraction.
A function should maintain some level of abstraction, that helps in reducing complexity of the code.
In this case, either the function is not maintaining the right abstraction, either the caller is not respecting it.
The function could have been something like get_point2d(); in this case, the level of the abstraction is on the tuple, and therefore returning None would be a good way to signal some particular case (e.g. non-existing entity). The error in this case would be to expect two items, while actually the only thing you know is that the function returns one object (with information related to a 2d point).
But it could also have been something like get_two_values_from_db(); in this case the abstraction would be broken by returning None, because the function (as the name suggest) should return two values and not one!
Either way, the main goal of using a function - reducing complexity - is, at least partially, lost.
Note that this issue would not appear clearly with the original name; that's also why it is always important to give good names to function and methods.
I don't think there's a trick. You can simplify your calling code to:
values = foo(False)
if values:
first, second = values
or even:
values = foo(False)
first, second = values or (first_default, second_default)
where first_default and second_default are values you'd give to first and second as defaults.
How about this:
$ cat foo.py
def foo(flag):
if flag:
return (1,2)
else:
return (None,)*2
first, second = foo(True)
first, second = foo(False)
Edit: Just to be clear, the only change is to replace return None with return (None,)*2. I am extremely surprised that no one else has thought of this. (Or if they have, I would like to know why they didn't use it.)
You should be careful with the x or y style of solution. They work, but they're a bit broader than your original specification. Essentially, what if foo(True) returns an empty tuple ()? As long as you know that it's OK to treat that as (None, None), you're good with the solutions provided.
If this were a common scenario, I'd probably write a utility function like:
# needs a better name! :)
def to_tup(t):
return t if t is not None else (None, None)
first, second = to_tup(foo(True))
first, second = to_tup(foo(False))
def foo(flag):
return ((1,2) if flag else (None, None))
OK, I would just return (None, None), but as long as we are in whacko-land (heh), here is a way using a subclass of tuple. In the else case, you don't return None, but instead return an empty container, which seems to be in the spirit of things. The container's "iterator" unpacks None values when empty. Demonstrates the iterator protocol anyway...
Tested using v2.5.2:
class Tuple(tuple):
def __iter__(self):
if self:
# If Tuple has contents, return normal tuple iterator...
return super(Tuple, self).__iter__()
else:
# Else return a bogus iterator that returns None twice...
class Nonerizer(object):
def __init__(self):
self.x=0
def __iter__(self):
return self
def next(self):
if self.x < 2:
self.x += 1
return None
else:
raise StopIteration
return Nonerizer()
def foo(flag):
if flag:
return Tuple((1,2))
else:
return Tuple() # It's not None, but it's an empty container.
first, second = foo(True)
print first, second
first, second = foo(False)
print first, second
Output is the desired:
1 2
None None
Over 10 years later, if you want to use default values I don't think there is a better way than the one already provided:
first, second = foo(False) or (first_default, second_default)
However, if you want to skip the case when None is returned, starting from Python 3.8 you can use the walrus operator (ie. assignment expressions) - also note the simplified foo:
def foo(flag):
return (1, 2) if flag else None
if values := Foo(False):
(first, second) = values
You could use an else branch to assign default values that's worse than the previous or option.
Sadly, the walrus operator does not support unparenthesized tuples so it is just a one line gain compared to:
values = foo(False)
if values:
first, second = values
One mechanism you can use to avoid the problem entirely when you have control of the method foo is to change the prototype to allow giving a default. This works if you are wrapping state but can't guarantee that a particular tuple value exists.
# self.r is for example, a redis cache
# This method is like foo -
# it has trouble when you unpack a json serialized tuple
def __getitem__(self, key):
val = self.r.get(key)
if val is None:
return None
return json.loads(val)
# But this method allows the caller to
# specify their own default value whether it be
# (None, None) or an entire object
def get(self, key, default):
val = self.r.get(key)
if val is None:
return default
return json.loads(val)
I found a solution for this problem:
Return None or return an object.
However you don't want to have to write a class just to return an object. For this, you can use a named tuple
Like this:
from collections import namedtuple
def foo(flag):
if flag:
return None
else:
MyResult = namedtuple('MyResult',['a','b','c']
return MyResult._make([1,2,3])
And then:
result = foo(True) # result = True
result = foo(False) # result = MyResult(a=1, b=2, c=3)
And you have access to the results like this:
print result.a # 1
print result.b # 2
print result.c # 3