assignment expression peeve

P

Paul Rubin

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.
 
B

Ben Finney

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.
 
P

Paul Rubin

Ben Finney said:
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.
 
B

Ben Finney

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.
 
C

Carl Banks

Paul said:
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
 
A

Alex Martelli

Paul said:
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
 
D

Daniel Dittmar

Paul said:
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
 
P

Peter Otten

Paul said:
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
 
D

David C. Fox

Paul said:
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
 
A

Alex Martelli

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
 
M

Mark Day

Paul Rubin said:
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.

If you want to address the nesting aspect of the problem, don't forget
the continue and break statements. Instead of needing a (nested) else
clause, you can put a continue or break in the if clause, and put the
"else" work at the same indentation as the "if" statement itself. For
example:

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

g = re.match(pat2, line)
if g:
x = mugwump(g.group(3))
y = wumpus(g.group(2))
return defenestrate(x, y+3)

g = re.match(pat3, line)
if g:
# do something completely different with groups of g
continue

g = re.match(pat4, line)
if g:
# more of the same
continue

Note that the match against pat2 already does a return, so no continue
needed.

-Mark
 
P

Paul Rubin

Ben Finney said:
The example you gave was contrived, sure.

It wasn't contrived, it was from a real program I was writing at the
time, except I abstracted out some of the code.
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.

Well that's normal with case structures. If the cases are all doing
the same thing, there's no need for a case structure.
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.

Four cases with 2 to 5 lines of code on each case is not a monstrous
structure. Making a bunch of separate named functions and a dispatch
system is far more bloated and confusing than an if/elif chain.
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.

If/elif was fine for what I was doing.
 
P

Paul Rubin

Alex Martelli said:
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

Yes, this is very close to what I ended up doing. GMTA :)
 
P

Paul Rubin

Carl Banks said:
The designers of Python believe that assignment expressions are bad
news

I'm not sure you're correct about that. The impression I've gotten
when this subject has come up before is designers are terrified of
someone saying

if x = y:

when they meant ==, and causing a nuclear meltdown or something.
Using a different operator like "x := y", avoids that problem which is
why I did it that way in the example I gave. However, with that
problem removed, it's still not considered a done deal.
(which I agree with),

Well, that's your opinion.
and they will NEVER make assigments into expressions just to avoid
this little problem.

There's no way to know that, without first waiting til the end of "never".
 
P

Paul Rubin

Mark Day said:
If you want to address the nesting aspect of the problem, don't forget
the continue and break statements. Instead of needing a (nested) else
clause, you can put a continue or break in the if clause, and put the
"else" work at the same indentation as the "if" statement itself.

Thanks. That's a good point and probably a workable solution for this
situation. However it's still a nuisance that the most natural and
obvious way to write the code isn't permitted.
 
C

Carl Banks

Paul said:
I'm not sure you're correct about that. The impression I've gotten
when this subject has come up before is designers are terrified of
someone saying

if x = y:

when they meant ==, and causing a nuclear meltdown or something.
Using a different operator like "x := y", avoids that problem which is
why I did it that way in the example I gave. However, with that
problem removed, it's still not considered a done deal.

With my apologies for sounding a little edgy in the first post, I
think this is only only a minor reason why Python has no assignment
expressions.

The real reason is that assignment expressions lead to all kinds of
ugly and unreadable code. This is because there is no linguistic
analogue for assignment as an expression. Things that work as
expressions in computer code are uttered as nouns, or noun phrases, in
natural languages. Assignments, however, are not uttered as nouns,
but as imperatives, and therefore are inappropraite as expressions.

Assignment expressions are bad news, I think, and I think the designers
think, because it goes against the grain of natural language, and is
thus counterintuitive, and leads to code that takes a lot of effort to
read and understand.

Well, that's your opinion.

I've given some linguistic evidence why it's ugly and
counterintuitive. Take it as you will.

There's no way to know that, without first waiting til the end of "never".

Well, an evil alien can possess Guido's body, and declare that Python
will add assignment expressions--and then do it tactfully enough that
the masses won't rise against the BDFL. You're right, I'd have to
wait until never ends to know that won't happen. Barring that though,
I can pretty much assure you that this will NEVER change.


--
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
 
C

Carl Banks

Paul said:
Thanks. That's a good point and probably a workable solution for this
situation. However it's still a nuisance that the most natural and
obvious way to write the code isn't permitted.

It's only natural and obvious to people who have been numbed to the
counterintuitive aspects of assignment expression by years of
programming in C.

Something that would really be the most natural and obvious way to do
it would look something like this, and you wouldn't have to put a big
turd in the language to do it:

supposing m=re.match(s1) if m:
do_something()
else supposing m=re.match(s2) if m:
do_something_else()


--
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
 
P

Paul Rubin

Carl Banks said:
The real reason is that assignment expressions lead to all kinds of
ugly and unreadable code. This is because there is no linguistic
analogue for assignment as an expression.

Swell, tell me the linguistic analog for metaclasses. Or should those
be removed too?
I've given some linguistic evidence why it's ugly and
counterintuitive. Take it as you will.

Well, that's also your opinion. Programmers have been using
assignment expressions in Lisp (and more recently C) for over 40 years
now and it's been working ok for them. Anything programming construct
can be ugly and counterintuitive if used untastefully. The idea is to
apply good judgement when using assignment expressions or anything
else. That way you can get code that's both beautiful and intuitive,
even if you use assignment expressions.
Well, an evil alien can possess Guido's body, and declare that Python
will add assignment expressions--and then do it tactfully enough that
the masses won't rise against the BDFL. You're right, I'd have to
wait until never ends to know that won't happen. Barring that though,
I can pretty much assure you that this will NEVER change.

Well, to be more specific, I don't get any sense from Guido's or Tim's
posts that it will never change. I do get the sense that a strong
enough case for changing it has not yet been made, and so it hasn't
changed thus far. I also get the sense that "if x=y: ..." will never
be allowed because of the possible confusion between = and ==. That
leaves the possibility open for some adding different assignment
operator or function someday, if there's a strong argument for adding
it. The regexp example I gave was a case that comes up over and over,
and it turns out Alex even has a workaround in the Python Cookbook for
dealing with it. But it seems to me like the tried and true solution
(in other languages) is assignment expressions, so my post aimed to
point out a common Python situation where they'd be useful.
 
L

Lulu of the Lotus-Eaters

|Swell, tell me the linguistic analog for metaclasses.

Metric structure?

I want my class to be a villanelle...
 
C

Carl Banks

Paul said:
Swell, tell me the linguistic analog for metaclasses. Or should those
be removed too?

Paragraph that defines some of its own words?

Seriously, you're comparing apples to oranges here. I'm comparing
syntax here, and there's nothing syntactical about metaclasses. If
you want to compare a class statement to natural language, that works.
Metaclasses don't have syntax, regular classes don't have syntax.
Class statements do.

And class statement certainly does have an analogue in natural
language. If you have ever uttered an itemized list, then you've used
the natural language analogue of a class statement.

Well, that's also your opinion. Programmers have been using
assignment expressions in Lisp (and more recently C) for over 40 years
now and it's been working ok for them.

I say C would be better off without them. Lisp probably would too,
although I can believe that, because Lisp is into that "design your
own language" thing, assignment expressions might be an evil worth
having in the language. But maybe not even then.

Anything programming construct
can be ugly and counterintuitive if used untastefully. The idea is to
apply good judgement when using assignment expressions or anything
else. That way you can get code that's both beautiful and intuitive,
even if you use assignment expressions.

Yeah, yeah, yeah. Some things are easier to abuse than others, and I
find assignment expressions to be one of the most often and most
easily abused contructs in the C language. It would certainly happen
in Python, too.

And frankly, I say nothing involving an assignment expression is every
beautiful or intuitive. My opinion on the beauty part. The intuitive
part is supported by the linguistic evidence I gave you.

Well, to be more specific, I don't get any sense from Guido's or Tim's
posts that it will never change. I do get the sense that a strong
enough case for changing it has not yet been made, and so it hasn't
changed thus far.

Because one doesn't exist. The little idiom you're complaining about
is the strongest case for it, and that's a VERY WEAK case. And there
are other solutions that don't **** up the rest of the language.


[snip]
But it seems to me like the tried and true solution
(in other languages) is assignment expressions, so my post aimed to
point out a common Python situation where they'd be useful.

It's not the tried and true solution. It's the hackish solution that
takes advantage of a mistake in the language. There are other ways to
solve this relatively minor problem.

Consider Perl. This problem never happens in Perl, but guess
what--it's not because Perl has assignment experssions!!! Perl
manages to handle this idiom perfectly well without assignment
expressions.

The real problem is that this idiom is awkward in Python. It's not
necessary to add assignment expressions to the language to fix this.


--
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top