question of style

S

Simon Forman

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?

(One minor point: in the first snippet, the "is None" in the first
line is superfluous in the context in which it will be used, the only
time "self.lower is self.higher" will be true is when they are both
None.)
 
S

Simon Forman

Simon Forman said:
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?
(One minor point: in the first snippet, the "is None" in the first
line is superfluous in the context in which it will be used, the only
time "self.lower is self.higher" will be true is when they are both
None.)

I'd write the first one as:

        if self.lower is None:
                return self.higher
        if self.higher is None:
            return self.lower

because the entire first 'if' statement is redundant.

As a matter of style however I wouldn't use the shorthand to run two 'is'
comparisons together, I'd write that out in full if it was actually needed
here.

Likewise in the second one:

    if self.lower is None:
        return
    return self.lower

is obviously the same as:

        return self.lower

so apart from reversing the order of the comparisons once you've dropped
the redundant test it is the same as the first one.

Wow. The equivalence in the second bit is obvious enough-- in
hindsight :] but I totally missed the redundancy in the first bit. I
must be getting old.

Thank you very much.
 
K

Kee Nethery

the fact that you felt compelled to explain the "one minor point" in
the first snippet tells me that the second snippet does not need that
explanation and will be easier for someone (like you for example) to
maintain in the future.
Second snippet would be my choice.
Kee Nethery

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?

(One minor point: in the first snippet, the "is None" in the first
line is superfluous in the context in which it will be used, the only
time "self.lower is self.higher" will be true is when they are both
None.)
 
T

Terry Reedy

Simon said:
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 happen is neither is None?
 
P

Paul Rubin

Simon Forman said:
These two snippets of code are functionally identical. Which would you
use and why?

Both are terrible. I can't tell what you're really trying to do. As
Terry Reedy points out, the case where self.higher and self.lower are
both not None is not handled. If you want to explicitly return None,
use "return None" rather than "return". Generally, having a special
value like None to denote a missing datum was considered standard
practice a few decades ago, but these days in some circles it's
treated as something of a code smell. You may want to look for
some other representation instead.

Most importantly, whatever code you write, put in an explanatory
comment saying what the real goal is.
 
T

Tim Harig

so apart from reversing the order of the comparisons once you've dropped
the redundant test it is the same as the first one.

I try to evaluate what you have given regardless of what Booth pointed out.
So, I will only evaluate the first line as it contains most of what I want
to illistrate.

I *really* don't like the fact that you don't specify a return value here
when you have specified a return value for the other conditions. Obviously
it is possible that something will be looking at the return value for this
function (ie, it would not be a void function() in C). I had to lookup to
make sure what Python would return in such a case. If you want the
condition to return something then you should specify what you are
returning. Specifing 'return None' is much clearer then relying on default
behavior.

I always prefer to see a:

condition:
operation

structure to my code. It is the way it is almost always see in Python and
in other languages. Putting it on one line doesn't save you anything and
it makes it easier to miss something.

I have no particular problem with running the is statements together as an
extended if statement together when it makes it clearer to do so. The fact
that you want all of them to be the same object is clear. I do have some
wroblem in this instance because you are referencing "is" against an
instance of a builtin object.

The fact that you used is instead of == for this particular instance
throws me a little because of the use of None. I wouldn't think second
about it if it was a user defined object. I have to wonder whether all
objects that are assigned the value of None are actually re-referenced
to the origional None object? Could deep copy style operations create
two equivilant but different copys of none? The same problems may be
encounted from any builtin objects such as number literals.

Had None not been specified I would be fine with stringing the comparison
together:

# if all three objects are the same instance, return None
if object1 is object2 is object3:
return None:

Because of this pecularity, I think that it would be clearer to break the
ifs for simularity and equality:

if object1 is object2:
if object1 == None:
return None
else:
return object1
else:
if object1 == None:
return Object2
elif object2 == None::
return Object1
else:
# what do we do if neither object == None?
raise houstonWeHaveAProblemException
 
P

Paul Rubin

Simon Forman said:
## 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?

I'm not sure, but my guess is that what you are really trying to write
is something like this:

# self.higher and self.lower are each either "available" (i.e. are
# not None), or unavailable (are None). Return the highest of the
# available values. If no value is available, return None.

if self.higher is not None:
return self.higher
elif self.lower is not None:
return self.lower
else:
return None
 
P

Paul Rubin

Tim Harig said:
If lower is 5 and higher is 3, then it returns 3 because 3 != None in the
first if.

Sorry, the presumption was that lower <= higher, i.e. the comparison
had already been made and the invariant was enforced by the class
constructor. The comment should have been more explicit, I guess.
 
P

Paul Rubin

Tim Harig said:
That being the case, it might be a good idea either to handle the situation
and raise an exception or add:

assert self.lower <= self.higher

That way an exception will be raised if there is an error somewhere else
in the code rather then silently passing a possibly incorrect value.

Well, that assert is not right because you have to handle the case
where one of the values is None. The general sentiment is reasonable
though, if you're concerned that the precondition may not be valid.
 
S

Simon Forman

Simon said:
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
 
S

Simon Forman

Speaking only to the style issue, when I've wanted to do something like
that, I find:

       if self.higher is None is self.lower:
           ...

more readable, by making clear they are both being compared to a
constant, rather than compared to each other.

I was going to do it that way for aesthetics if nothing else, but in
this particular code "self.higher is self.lower" could only have been
True if they were both None, so the final "is None" was redundant and
I only included it as a "just-in-case" check in case someone someday
used the code in such a way as to assign the same object (not None) to
both self.higher and self.lower... Totally pointless, I'm sorry to
say. I'm glad the whole line was redundant really.
 
P

Paul Rubin

Tim Harig said:
Sorry, it worked under 2.5:

Well, it didn't crash under 2.5. Whether the result was correct
is a different question.
None seems to have been evaluated less then any integer. The same isn't
true under 3.0:

But the original code didn't specify the non-None values to be
integers. Really, it's unwise to rely on an ordering relation between
None and values of arbitrary other types, unless supportable by a
clear specification, but even then, it's a code smell.
3.0 TypeError: unorderable types: NoneType() <= int()

That is far preferable to what 2.x does.
 
P

Paul Rubin

Simon Forman said:
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.

Yes, but the concept of null pointers is considered kludgy these days.
One alternative is to represent an empty node as an empty list [], and
a node with value x as the 1-element list [x]. That incurs some
storage overhead, but all the case by case checking in your comparison
function goes away:

return (self.higher + self.lower)[:1]
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.)

I really wouldn't worry about this. Python is so slow no matter what
you do, that 1 more no-op bytecode won't matter.
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.

The code you posted didn't return anything if both values were non-None.

As for checking for None-ness vs. non-None-ness, sure, the two are
logically equivalent, but I think checking for non-None expresses the
algorithm more clearly.
FWIW, here's the BinaryTree class, ...
A Binary Tree implementation. Provides:
get(key) - Return item for key or None.

I'm sorry but I find that class rather ugly. The code would be a lot
smaller and have fewer corner cases with a different data
representation.
 
S

Simon Forman

Yes, but the concept of null pointers is considered kludgy these days.

Seriously? "kludgy"? (I really AM getting old.)

Well I wouldn't know, I've been fortunate enough to program mostly in
python for over half a decade now and None and 0 are as close as I've
gotten to NULL in a long time.
One alternative is to represent an empty node as an empty list [], and
a node with value x as the 1-element list [x].  That incurs some
storage overhead, but all the case by case checking in your comparison
function goes away:

   return (self.higher + self.lower)[:1]

Heh, program LISP much? :]

That sounds very interesting, but I'm only writing a BTree to then use
it to play with "persistent data structures" and the models I'm using
are based on pointer code. I wouldn't want to try to re-implement
them on top of a different "architecture", at least not at first.
I really wouldn't worry about this.  Python is so slow no matter what
you do, that 1 more no-op bytecode won't matter.

I'm not worried about it at all. In fact, I'm pretty sure the compiler
handles it for me as well as I could want or expect it to.

"A Computer's Perspective on Moore's Law: Humans are getting more
expensive at an exponential rate." - Mark Miller
http://www.caplet.com/adages.html

Python may be slow to execute but it's fast to write. (And write
well.) ;]
The code you posted didn't return anything if both values were non-None.

Yep, it was just a snippet, just the part I wanted feedback on.
As for checking for None-ness vs. non-None-ness, sure, the two are
logically equivalent, but I think checking for non-None expresses the
algorithm more clearly.

In this particular case it's somewhat more elegant (IMO) to check "is
None".
I'm sorry but I find that class rather ugly.  The code would be a lot
smaller and have fewer corner cases with a different data
representation.

Um, thanks? Seriously though, care to put your money where your mouth
is? I'd be interested to see a BTree implemented as you indicated
above.

Pointer code in python is silly in general, since you're only
imitating real pointers with attributes (i.e. instance dicts) anyway.
As I said above, I'm faking it with python to explore persistent data
structures and the models I've found so far are pointer-based. I'm
sure there are better (i.e. faster) ways to get the same (or
sufficiently similar) behavior in a more "pythonic" fashion. (for
example, to maintain a sorted sequence under insertion I'd use a list
and the bisect module, and so on.)

Reminds me, a few years back I implemented Knuth's "Dancing Links"
algorithm in python for a Sudoku solver. It uses a kind of grid of
doubly-linked lists to store information about the search space and to
traverse that space to resolve an answer. The algorithm takes
advantage of the fact that allocated nodes persisted in memory even
when completely unlinked from the "surrounding" lists. You unlinked
and re-linked the nodes/lists in a certain pattern to search and
backtrack the search space. It's more than I can explain here. Really
elegant algorithm.

The point is in python you had to put ALL the nodes into a storage
structure just to prevent them disappearing while unlinked from the
"head" of the grid.

Regards,
~Simon
 
P

Paul Rubin

Simon Forman said:
Seriously? "kludgy"? (I really AM getting old.)

http://math.andrej.com/2009/04/11/on-programming-language-design/

discusses the issue (scroll down to "Undefined values" section).
Well I wouldn't know, I've been fortunate enough to program mostly in
python for over half a decade now and None and 0 are as close as I've
gotten to NULL in a long time.

Right, and how many times have you had to debug

AttributeError: 'NoneType' object has no attribute 'foo'

or the equivalent null pointer exceptions in Java, C, or whatever?
They are very common. And the basic idea is that if you avoid using
null pointers in the first place, you'll get fewer accidental null
pointer exceptions.
That sounds very interesting, but I'm only writing a BTree to then use
it to play with "persistent data structures"

But you have implemented a mutable tree. If you want to write a
persistent one, then write a persistent one! You also use a wishful
heuristic in the hope of keeping the tree balanced--if you want a
balanced tree, why not use one that's guaranteed to stay in balance?
AVL trees are pretty easy to implement; maybe you could try writing a
persistent one:

http://en.wikipedia.org/wiki/AVL_tree
In this particular case it's somewhat more elegant (IMO) to check "is
None".

Well, I can't understand why that might be, but it's surely
subjective, so ok.
Um, thanks? Seriously though, care to put your money where your mouth
is? I'd be interested to see a BTree implemented as you indicated above.

Er, I'm not trying to argue with you. You asked for people's advice
and impressions, so I gave you some. I don't feel like rewriting that
whole class, but I'll do a method or two, using [] to represent an
empty node. (Hmm, actually changing the empty node representation did
less to shorten the code than just getting rid of a bunch of
duplication. Really, I'd tend to look for a slightly different tree
structure which tried to avoid these issues).

Untested:

class BinaryTree:
def __init__(self, key, value):
self.key = key
self.value = value
self.higher = self.lower = []

def get(self, key):
if key == self.key:
return self.value
branch = self.lower if key < self.key else self.higher
return branch.get(key) if branch else []

def insert(self, key, value):
def xinsert(xtree): # xtree is either a tree or []
if not xtree: xtree = BinaryTree(key, value)
else: xtree.insert(key, value)
return xtree

if key == self.key:
self.value = value
elif key < self.key:
self.lower = xinsert(self.lower)
else:
self.higher = xinsert(self.higher)

and so forth.
 
L

Lie Ryan

Tim said:
By comparing them to *any* constant with 'is' you end up with the same
potential problems as to whether something with a value of None is actually
None?

Oh really? None is a Singleton and all None always point to the same
None object. And the fact that it is impossible to override `is`
operator is the reason for the "is None" idiom.

This does not happen with other objects though (except if you make them
as Singletons as well), and in case of immutable, you may get bugs due
to python's optimizing small integers.
With equality operators
everything is either the same or it isn't.

How about:
class Fake(object):
def __eq__(self, other):
return True

we uses `is` when we want to make sure an object is really the same
object as another, not just an equivalent object.

I can start from the first
variable and compare it to the second then to the third then ... In the
case of the inequality, I have to start with the first and compare it to
all of the rest to make sure that none are the same. Then I have to do the
same for each with the remaining. This is much more difficult to do in my
head. I requires much more thought, better left to a computer, when
evaluating the expression for myself. Therefore, equalites are quick and
easy to keep straight whereas any inequalites are more error prone when
trying to evaluate how a section of code will react under different
circumstances.


As long as everything is an equality, then I don't mind comparing to the
end of an 70 column line. Mixed types of equalities or inequalities
require too many operations mentally evaluate safely.

Personally, I'd only chain inequality for something like this:
if 0 < x < 10:
 
L

Lie Ryan

Simon said:
I was going to do it that way for aesthetics if nothing else, but in
this particular code "self.higher is self.lower" could only have been
True if they were both None, so the final "is None" was redundant and
I only included it as a "just-in-case" check in case someone someday
used the code in such a way as to assign the same object (not None) to
both self.higher and self.lower... Totally pointless, I'm sorry to
say. I'm glad the whole line was redundant really.

I say, you should keep the `is None` for readability and to safe guard
against immutable optimization and/or Singleton objects. Due to an
implementation detail, python does not guarantee that two immutable
object will return different objects; you have:
True
 
L

Lie Ryan

Paul said:
Generally, having a special
value like None to denote a missing datum was considered standard
practice a few decades ago,

I guess in python, None as the missing datum idiom is still quite prevalent:

def cat_list(a=None, b=None):
# poor man's list concatenation
if a is None and b is None: return []
if a is None: return b
if b is None: return a
return a + b
 
L

Lie Ryan

Paul said:
Well, that assert is not right because you have to handle the case
where one of the values is None. The general sentiment is reasonable
though, if you're concerned that the precondition may not be valid.

Well, it depends on where you put the assert statement. If your code is
like this:

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
assert self.lower <= self.higher

then the assert statement is correct.

Some people might prefer to keep all asserts at the top of function though.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,772
Messages
2,569,588
Members
45,100
Latest member
MelodeeFaj
Top