Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Python > assignment expression peeve

Reply
Thread Tools

assignment expression peeve

 
 
Paul Rubin
Guest
Posts: n/a
 
      10-15-2003
OK, I want to scan a file for lines matching a certain regexp. I'd
like to use an assignment expression, like

for line in file:
if (g := re.match(pat, line)):
croggle(g.group(1))

Since there are no assignment expressions in Python, I have to use a
temp var. That's a little more messy, but bearable:

for line in file:
g = re.match(pat, line)
if g:
croggle(g.group(1))

It gets annoying when there are 4 different regexps that the line
might match, and I want to do something different depending on which
one matches. That's not that uncommon a text scanning situation.
With assignment expressions, it's a very natural if/elif chain:

for line in file:
if g := re.match(pat1, line):
croggle(g.group(1), 17)
elif g := re.match(pat2, line):
x = mugwump(g.group(3))
y = wumpus(g.group(2))
return defenestrate(x, y+3)
elif g := re.match(pat3, line):
# do something completely different with groups of g
elif g := re.match(pat4, line):
# more of the same

Without assigment expressions, it gets unspeakably ugly. You have to
use a deeply nested if/else if sequence where you match the regexp and
test the result on 2 separate lines at each branch, or reorganize the
code to use some kind of dispatch table (good if there's a lot more
than 4 regexps, but overkill for just 4), or whatever. I ended up
creating a special class instance just to match a regexp and remember
the result, so I could write in the if/elif style.

This kind of regexp matching is a common pattern and I keep wanting
assignment expressions whenever I code it, and end up crocking up some
silly workaround.
 
Reply With Quote
 
 
 
 
Ben Finney
Guest
Posts: n/a
 
      10-15-2003
On 14 Oct 2003 21:12:29 -0700, Paul Rubin wrote:
> It gets annoying when there are 4 different regexps that the line
> might match, and I want to do something different depending on which
> one matches.


If you want to do lots of different things, you really should be placing
them in different functions:

def deal_with_line_type_A():
# do stuff for lines of type A

def deal_with_line_type_B():
# do stuff for lines of type B

This allows the tasks to be completely different for each type of line,
without cramming it into one structure.

Then, having defined what to do with each line type, map the regex to
the function for each type:

line_action = {
'AAA': deal_with_line_type_A,
'BBB': deal_with_line_type_B,
}

The dictionary then acts as the switchboard:

for regex in line_action:
match = re.match( regex, line )
if( match ):
line_action[regex](match)

Extending the range of lines is a matter of adding items to
line_actions, and writing the resulting function. The decision code
remains the same.

--
\ "Self-respect: The secure feeling that no one, as yet, is |
`\ suspicious." -- Henry L. Mencken |
_o__) |
Ben Finney <http://bignose.squidly.org/>
 
Reply With Quote
 
 
 
 
Paul Rubin
Guest
Posts: n/a
 
      10-15-2003
Ben Finney <(E-Mail Removed)> writes:
> If you want to do lots of different things, you really should be placing
> them in different functions:


Yeah, I mentioned I could do something like that, but that it was
overkill for such a small list. Really, anywhere you have an
if/elif/elif/elif chain of any kind, you can probably replace it with
a lookup table. But the if/elif/elif idiom exists because a lot of
the time, it's the most natural and clear and correct way to write the
code. And this is just an obvious one of those instances, except it's
thwarted by a Python language deficiency that I hope will get
corrected someday.
 
Reply With Quote
 
Ben Finney
Guest
Posts: n/a
 
      10-15-2003
On 14 Oct 2003 22:19:07 -0700, Paul Rubin wrote:
> Ben Finney <(E-Mail Removed)> writes:
>> If you want to do lots of different things, you really should be
>> placing them in different functions:

>
> Yeah, I mentioned I could do something like that, but that it was
> overkill for such a small list.


The example you gave was contrived, sure. But even it was getting
complex enough that it was hard to parse visually -- and using a switch
or case structure wouldn't have made it easier. The problem was the
fact that each case was doing something completely different.

Once each case is more complex than a smple statement or two, it makes
more sense to handle them in separate functions. If a task expands to
several statements, the likelihood is that it will keep expanding.
Better that it do so in a separate function rather than bloating some
monstrous switch structure.

> But the if/elif/elif idiom exists because a lot of the time, it's the
> most natural and clear and correct way to write the code.


Yes, when each case is trivially simple and easy to read along with all
the others, it does make sense to keep them together. The if/elif/else
structure works fine there.

That's not so for the example you gave, where each case was more complex
and differed sufficiently from the others that they were hard to see as
a single structure. Thus why I recommended using separate functions if
you want to do lots of different things.

--
\ "There is more to life than increasing its speed." -- Mahatma |
`\ Gandhi |
_o__) |
Ben Finney <http://bignose.squidly.org/>
 
Reply With Quote
 
Carl Banks
Guest
Posts: n/a
 
      10-15-2003
Paul Rubin wrote:
> Without assigment expressions, it gets unspeakably ugly.


The designers of Python believe that assignment expressions are bad
news (which I agree with), and they will NEVER make assigments into
expressions just to avoid this little problem.

Frankly, I think assignment expressions would cause far more
unspeakable ugliness than they would prevent.

There's workarounds. Live with them.


--
CARL BANKS http://www.aerojockey.com/software

As the newest Lady Turnpot descended into the kitchen wrapped only in
her celery-green dressing gown, her creamy bosom rising and falling
like a temperamental souffle, her tart mouth pursed in distaste, the
sous-chef whispered to the scullery boy, "I don't know what to make of
her."
--Laurel Fortuner, Montendre, France
1992 Bulwer-Lytton Fiction Contest Winner
 
Reply With Quote
 
Alex Martelli
Guest
Posts: n/a
 
      10-15-2003
Paul Rubin wrote:

> OK, I want to scan a file for lines matching a certain regexp. I'd
> like to use an assignment expression, like
>
> for line in file:
> if (g := re.match(pat, line)):
> croggle(g.group(1))
>
> Since there are no assignment expressions in Python, I have to use a
> temp var. That's a little more messy, but bearable:

...
> This kind of regexp matching is a common pattern and I keep wanting
> assignment expressions whenever I code it, and end up crocking up some
> silly workaround.


Indeed, this is one case where I have in fact used the pattern I
showed in more generic context in the Cookbook, something like (typing
the code back in from memory):

class Matcher(object):
def __init__(self, pat, *args):
self.re = re.compile(pat, *args)
self.mo = None
def __nonzero__(self):
return bool(self.mo)
def match(self, astr):
self.mo = self.re.match(astr)
return self.mo
def __getattr__(self, name):
return getattr(self.mo, name)

I'd rather inherit from the match object's type, but _sre doesn't
allow that; this containment + delegation is OK, anyway.

Note that my use is somewhat different from yours -- I prefer to
compile my re's at the start anyway, so what I do here is e.g.

ma1 = Matcher(pat1)
ma2 = Matcher(pat2)
for line in file:
if ma1.match(line): croggle(ma1.group(1))
elif ma2.match(line): plakke(ma2.group(2))

i.e., I prefer to keep separate matchers, one per re pattern.
If you prefer to have just one matcher:

ma = Matcher()
for line in file:
if ma.match(pat1, line): croggle(ma.group(1))
elif ma.match(pat2, line): plakke(ma.group(2))

that's perhaps even easier to arrange:

class Matcher(object):
def match(self, repat, astr):
self.mo = re.match(repat, astr)
return self.mo
def __getattr__(self, name):
return getattr(self.mo, name)

no need for __init__ or __nonzero__ (actually I'm not quite
sure WHY I had __nonzero__ last time I coded this -- maybe I
was also testing the matcher itself, not just the return of its
match method).


Alex

 
Reply With Quote
 
Daniel Dittmar
Guest
Posts: n/a
 
      10-15-2003
Paul Rubin wrote:
> OK, I want to scan a file for lines matching a certain regexp. I'd
> like to use an assignment expression, like
>
> for line in file:
> if (g := re.match(pat, line)):
> croggle(g.group(1))

[...]
> It gets annoying when there are 4 different regexps that the line
> might match, and I want to do something different depending on which
> one matches. That's not that uncommon a text scanning situation.
> With assignment expressions, it's a very natural if/elif chain:

[...]
> This kind of regexp matching is a common pattern and I keep wanting
> assignment expressions whenever I code it, and end up crocking up some
> silly workaround.


If this is a common pattern in your code, then write an iterator class that
reads lines from a file and spits out the match object (+ tag, so you know
which regular expression was matched.
So your code becomes:

for match, tag in LineMatcher (...).parseFile (fname):
if tag == tag1:
action1
elif tag == tag2:
action2

cons: you have to invent names for every pattern
pros: patterns are compiled and thus more efficient

Daniel



 
Reply With Quote
 
Peter Otten
Guest
Posts: n/a
 
      10-15-2003
Paul Rubin wrote:

> It gets annoying when there are 4 different regexps that the line
> might match, and I want to do something different depending on which
> one matches. That's not that uncommon a text scanning situation.
> With assignment expressions, it's a very natural if/elif chain:
>
> for line in file:
> if g := re.match(pat1, line):
> croggle(g.group(1), 17)
> elif g := re.match(pat2, line):
> x = mugwump(g.group(3))
> y = wumpus(g.group(2))
> return defenestrate(x, y+3)
> elif g := re.match(pat3, line):
> # do something completely different with groups of g
> elif g := re.match(pat4, line):
> # more of the same
>
> Without assigment expressions, it gets unspeakably ugly. You have to
> use a deeply nested if/else if sequence where you match the regexp and
> test the result on 2 separate lines at each branch, or reorganize the
> code to use some kind of dispatch table (good if there's a lot more
> than 4 regexps, but overkill for just 4), or whatever. I ended up
> creating a special class instance just to match a regexp and remember
> the result, so I could write in the if/elif style.
>
> This kind of regexp matching is a common pattern and I keep wanting
> assignment expressions whenever I code it, and end up crocking up some
> silly workaround.


Here's yet another "silly workaround" that attacks the inline assignment
problem in the most general form I can think of. Instead of g := expr, use
g(expr). The overhead is an additional get() method call when accessing the
value.
Of course you could delegate attribute access as Alex Martelli did, but this
reminds me too much of C++ STL's auto_ptr

class Assign(object):
def __call__(self, value):
self.value = value
return value
def get(self):
return self.value

#sample usage
g = Assign()
for line in file:
if g(re.match(pat1, line)):
croggle(g.get().group(1), 17)
elif g(re.match(pat2, line)):
x = mugwump(g.get().group(3))
y = wumpus(g.get().group(2))
return defenestrate(x, y+3)
elif g(re.match(pat3, line)):
# do something completely different with groups of g
elif g(re.match(pat4, line)):
# more of the same

Peter
 
Reply With Quote
 
David C. Fox
Guest
Posts: n/a
 
      10-15-2003
Paul Rubin wrote:
> OK, I want to scan a file for lines matching a certain regexp. I'd
> like to use an assignment expression, like
>
> for line in file:
> if (g := re.match(pat, line)):
> croggle(g.group(1))
>
> Since there are no assignment expressions in Python, I have to use a
> temp var. That's a little more messy, but bearable:
>
> for line in file:
> g = re.match(pat, line)
> if g:
> croggle(g.group(1))
>
> It gets annoying when there are 4 different regexps that the line
> might match, and I want to do something different depending on which
> one matches. That's not that uncommon a text scanning situation.
> With assignment expressions, it's a very natural if/elif chain:
>
> for line in file:
> if g := re.match(pat1, line):
> croggle(g.group(1), 17)
> elif g := re.match(pat2, line):
> x = mugwump(g.group(3))
> y = wumpus(g.group(2))
> return defenestrate(x, y+3)
> elif g := re.match(pat3, line):
> # do something completely different with groups of g
> elif g := re.match(pat4, line):
> # more of the same
>
> Without assigment expressions, it gets unspeakably ugly. You have to
> use a deeply nested if/else if sequence where you match the regexp and
> test the result on 2 separate lines at each branch, or reorganize the
> code to use some kind of dispatch table (good if there's a lot more
> than 4 regexps, but overkill for just 4), or whatever. I ended up
> creating a special class instance just to match a regexp and remember
> the result, so I could write in the if/elif style.
>
> This kind of regexp matching is a common pattern and I keep wanting
> assignment expressions whenever I code it, and end up crocking up some
> silly workaround.


I like Alex's suggestion, but if you really want to do this generically,
and not just for re.match, you can create a generic proxy object
something like this:

class SetTo:
def setto(self, value):
self.__dict__['value'] = value
return value
def __getattr__(self, name):
return getattr(self.value, name)
def __setattr__(self, name, value):
return setattr(self.value, name, value)

Then your example above can be rewritten as

g = SetTo()
for line in file:
if g.setto(re.match(pat1, line)):
croggle(g.group(1), 17)
elif g.setto(re.match(pat2, line)):
x = mugwump(g.group(3))
y = wumpus(g.group(2))
return defenestrate(x, y+3)
elif g.setto(re.match(pat3, line)):
# do something completely different with groups of g
elif g.setto(re.match(pat4, line)):
# more of the same

David

 
Reply With Quote
 
Alex Martelli
Guest
Posts: n/a
 
      10-15-2003
David C. Fox wrote:
...
> I like Alex's suggestion, but if you really want to do this generically,
> and not just for re.match, you can create a generic proxy object


Yes, a generic (but more explicit) proxy is the Python Cookbook solution
I mentioned. But in practice I've only used it for re's, so...


Alex

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
C/C++ language proposal: Change the 'case expression' from "integral constant-expression" to "integral expression" Adem C++ 42 11-04-2008 12:39 PM
C/C++ language proposal: Change the 'case expression' from "integral constant-expression" to "integral expression" Adem C Programming 45 11-04-2008 12:39 PM
Credit-card number peeve Lawrence D'Oliveiro NZ Computing 16 03-31-2008 12:18 AM
Peeve-black plastic peripherals Don Stauffer in Minnesota Digital Photography 5 12-27-2007 09:23 AM
Smallest peeve ever! Just Another Victim of the Ambient Morality Ruby 3 10-25-2007 11:15 AM



Advertisments