question of style
Simon Forman
sajmikins at gmail.com
Thu Jul 2 19:04:31 EDT 2009
On Jul 2, 4:08 pm, Erik Max Francis <m... at alcyone.com> wrote:
> Simon Forman wrote:
> > Hey I was hoping to get your opinions on a sort of minor stylistic
> > point.
> > These two snippets of code are functionally identical. Which would you
> > use and why?
> > The first one is easier [for me anyway] to read and understand, but
> > slightly less efficient, while the second is [marginally] harder to
> > follow but more efficient.
>
> > ## First snippet
>
> > if self.higher is self.lower is None: return
> > if self.lower is None: return self.higher
> > if self.higher is None: return self.lower
>
> > ## Second snippet
>
> > if self.higher is None:
> > if self.lower is None:
> > return
> > return self.lower
> > if self.lower is None:
> > return self.higher
>
> > What do you think?
>
> Explicit is always better, `return None` when that is your intent.
> `return` shouts to the reader, "I want to get out of this function now,
> and no one cares about the return result."
>
> Personally, I only use the one-liner if/else clauses if it's an
> extremely trivial condition, and even then, usually not there. (The
> only place I use it consistently is `if __name__ == '__main__':
> main()`.) If it's part of something more important that needs
> inspection -- as this obviously does given the questions you've had
> about it, doesn't skip space. Newlines are free.
>
> Even when expanding about the first test, there's no reason to do
> chained `if`s. One `if` with an `and` works just fine.
>
> Paul Rubin's looked to be the best overall replacement, as the logic
> here looks strangely turned inside out. You're obviously looking for
> which one _isn't_ `None`, so write the tests that way. It's much easier
> for everyone else (including your potential future self) to follow.
>
Thanks for all the great feedback!
Some notes:
This code is part of a delete() method for a binary tree
implementation. "None" is used to simulate a NULL pointer. In the case
where both children are non-None the code goes on to handle that.
None is a "unitary" or "singleton" value, so "is" is the right
comparison operator to use with it, at least in this "imitation
pointer" usage.
The bare return was used both to preclude executing the rest of the
function and to return None. (I sometimes use "return # None" with
None in a comment to note that the return value is used. Also, I will
put "# return" or "# return None" at the end of a function if the
returning-of-None is important to the implementation of whatever.
I've never used dis.dis() to check the bytecodes, but I wouldn't be
surprised to find that the compiler generated the same bytecode
whether you explicitly state the return or comment it out.)
In any event, as Duncan Booth pointed out, the entire line was
redundant so the issue is somewhat moot in this particular case.
As for one-line control flow statements in python, I usually refrain
but this code wasn't originally meant for public viewing.
Last but not least, the logic may seem backwards but it's actually
correct. If you check for non-None (NULL) -ness and return the thing
you checked, rather than checking for None-ness and returning the
other, the case where both are non-None is not handled correctly.
Thanks again,
~Simon
FWIW, here's the BinaryTree class, the code is in the _delete_me()
method.
from random import random
class BinaryTree:
'''
A Binary Tree implementation. Provides:
get(key) - Return item for key or None.
insert(key, value) - Insert value under key, replacing old
ones.
delete(key) - Delete value under key if any.
keys() - Iterate through the keys in sort order.
values() - Iterate through the values in sort order.
items() - Iterate through (key, value) pairs in sort order.
'''
def __init__(self, key, value):
self.key = key
self.value = value
self.higher = self.lower = None
def get(self, key):
'''
Return item for key or None.
'''
if key == self.key:
return self.value
if key < self.key and self.lower is not None:
return self.lower.get(key)
if self.higher is not None:
assert key > self.key
return self.higher.get(key)
def insert(self, key, value):
'''
Insert value under key, replacing old ones.
'''
if key == self.key:
self.value = value
elif key < self.key:
if self.lower is None:
self.lower = BinaryTree(key, value)
else:
self.lower.insert(key, value)
else:
assert key > self.key
if self.higher is None:
self.higher = BinaryTree(key, value)
else:
self.higher.insert(key, value)
def delete(self, key):
'''
Delete value under key if any.
'''
if key < self.key and self.lower is not None:
self.lower = self.lower.delete(key)
elif key > self.key and self.higher is not None:
self.higher = self.higher.delete(key)
elif key == self.key:
return self._delete_me()
return self
def _delete_me(self):
if self.lower is None: return self.higher
if self.higher is None: return self.lower
# Two children...
try:
side = self._which_side
except AttributeError:
side = bool(int(random() * 2))
# Alternate sides, should help keep tree balanced.
self._which_side = not side
method = self._delete_lower if side else self._delete_higher
return method()
def _delete_lower(self):
next = self.lower
while next.higher is not None: next = next.higher
self.key = next.key
self.value = next.value
self.lower = self.lower.delete(self.key)
return self
def _delete_higher(self):
next = self.higher
while next.lower is not None: next = next.lower
self.key = next.key
self.value = next.value
self.higher = self.higher.delete(self.key)
return self
def keys(self):
'''
Iterate through the keys in sort order.
'''
for key, value in self.items():
yield key
def values(self):
'''
Iterate through the values in sort order.
'''
for key, value in self.items():
yield value
def items(self):
'''
Iterate through (key, value) pairs in sort order.
'''
if self.lower is not None:
for kv in self.lower.items():
yield kv
yield self.key, self.value
if self.higher is not None:
for kv in self.higher.items():
yield kv
More information about the Python-list
mailing list