Inline assignments

Alex Martelli aleaxit at yahoo.com
Sun Mar 5 16:54:53 CET 2006


Fredrik Tolf <fredrik at dolda2000.com> wrote:
   ...
> I'm relatively new to Python, and one thing I can't seem to get over is
> the lack of in-expression assignments, as present in many other
> languages. I'd really like to know how Python regulars solve the
> problems posed by that.

In general, we've learned to do without and structure our code
differently -- less compactly, more "airy". Python is not about
maximally compact code -- other languages of roughly equivalent power,
such as Perl, are more suitable if that's your goal; in Python, we aim
for less compactness and more readability (and maintainability).  E.g.:

> if callable(f = getattr(self, "cmd_" + name)):
>       # Do something with f
> elif callable(f = getattr(self, "cmdv_" + name)):
>       # Do something else with f

might become:

f = getattr(self, "cmd_" + name)
if callable(f):
    # Do something with f
else:
    f = getattr(self, "cmdv_" + name)
    if callable(f):
        # Do something else with f

7 lines instead of 4 (if the comments stand for one line each), but
exactly the same semantics.


> Another common problem for me are while loops. I would often like to do

That's part of why Python often prefers for loops -- with a generator to
handle the "looping logic" part, if needed.  For example, 

> while (pos = somestring.find("/")) != -1:
>       part = somestring[:pos]
>       somestring = somestring[pos + 1:]
>       # Handle part

might often become a simple:

for part in somestring.split('/'):
    # handle part

but that has slightly different semantics (handles the part after the
last occurrence of / too, doesn't explicitly record pos, ...) which
might be adjusted if needed (e.g. with a [:-1] after the call to split
and before the colon to omit the last part). If the adjustment is
"major", e.g. you do need every variable to be set just like so in the
body, then a generator can help -- with the focus being on a separation
between the business logic (the "handle part" processing) and the
plumbing (generate all 'parts' in sequence), and the latter going in the
generator, not distracting from the handling as may happen when you mix
the two things.  The business-logic part will always look like:

for pos, part in splitter(somestring, '/'):
    # handle part, using pos as well

and the splitter generator is written elsewhere, a la

def splitter(somestring, separ):
    while True:
        pos = somestring.find(separ)
        if pos == -1: break
        yield pos, somestring[:pos]
        somestring = somestring[pos+1:]

with the big advantage that you may change the implementation to be more
efficient, if and when needed, without touching the business logic part
which remains nestled in the nice, simple, readable for loop.

for example:

def splitter(somestring, separ):
    pos = 0
    while True:
        nextpos = somestring.find(separ, pos)
        if nextpos == -1: break
        yield nextpos-pos, somestring[pos:nextpos]
        pos = nextpos+1

Same semantics, less busywork, so presumably faster (the timeit module
exists to let you check the speed of code fragments, of course).


All this being said, I do sometimes find myself coding a first version
of some algorithm by following as closely as possible some published
reference code or pseudocode, before enhancing it to be clearer, more
efficient, more Pythonic, or whatever. When that happens, some syntax
sugar to "assign and test in the same expression" does come in handy, to
enable coding of a first version that's closer in spirit to the
non-Python reference version.

It was, of course, no trouble at all to make myself an "assign and test
in the same expression" snippet, and I (again, of course) published it
in the Python Cookbook (both online and printed editions) for others'
benefit. For example:

class Holder(object):
    def set(self, value):
        self.value = value
        return value
data = Holder()

Now, with this tiny piece of "plumbing adapter", you can code, e.g.:

while data.set(somestring.find("/")) != -1:
    part = somestring[:data.value]
    somestring = somestring[data.value + 1:]
    # Handle part

Another use case for this Holder class is when you're using Python to
prototype something that you already know will need to be recoded in
another given language once it's ready -- the motivation is similar to
the one for the main use case of "code as close as possible to a
published reference implementation", namely enabling Python code that is
closer to what's idiomatic in some other language than to idiomatic
Python itself.

Therein, of course, lies the danger -- over-relying on this adapter may
mean you never really learn to "think the Python way" and thus to code
idiomatic Python, even when that would be most appropriate (i.e., in
most cases). Still, Python _is_ a "consenting adults" language, so I
think it's appropriate to show how its power can easily let you keep
using idioms coming from different languages... even when that usage is
not the optimal choice that you could and should make, it's up to you.


Alex



More information about the Python-list mailing list