1.10 Assigning and Testing with One Statement
Credit: Alex Martelli
1.10.1 Problem
You are
transliterating C or Perl code to Python, and, to keep close to the
original's structure, you need an
expression's result to be both assigned and tested
(as in if((x=foo( )) or while((x=foo(
)) in such other languages).
1.10.2 Solution
In Python, you can't code:
if x=foo( ):
Assignment is a statement, so it cannot fit into an expression, which
is necessary for conditions of if and
while statements. Normally this
isn't a problem, as you can just structure your code
around it. For example, this is quite Pythonic:
while 1:
line = file.readline( )
if not line: break
process(line)
In modern Python, this is far better, but it's even
farther from C-like idioms:
for line in file.xreadlines( ):
process(line)
In Python 2.2, you can be even simpler and more elegant:
for line in file:
process(line)
But sometimes you're transliterating C, Perl, or
some other language, and you'd like your
transliteration to be structurally close to the original.
One simple utility class makes this easy:
class DataHolder:
def _ _init_ _(self, value=None):
self.value = value
def set(self, value):
self.value = value
return value
def get(self):
return self.value
# optional and strongly discouraged, but handy at times:
import _ _builtin_ _
_ _builtin_ _.DataHolder = DataHolder
_ _builtin_ _.data = DataHolder( )
With the help of the DataHolder class and its
data instance, you can keep your C-like code
structure intact in transliteration:
while data.set(file.readline( )):
process(data.get( ))
1.10.3 Discussion
In Python, assignment is not an expression. Thus, you cannot assign
the result that you are testing in, for example, an
if, elif, or
while statement. This is usually okay: you just
structure your code to avoid the need to assign while testing (in
fact, your code will often become clearer as a result). However,
sometimes you may be writing Python code that is the transliteration
of code originally written in C, Perl, or another language that
supports assignment-as-expression. For example, such transliteration
often occurs in the first Python version of an algorithm for which a
reference implementation is supplied, an algorithm taken from a book,
and so on. In such cases, having the structure of your initial
transliteration be close to that of the code you're
transcribing is often preferable. Fortunately, Python offers enough
power to make it pretty trivial to satisfy this requirement.
We can't redefine assignment, but we can have a
method (or function) that saves its argument somewhere and returns
that argument so it can be tested. That
"somewhere" is most naturally an
attribute of an object, so a method is a more natural choice than a
function. Of course, we could just retrieve the attribute directly
(i.e., the get method is redundant), but it looks
nicer to have symmetry between data.set and
data.get.
Special-purpose solutions, such as the xreadlines
method of file objects, the similar decorator function in the
xreadlines module, and (not so special-purpose)
Python 2.2 iterators, are obviously preferable for the purposes for
which they've been designed. However, such
constructs can imply even wider deviation from the structure of the
algorithm being transliterated. Thus, while they're
great in themselves, they don't really address the
problem presented here.
data.set(whatever) can be seen as little more than
syntactic sugar for data.value=whatever, with the
added value of being acceptable as an expression. Therefore,
it's the one obviously right way to satisfy the
requirement for a reasonably faithful transliteration. The only
difference is the syntactic sugar variation needed, and
that's a minor issue.
Importing _ _builtin_ _ and assigning to its
attributes is a trick that basically defines a new built-in object at
runtime. All other modules will automatically be able to access these
new built-ins without having to do an import. It's
not good practice, though, since readers of those modules should not
need to know about the strange side effects of other modules in the
application. Nevertheless, it's a trick worth
knowing about in case you encounter it.
Not recommended, in any case, is the following abuse of list format
as comprehension syntax:
while [line for line in (file.readline(),) if line]:
process(line)
It works, but it is unreadable and error-prone.
1.10.4 See Also
The Tutorial section on classes; the
documentation for the builtin module in the
Library Reference.
|