Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Python > RFC: Assignment as expression (pre-PEP)

Reply
Thread Tools

RFC: Assignment as expression (pre-PEP)

 
 
TimeHorse@gmail.com
Guest
Posts: n/a
 
      04-05-2007
I would like to gauge interest in the following proposal:

Problem:

Assignment statements cannot be used as expressions.

Performing a list of mutually exclusive checks that require data
processing can cause excessive tabification. For example, consider
the following python snipet...

temp = my_re1.match(exp)
if temp:
# do something with temp
else:
temp = my_re2.match(exp)
if temp:
# do something with temp
else:
temp = my_re3.match(exp)
if temp:
# do something with temp
else:
temp = my_re4.match(exp)

# etc.

Even with 2-space tabification, after about 20 matches, the
indentation will take up half an 80-column terminal screen.

Details:

Python distinguishes between an assignment statement and an equality
expression. This is to force disambiguation of assignment and
comparison so that a statement like:

if x = 3:

Will raise an expression rather than allowing the programmer to
accidentally overwrite x. Likewise,

x == 3

Will either return True, False or raise a NameError exception, which
can alert the author of any potential coding mistakes since if x = 3
(assignment) was meant, assignment being a statement returns nothing
(though it may raise an exception depending on the underlying
assignment function called).

Because this forced disambiguation is a guiding virtue of the python
language, it would NOT be wise to change these semantics of the
language.

Proposal:

Add a new assignment-expression operator to disambiguate it completely
from existing operators.

Although any number of glyph could be used for such a new operator, I
here propose using pascal/gnu make-like assignment. Specifically,

let:

x = 3

Be a statement that returns nothing;

let:

x == 3

Be an expression that, when x is a valid, in-scope name, returns True
or False;

let:

x := 3

Be an expression that first assigned the value (3) to x, then returns
x.

Thus...

if x = 3:
# Rais exception
pass

if x == 3:
# Execute IFF x has a value equivalent to 3
pass

if x := 3:
# Executes based on value of x after assignment;
# since x will be 3 and non-zero and thus represents true, always
executed
pass

Additional:

Since python allows in-place operator assignment, (e.g. +=, *=, etc.),
allow for these forms again by prefixing each diglyph with a colon
(, forming a triglyph.

E.g.

if x :+= 3:
# Executes IFF, after adding 3 to x, x represents a non-zero number.
pass

Also note, that although the colon operator is used to denote the
beginning of a programme block, it should be easily distinguished from
the usage of : to denote a diglyph or triglyph assignment expression
as well as the trinary conditional expression. This is because
firstly, the statement(s) following a colon ( in existing python
should never begin with an assignment operator. I.e.,

if x: = y

is currently not valid python. Any attempt at interpreting the
meaning of such an expression in the current implementation of python
is likely to fail. Secondly, the diglyph and triglyph expressions do
not contain spaces, further disambiguating them from existing python.

Alternative proposals for dyglyph and triglyph representations for
assignment expressions are welcome.

Implementation details:

When the python interpreter parser encounters a diglyph or triglyph
beginning with a colon ( and ending with an equals sign (=), perform
the assignment specified by glyph[1:] and then return the value of the
variable(s) on the left-hand side of the expression. The assignment
function called would be based on standard python lookup rules for the
corresponding glyph[1:] operation (the glyph without the leading
colon).

Opposition:

Adding any new operator to python could be considered code bloat.

Using a colon in this way could still be ambiguous.

Adding the ability to read triglyph operators in the python
interpreter parser would require too big a code revision.

Usage is too obscure.

Using an assignment expression would lead to multiple conceptual
instructions for a single python statement (e.g. first an assignment,
then an if based on the assignment would mean two operations for a
single if statement.)

Comments:

[Please comment]

Jeffrey.

 
Reply With Quote
 
 
 
 
Carsten Haese
Guest
Posts: n/a
 
      04-05-2007
On Thu, 2007-04-05 at 12:51 -0700, http://www.velocityreviews.com/forums/(E-Mail Removed) wrote:
> I would like to gauge interest in the following proposal:
>
> Problem:
>
> Assignment statements cannot be used as expressions.
>
> Performing a list of mutually exclusive checks that require data
> processing can cause excessive tabification. For example, consider
> the following python snipet...
>
> temp = my_re1.match(exp)
> if temp:
> # do something with temp
> else:
> temp = my_re2.match(exp)
> if temp:
> # do something with temp
> else:
> temp = my_re3.match(exp)
> if temp:
> # do something with temp
> else:
> temp = my_re4.match(exp)
>
> # etc.
>
> Even with 2-space tabification, after about 20 matches, the
> indentation will take up half an 80-column terminal screen.


If that's your only justification for this proposal, that's almost
certainly not enough to convince anybody of its necessity. Your code
example should be rewritten as a loop:

match_actions = [(my_re1, action1),
(my_re2, action2),
...]

for my_re, action in match_actions:
if my_re.match(exp):
action(exp)
break

Hope this helps,

Carsten


 
Reply With Quote
 
 
 
 
Duncan Booth
Guest
Posts: n/a
 
      04-05-2007
(E-Mail Removed) wrote:

> Performing a list of mutually exclusive checks that require data
> processing can cause excessive tabification. For example, consider
> the following python snipet...
>
> temp = my_re1.match(exp)
> if temp:
> # do something with temp
> else:
> temp = my_re2.match(exp)
> if temp:
> # do something with temp
> else:
> temp = my_re3.match(exp)
> if temp:
> # do something with temp
> else:
> temp = my_re4.match(exp)
>


Can you come up with a real example where this happens and which cannot be
easily rewritten to provide better, clearer code without the indentation?

I'll admit to having occasionally had code not entirely dissimilar to this
when first written, but I don't believe it has ever survived more than a
few minutes before being refactored into a cleaner form. I would claim that
it is a good thing that Python makes it obvious that code like this should
be refactored.
 
Reply With Quote
 
Duncan Booth
Guest
Posts: n/a
 
      04-05-2007
Carsten Haese <(E-Mail Removed)> wrote:

> If that's your only justification for this proposal, that's almost
> certainly not enough to convince anybody of its necessity. Your code
> example should be rewritten as a loop:
>
> match_actions = [(my_re1, action1),
> (my_re2, action2),
> ...]
>
> for my_re, action in match_actions:
> if my_re.match(exp):
> action(exp)
> break
>


Depending on what his 'do something with temp' actually was, it may or may
not be easy to rewrite it as a for loop. However, even if a for loop isn't
an obvious replacement other solutions may be appropriate such as combining
the regular expressions to a single regex with named groups and/or using
the command pattern.

If assignment was an expression that only addresses one problem with
the sample code. It still leaves his code with excessive repetition and
probably with an excessively long function that calls out to be refactored
as a group of smaller methods.
 
Reply With Quote
 
darklord@timehorse.com
Guest
Posts: n/a
 
      04-05-2007
On Apr 5, 4:22 pm, Duncan Booth <(E-Mail Removed)> wrote:
> Can you come up with a real example where this happens and which cannot be
> easily rewritten to provide better, clearer code without the indentation?
>
> I'll admit to having occasionally had code not entirely dissimilar to this
> when first written, but I don't believe it has ever survived more than a
> few minutes before being refactored into a cleaner form. I would claim that
> it is a good thing that Python makes it obvious that code like this should
> be refactored.


I am trying to write a parser for a text string. Specifically, I am
trying to take a filename that contains meta-data about the content of
the A/V file (mpg, mp3, etc.).

I first split the filename into fields separated by spaces and dots.

Then I have a series of regular expression matches. I like
Cartesian's 'event-based' parser approach though the even table gets a
bit unwieldy as it grows. Also, I would prefer to have the 'action'
result in a variable assignment specific to the test. E.g.

def parseName(name):
fields = sd.split(name)
fields, ext = fields[:-1], fields[-1]
year = ''
capper = ''
series = None
episodeNum = None
programme = ''
episodeName = ''
past_title = false
for f in fields:
if year_re.match(f):
year = f
past_title = True
else:
my_match = capper_re.match(f):
if my_match:
capper = capper_re.match(f).group(1)
if capper == 'JJ' or capper == 'JeffreyJacobs':
capper = 'Jeffrey C. Jacobs'
past_title = True
else:
my_match = epnum_re.match(f):
if my_match:
series, episodeNum = my_match.group('series',
'episode')
past_title = True
else:
# If I think of other parse elements, they go
here.
# Otherwise, name is part of a title; check for
capitalization
if f[0] >= 'a' and f[0] <= 'z' and f not in
do_not_capitalize:
f = f.capitalize()
if past_title:
if episodeName: episodeName += ' '
episodeName += f
else:
if programme: programme += ' '
programme += f

return programme, series, episodeName, episodeNum, year, capper,
ext

Now, the problem with this code is that it assumes only 2 pieces of
free-form meta-data in the name (i.e. Programme Name and Episode
Name). Also, although this is not directly adaptable to Cartesian's
approach, you COULD rewrite it using a dictionary in the place of
local variable names so that the event lookup could consist of 3
properties per event: compiled_re, action_method, dictionary_string.
But even with that, in the case of the epnum match, two assignments
are required so perhaps a convoluted scheme such that if
dictionary_string is a list, for each of the values returned by
action_method, bind the result to the corresponding ith dictionary
element named in dictionary_string, which seems a bit convoluted. And
the fall-through case is state-dependent since the 'unrecognized
field' should be shuffled into a different variable dependent on
state. Still, if there is a better approach I am certainly up for
it. I love event-based parsers so I have no problem with that
approach in general.

 
Reply With Quote
 
Neil Hodgson
Guest
Posts: n/a
 
      04-05-2007
(E-Mail Removed):

> else:
> my_match = capper_re.match(f):
> if my_match:
> capper = capper_re.match(f).group(1)
> if capper == 'JJ' or capper == 'JeffreyJacobs':
> capper = 'Jeffrey C. Jacobs'
> past_title = True


The assignment to my_match here is not used, so the test can be "if
capper_re.match(f)" which can then merge up into the previous else as an
elif dropping one level of indentation.

Neil
 
Reply With Quote
 
darklord@timehorse.com
Guest
Posts: n/a
 
      04-05-2007
On Apr 5, 6:01 pm, Neil Hodgson <(E-Mail Removed)> wrote:
> (E-Mail Removed):
>
> > else:
> > my_match = capper_re.match(f):
> > if my_match:
> > capper = capper_re.match(f).group(1)
> > if capper == 'JJ' or capper == 'JeffreyJacobs':
> > capper = 'Jeffrey C. Jacobs'
> > past_title = True

>
> The assignment to my_match here is not used, so the test can be "if
> capper_re.match(f)" which can then merge up into the previous else as an
> elif dropping one level of indentation.
>
> Neil


That was a typo. I meant to reuse my_match in the line "capper =
my_match.group(1)" rather than the line above just so I would not have
to evaluate the regular expression twice. Sorry for the confusion.

Jeffrey.

 
Reply With Quote
 
Steven Bethard
Guest
Posts: n/a
 
      04-05-2007
(E-Mail Removed) wrote:
> On Apr 5, 4:22 pm, Duncan Booth <(E-Mail Removed)> wrote:
>> Can you come up with a real example where this happens and which cannot be
>> easily rewritten to provide better, clearer code without the indentation?
>>
>> I'll admit to having occasionally had code not entirely dissimilar to this
>> when first written, but I don't believe it has ever survived more than a
>> few minutes before being refactored into a cleaner form. I would claim that
>> it is a good thing that Python makes it obvious that code like this should
>> be refactored.

>
> I am trying to write a parser for a text string. Specifically, I am
> trying to take a filename that contains meta-data about the content of
> the A/V file (mpg, mp3, etc.).
>
> I first split the filename into fields separated by spaces and dots.
>
> Then I have a series of regular expression matches. I like
> Cartesian's 'event-based' parser approach though the even table gets a
> bit unwieldy as it grows. Also, I would prefer to have the 'action'
> result in a variable assignment specific to the test. E.g.
>
> def parseName(name):
> fields = sd.split(name)
> fields, ext = fields[:-1], fields[-1]
> year = ''
> capper = ''
> series = None
> episodeNum = None
> programme = ''
> episodeName = ''
> past_title = false
> for f in fields:
> if year_re.match(f):
> year = f
> past_title = True
> else:
> my_match = capper_re.match(f):
> if my_match:
> capper = capper_re.match(f).group(1)
> if capper == 'JJ' or capper == 'JeffreyJacobs':
> capper = 'Jeffrey C. Jacobs'
> past_title = True
> else:
> my_match = epnum_re.match(f):
> if my_match:
> series, episodeNum = my_match.group('series',
> 'episode')
> past_title = True
> else:
> # If I think of other parse elements, they go
> here.
> # Otherwise, name is part of a title; check for
> capitalization
> if f[0] >= 'a' and f[0] <= 'z' and f not in
> do_not_capitalize:
> f = f.capitalize()
> if past_title:
> if episodeName: episodeName += ' '
> episodeName += f
> else:
> if programme: programme += ' '
> programme += f
>
> return programme, series, episodeName, episodeNum, year, capper,
> ext


Why can't you combine your regular expressions into a single expression,
e.g. something like::

>>> exp = r'''

... (?P<year>\d{4})
... |
... by\[(?P<capper>.*)\]
... |
... S(?P<series>\d\d)E(?P<episode>\d\d)
... '''
>>> matcher = re.compile(exp, re.VERBOSE)
>>> matcher.match('1990').groupdict()

{'series': None, 'capper': None, 'episode': None, 'year': '1990'}
>>> matcher.match('by[Jovev]').groupdict()

{'series': None, 'capper': 'Jovev', 'episode': None, 'year': None}
>>> matcher.match('S01E12').groupdict()

{'series': '01', 'capper': None, 'episode': '12', 'year': None}

Then your code above would look something like::

for f in fields:
match = matcher.match(f)
if match is not None:
year = match.group('year')
capper = match.group('capper')
if capper == 'JJ' or capper == 'JeffreyJacobs':
capper = 'Jeffrey C. Jacobs'
series = match.group('series')
episodeNum = match.group('episode')
past_title = True
else:
if 'a' <= f[0] <= 'z' and f not in do_not_capitalize:
f = f.capitalize()
if past_title:
if episodeName:
episodeName += ' '
episodeName += f
else:
if programme:
programme += ' '
programme += f

STeVe
 
Reply With Quote
 
Steven Bethard
Guest
Posts: n/a
 
      04-05-2007
Steven Bethard wrote:
> (E-Mail Removed) wrote:
>> On Apr 5, 4:22 pm, Duncan Booth <(E-Mail Removed)> wrote:
>>> Can you come up with a real example where this happens and which
>>> cannot be
>>> easily rewritten to provide better, clearer code without the
>>> indentation?
>>>
>>> I'll admit to having occasionally had code not entirely dissimilar to
>>> this
>>> when first written, but I don't believe it has ever survived more than a
>>> few minutes before being refactored into a cleaner form. I would
>>> claim that
>>> it is a good thing that Python makes it obvious that code like this
>>> should
>>> be refactored.

>>
>> I am trying to write a parser for a text string. Specifically, I am
>> trying to take a filename that contains meta-data about the content of
>> the A/V file (mpg, mp3, etc.).
>>
>> I first split the filename into fields separated by spaces and dots.
>>
>> Then I have a series of regular expression matches. I like
>> Cartesian's 'event-based' parser approach though the even table gets a
>> bit unwieldy as it grows. Also, I would prefer to have the 'action'
>> result in a variable assignment specific to the test. E.g.
>>
>> def parseName(name):
>> fields = sd.split(name)
>> fields, ext = fields[:-1], fields[-1]
>> year = ''
>> capper = ''
>> series = None
>> episodeNum = None
>> programme = ''
>> episodeName = ''
>> past_title = false
>> for f in fields:
>> if year_re.match(f):
>> year = f
>> past_title = True
>> else:
>> my_match = capper_re.match(f):
>> if my_match:
>> capper = capper_re.match(f).group(1)
>> if capper == 'JJ' or capper == 'JeffreyJacobs':
>> capper = 'Jeffrey C. Jacobs'
>> past_title = True
>> else:
>> my_match = epnum_re.match(f):
>> if my_match:
>> series, episodeNum = my_match.group('series',
>> 'episode')
>> past_title = True
>> else:
>> # If I think of other parse elements, they go
>> here.
>> # Otherwise, name is part of a title; check for
>> capitalization
>> if f[0] >= 'a' and f[0] <= 'z' and f not in
>> do_not_capitalize:
>> f = f.capitalize()
>> if past_title:
>> if episodeName: episodeName += ' '
>> episodeName += f
>> else:
>> if programme: programme += ' '
>> programme += f
>>
>> return programme, series, episodeName, episodeNum, year, capper,
>> ext

>
> Why can't you combine your regular expressions into a single expression,
> e.g. something like::
>
> >>> exp = r'''

> ... (?P<year>\d{4})
> ... |
> ... by\[(?P<capper>.*)\]
> ... |
> ... S(?P<series>\d\d)E(?P<episode>\d\d)
> ... '''
> >>> matcher = re.compile(exp, re.VERBOSE)
> >>> matcher.match('1990').groupdict()

> {'series': None, 'capper': None, 'episode': None, 'year': '1990'}
> >>> matcher.match('by[Jovev]').groupdict()

> {'series': None, 'capper': 'Jovev', 'episode': None, 'year': None}
> >>> matcher.match('S01E12').groupdict()

> {'series': '01', 'capper': None, 'episode': '12', 'year': None}
>
> Then your code above would look something like::
>
> for f in fields:
> match = matcher.match(f)
> if match is not None:
> year = match.group('year')
> capper = match.group('capper')
> if capper == 'JJ' or capper == 'JeffreyJacobs':
> capper = 'Jeffrey C. Jacobs'
> series = match.group('series')
> episodeNum = match.group('episode')
> past_title = True


I guess you need to be a little more careful here not to overwrite your
old values, e.g. something like::

year = match.group('year') or year
capper = match.group('capper') or capper
...

STeVe
 
Reply With Quote
 
Gabriel Genellina
Guest
Posts: n/a
 
      04-05-2007
En Thu, 05 Apr 2007 18:08:46 -0300, (E-Mail Removed)
<(E-Mail Removed)> escribió:

> I am trying to write a parser for a text string. Specifically, I am
> trying to take a filename that contains meta-data about the content of
> the A/V file (mpg, mp3, etc.).
>
> I first split the filename into fields separated by spaces and dots.
>
> Then I have a series of regular expression matches. I like
> Cartesian's 'event-based' parser approach though the even table gets a
> bit unwieldy as it grows. Also, I would prefer to have the 'action'
> result in a variable assignment specific to the test. E.g.
>
> def parseName(name):
> fields = sd.split(name)
> fields, ext = fields[:-1], fields[-1]
> year = ''
> capper = ''
> series = None
> episodeNum = None
> programme = ''
> episodeName = ''
> past_title = false
> for f in fields:
> if year_re.match(f):
> year = f
> past_title = True
> else:
> my_match = capper_re.match(f):
> if my_match:
> capper = capper_re.match(f).group(1)
> if capper == 'JJ' or capper == 'JeffreyJacobs':
> capper = 'Jeffrey C. Jacobs'
> past_title = True
> else:
> my_match = epnum_re.match(f):
> if my_match:
> series, episodeNum = my_match.group('series',
> 'episode')
> past_title = True
> else:
> # If I think of other parse elements, they go
> here.
> # Otherwise, name is part of a title; check for
> capitalization
> if f[0] >= 'a' and f[0] <= 'z' and f not in
> do_not_capitalize:
> f = f.capitalize()
> if past_title:
> if episodeName: episodeName += ' '
> episodeName += f
> else:
> if programme: programme += ' '
> programme += f
>
> return programme, series, episodeName, episodeNum, year, capper,
> ext
>
> Now, the problem with this code is that it assumes only 2 pieces of
> free-form meta-data in the name (i.e. Programme Name and Episode
> Name). Also, although this is not directly adaptable to Cartesian's
> approach, you COULD rewrite it using a dictionary in the place of
> local variable names so that the event lookup could consist of 3
> properties per event: compiled_re, action_method, dictionary_string.
> But even with that, in the case of the epnum match, two assignments
> are required so perhaps a convoluted scheme such that if
> dictionary_string is a list, for each of the values returned by
> action_method, bind the result to the corresponding ith dictionary
> element named in dictionary_string, which seems a bit convoluted. And
> the fall-through case is state-dependent since the 'unrecognized
> field' should be shuffled into a different variable dependent on
> state. Still, if there is a better approach I am certainly up for
> it. I love event-based parsers so I have no problem with that
> approach in general.


Maybe it's worth using a class instance. Define methods to handle each
matching regex, and keep state in the instance.

class NameParser:

def handle_year(self, field, match):
self.year = field
self.past_title = True

def handle_capper(self, field, match):
capper = match.group(1)
if capper == 'JJ' or capper == 'JeffreyJacobs':
capper = 'Jeffrey C. Jacobs'
self.capper = capper
self.past_title = True

def parse(self, name):
fields = sd.split(name)
for field in fields:
for regex,handler in self.handlers:
match = regex.match(field)
if match:
handler(self, field, match)
break

You have to build the handlers list, containing (regex, handler) items;
the "unknown" case might be a match-all expression at the end.
Well, after playing a bit with decorators I got this:

class NameParser:

year = ''
capper = ''
series = None
episodeNum = None
programme = ''
episodeName = ''
past_title = False
handlers = []

def __init__(self, name):
self.name = name
self.parse()

def handle_this(regex, handlers=handlers):
# A decorator; associates the function to the regex
# (Not intended to be used as a normal method! not even a static
method!)
def register(function, regex=regex):
handlers.append((re.compile(regex), function))
return function
return register

@handle_this(r"\(?\d+\)?")
def handle_year(self, field, match):
self.year = field
self.past_title = True

@handle_this(r"(expression)")
def handle_capper(self, field, match):
capper = match.group(1)
if capper == 'JJ' or capper == 'JeffreyJacobs':
capper = 'Jeffrey C. Jacobs'
self.capper = capper
self.past_title = True

@handle_this(r".*")
def handle_unknown(self, field, match):
if field[0] >= 'a' and field[0] <= 'z' and field not in
do_not_capitalize:
field = field.capitalize()
if self.past_title:
if self.episodeName: self.episodeName += ' '
self.episodeName += field
else:
if self.programme: self.programme += ' '
self.programme += field

def parse(self):
fields = sd.split(self.name)
for field in fields:
for regex,handler in self.handlers:
match = regex.match(field)
if match:
handler(self, field, match)
break


--
Gabriel Genellina

 
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
Assignment operator self-assignment check Chris C++ 34 09-26-2006 04:26 AM
Augument assignment versus regular assignment nagy Python 36 07-20-2006 07:24 PM
assignment expression peeve Paul Rubin Python 47 10-21-2003 02:33 AM



Advertisments