Recipe4.19.Assigning and Testing with One Statement


Recipe 4.19. Assigning and Testing with One Statement

Credit: Alex Martelli, Martin Miller

Problem

You are transliterating C or Perl code to Python, and to keep close to the original's structure, you'd like an expression's result to be both assigned and tested (as in if((x=foo( )) or while((x=foo( )) in such other languages).

Solution

In Python, you can't code if x=foo(): . . . . Assignment is a statement, so it cannot fit into an expression, and you can only use expressions as conditions of if and while statements. This isn't a problem, it just means you have to structure your code Pythonically! For example, to process a file object f line by line, instead of the following C-like (and syntactically incorrect, in Python) approach:

while (line=f.readline( )) != '':     process(line)

you can code a highly Pythonic (readable, clean, fast) approach:

for line in f:     process(line)

But sometimes, you're transliterating from C, Perl, or another language, and you'd like your transliteration to be structurally close to the original. One simple utility class makes it easy:

class DataHolder(object):     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 nevertheless handy at times: import _ _builtin_ _ _ _builtin_ _.DataHolder = DataHolder _ _builtin_ _.data = data = DataHolder( )

With the help of the DataHolder class and its instance data, you can keep your C-like code structure intact in transliteration:

while data.set(file.readline( )) != '':     process(data.get( ))

Discussion

In Python, assignment is a statement, not an expression. Thus, you cannot assign the result that you are also testing, for example, in the condition of an if, elif, or while statement. This is usually fine: just structure your code to avoid the need to assign while testing (in fact, your code will often become clearer as a result). In particular, whenever you feel the need to assign-and-test within the condition of a while loop, that's a good hint that your loop's structure probably wants to be refactored into a generator (or other iterator). Once you have refactored in this way, your loops become plain and simple for statements. The example given in the recipe, looping over each line read from a text file, is one where the refactoring has already been done on your behalf by Python itself, since a file object is an iterator whose items are the file's lines.

However, sometimes you may be writing Python code that is the transliteration of code originally written in C, Perl, or some other language that supports assignment-as-expression. Such transliterations often occur 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, it's often preferable to have the structure of your initial transliteration be close to that of the code you're transcribing. You can refactor later and make your code more Pythonicclearer, faster, and so on. But first, you want to get working code as soon as possible, and specifically you want code that is easy to check for compliance to the original it has been transliterated from. Fortunately, Python offers enough power to make it quite easy for you to satisfy this requirement.

Python doesn't let us redefine the meaning of assignment, but we can have a method (or function) that saves its argument somewhere and also 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 me to have symmetry between data.set and data.get.

data.set(whatever) can be seen as little more than syntactic sugar around 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 between the resulting Python code and the original (say) C or Perl code, is at the syntactic sugar levelthe overall structure is the same, and that's the key issue.

Importing _ _builtin_ _ and assigning to its attributes is a trick that basically defines a new built-in object at runtime. You can use that trick in your application's start-up code, and then all other modules will automatically be able to access your new built-ins without having to do an import. It's not good Python practice, though; on the contrary, it's pushing the boundaries of Pythonic good taste, since the readers of all those other modules should not have to know about the strange side effects performed in your application's startup code. But since this recipe is meant to offer a quick-and-dirty approach for a first transliteration that will soon be refactored to make it better, it may be acceptable in this specific context to cut more corners than one would in production-level code.

On the other hand, one trick you should definitely not use is the following abuse of a currently existing wart in list comprehensions:

while [line for line in [f.readline( )] if line!='']:     process(line)

This trick currently works, since both Python 2.3 and 2.4 still "leak" the list comprehension control variable (here, line) into the surrounding scope. However, besides being obscure and unreadable, this trick is specifically deprecated: list comprehension control variable leakage will be fixed in some future version of Python, and this trick will then stop working at all.

See Also

The Tutorial section on classes; the documentation for the _ _builtin_ _ module in the Library Reference and Python in a Nutshell; Language Reference and Python in a Nutshell documentation on list comprehensions.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net