I l@ve RuBoard Previous Section Next Section

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.

    I l@ve RuBoard Previous Section Next Section