
Hi all, Many languages allow assignment expressions in if conditions. (Perl, PHP, Ruby, C, etc..) I know that this was dismissed by guido because it can lead to mistakes. And i can support that. However in some situations it might be required because you have to nest if blocks and regular expressions for example:: while pos < text_length: if match = name_re.match(text, pos): pos = match.end() do_something(match) elif match = digit_re.match(text, pos): pos = match.end() do_something(match) else: pos += 1 Well. But that would require an assignment. Why not use the "as" keyword introduced in python2.5 with the future import:: while pos < text_length: if name_re.match(text, pos) as match: pos = match.end() do_something(match) elif digit_re.match(text, pos) as match: pos = match.end() do_something(match) else: pos += 1 Advantages: Still no assignment expression, no additional keyword, simple to understand. Regards, Armin

Armin Ronacher <armin.ronacher@active-4.com> wrote:
[snip] You could convert the code you have offered into the following: while pos < text_length: match = name_re.match(text, pos) or \ digit_re.match(text, pos) or \ None if match: post = match.end() do_something(match) else: pos += 1 Not only does it work today in any Python with the re module, has the same number of lines as what you provided, and doesn't repeat itself, it can be shortened with a simple helper function. -1 Also, I think it would be confusing. It is currently used in imports and the with statement, but it was *necessary* to have some assignment semantic in with statement - there is no such necessity in if/elif (or even while, which is the next step after if/elif). - Josiah

Armin Ronacher wrote:
Personally, I like it - it's an issue that I've brought up before, but your syntax is better. With the introduction of 2.5's "with A as B", and the new exception-handling syntax in Py3K 'except E as v' (and the already existing import syntax), it seems to me that we are, in fact, establishing a general rule that: <keyword> <expression> as <variable>: ...is a common syntactical pattern in Python, meaning 'do something special with expression, and then as a side effect, assign that expression to the named variable for this block." -- Talin

Talin wrote:
While I am not going to advocate it, I would like to point out that these are all just broken versions of an infix assignment operator[1]. As Josiah pointed out, they are used right now in places where explicit assignment is not possible. I don't believe you will ever successfully push such an operator through when it could easily be done explicitly. As for this particular case, it is only useful in a very restricted set of expressions and I was only able to find a handful of cases in stdlib where I could drop in a "if x as y". I believe this is an indication of how rarely one wants to do this. YMMV. -Scott [1] An infix assignment operator would be grammatically add to tests the form: test 'as' testlist. This is just a can of worms yielding pure obfuscation. -- Scott Dial scott@scottdial.com scodial@cs.indiana.edu

Scott Dial schrieb:
The existing uses for "as" are all different. The ones with "except" and "with" do not just assign the left hand expression to the right hand name, but the one with "import" does.
It does here. I can't present use cases right now, but I do recall several times where I thought this could have made the intent of the code clearer, I think I even proposed it myself some time ago. Georg

On 04/03/07, Scott Dial <scott+python-ideas@scottdial.com> wrote:
I have a concrete use case. My hobby MUD server needs to do a lot of parsing of players' input, as you'd expect. I use pyparsing to construct the grammar, but I then have the slightly hairy problem of dispatching to the correct method. Let's consider a single command, 'target', where the rest of the line can take one of three valid forms: 'set $name to word list here', 'clear $name', and 'list'. Presently, the code looks like this (minus some permission-checking complications and room sanity checks): def targetDistributor(actor, text): try: name, target = target_set_pattern.parseString(text) except ParseException: pass else: targetSet(actor, name.lower(), target) return try: name, = target_clear_pattern.parseString(text) except ParseException: pass else: targetClear(actor, name.lower()) return try: target_list_pattern.parseString(text) except ParseException: pass else: targetList(actor) return badSyntax(actor) Yuck. But, let's rewrite this using as-syntax and two new functions: #for patterns which return no useful results, but just need to match (whose results, when bool-ified, result in False) def matchnoresults(pattern, string): try: pattern.parseString(string) except ParseException: return False return True def matchwithresults(pattern, string): try: res = pattern.parseString(string) except ParseException: return False return res def targetDistributor(actor, rest, info): if matchwithresults(target_set_pattern, rest) as name, target: targetSet(actor, name, target) elif matchwithresults(target_clear_pattern, rest) as name,: targetClear(actor, name) elif matchnoresults(target_list_pattern, rest): targetList(actor) else: badSyntax(actor) I do think that the majority of use cases will be parsing, or in similar cases where one needs to both test for success and obtain results from the test.

Sam <free.condiments@gmail.com> wrote:
I'm going to agree with Scott Dial on this. You aren't going to need it often enough to warrant this syntax change. Coupled with the potential confusion that Jim Jewett pointed out, and this particular suggestion smells like a misfeature. Also, with the nonlocal declaration that is going to be making it into Python 3 (or was it 2.6?), you can use a closure without a list to do the same thing. def foo(): result = None def do_something(...): nonlocal result ... result = 8 return True if do_something(...): #do something with result ... Then again, closures, assignments in while/if, etc., all smell to me like a way of getting the features of object semantics, without actually using objects. Take your final example and use an object instead. def targetDistributor(actor, rest, info): matcher = Matcher() if matcher.match(target_set_pattern, rest): name, target = matcher.result targetSet(actor, name, target) elif matcher.match(target_clear_pattern, rest): name, = matcher.result targetClear(actor, name) elif matcher.match(target_list_pattern, rest): targetList(actor) else: badSyntax(actor) You know what? That works *today* AND is clearer and more concise than your original code. Even better, it doesn't require a language syntax change. Try using an object for this. You may find that it can do everything that the assignment thing could do. - Josiah

(apologies to Larry - the GMail interface is still confusing and arbirary) On 05/03/07, Larry Hastings <larry@hastings.org> wrote:
I could be using a different definition of 'scope' than you, but:
seems to suggest that except blocks don't set up a new scope to me.
with file('C:/foo.txt') as f: pass
print f <closed file 'C:/foo.txt', mode 'r' at 0x00B98800>
says the same thing about the with statement to me. So the variable defined with an if <expr> as <name>: statement outliving the end of the indented block would be absolutely no surprise according to the current semantics of other statements. --Sam

Sam wrote:
(What version of Python are you using, a 2.6 build? In 2.5 release, a file() is not a context manager, so 'with file() as f:' doesn't work. And in py3k the "except E, N" syntax is gone.) First off, you are absolutely right, the "with" and "except" statements do not open new scopes. I was wrong about that. But PEP 3110, "Catching Exceptions In Python:, describes "except E as N" this way: try: try_body except E as N: except_body ... gets translated to (in Python 2.5 terms) try: try_body except E, N: try: except_body finally: N = None del N ... http://www.python.org/dev/peps/pep-3110/ So the intent for "except E as N" is that N does *not* outlive the "except" block. And indeed that's what happens in my month-old Py3k. Also, in your example "with file(...) as f:", outside the "with" block "f" evaluated as "<closed file 'C:/foo.txt', mode 'r' at 0x00B98800>". The file was closed because "with file(...) as f:" wasn't simply assigning the result of the "with" expression to f; it assigns to f the result of calling __enter__() on the with with expression's result. It seems that post-Python 2.5 file.__enter__() returns the file. This isn't a bad thing, but it's a little misleading as "with A as X" doesn't usually wind up with X containing the "result" of A. So I can state: in Py3k, in both cases of "except E as N" and "with E as N", after the nested block has exited, N does not contain the direct result of E. I guess I'm starting to disagree with sprinkling "as" into the syntax as the defacto inline assignment operator. Quoting from PEP 343: So now the final hurdle was that the PEP 310 <http://www.python.org/dev/peps/pep-0310> syntax: with VAR = EXPR: BLOCK1 would be deceptive, since VAR does *not* receive the value of EXPR. Borrowing from PEP 340 <http://www.python.org/dev/peps/pep-0340>, it was an easy step to: with EXPR as VAR: BLOCK1 http://www.python.org/dev/peps/pep-0343/ Clearly the original intent with "as" was that it was specifically *not* direct assignment. Python added a *new keyword*, specifically because this was not direct inline assignment. So I assert a Python programmer should not see "as" and think "= with the two sides swapped". For my final trick, I will attempt to channel Guido. (Which is spooky, as he hasn't even Crossed Over To The Other Side.) Python was originally written in C, the poster child for inline assignment operators, so he was obviously familiar with the syntactic construct. But Python does *not* support inline assignment everywhere. AFAIK it specifically has supports for it in one place: allowing multiple assignments at once. This works: a = b = file(...) But this does not: if a = expression: # hooray, expression worked else: # boo, expression failed Since nobody likes to type, I'm sure people have requested inline assignment zillions of times before. Since Python does not support it, I'm guessing Guido doesn't want it for some reason. Perhaps it's to save programmers from the inevitable heartache of typing "if a = x" when they meant "if a == x" (or vice-versa); if so then that's a little surprising, considering the "consenting adults" design tenet, but at least "if E as N" would not have that problem. If there was some other reason, then I bet "if E as N" doesn't address it. Knock three times, /larry/

Larry Hastings wrote:
So the intent for "except E as N" is that N does *not* outlive the "except" block. And indeed that's what happens in my month-old Py3k.
That's only because the traceback is being attached to the exception, and there's a desire to avoid creating a cycle. There are indications that this idea might be dropped, in which case deleting the exception would no longer be necessary. In any case, it's still not really introducing a new scope, since if you use 'e' anywhere else in the function, it's the same variable.
I believe the main objection is that it would reintroduce the potential for accidentally writing if a = b: instead of if a == b: and having it go undetected. Using an 'as' clause would be one way of avoiding that. Using a different operator, such as if a := b: would be another way. A further possible objection is that allowing in-line assignments anywhere in any expression could lead to hard-to-follow code. That could be mitigated by only allowing them in certain places, such as the conditions of if and while statements.
Clearly the original intent with "as" was that it was specifically *not* direct assignment.
Each usage of "as" is unique. It's whatever it needs to be in each case. In general it's not direct assignment, but that doesn't mean that some of its uses couldn't be direct assignment if we wanted. -- Greg

Armin Ronacher <armin.ronacher@active-4.com> wrote:
[snip] You could convert the code you have offered into the following: while pos < text_length: match = name_re.match(text, pos) or \ digit_re.match(text, pos) or \ None if match: post = match.end() do_something(match) else: pos += 1 Not only does it work today in any Python with the re module, has the same number of lines as what you provided, and doesn't repeat itself, it can be shortened with a simple helper function. -1 Also, I think it would be confusing. It is currently used in imports and the with statement, but it was *necessary* to have some assignment semantic in with statement - there is no such necessity in if/elif (or even while, which is the next step after if/elif). - Josiah

Armin Ronacher wrote:
Personally, I like it - it's an issue that I've brought up before, but your syntax is better. With the introduction of 2.5's "with A as B", and the new exception-handling syntax in Py3K 'except E as v' (and the already existing import syntax), it seems to me that we are, in fact, establishing a general rule that: <keyword> <expression> as <variable>: ...is a common syntactical pattern in Python, meaning 'do something special with expression, and then as a side effect, assign that expression to the named variable for this block." -- Talin

Talin wrote:
While I am not going to advocate it, I would like to point out that these are all just broken versions of an infix assignment operator[1]. As Josiah pointed out, they are used right now in places where explicit assignment is not possible. I don't believe you will ever successfully push such an operator through when it could easily be done explicitly. As for this particular case, it is only useful in a very restricted set of expressions and I was only able to find a handful of cases in stdlib where I could drop in a "if x as y". I believe this is an indication of how rarely one wants to do this. YMMV. -Scott [1] An infix assignment operator would be grammatically add to tests the form: test 'as' testlist. This is just a can of worms yielding pure obfuscation. -- Scott Dial scott@scottdial.com scodial@cs.indiana.edu

Scott Dial schrieb:
The existing uses for "as" are all different. The ones with "except" and "with" do not just assign the left hand expression to the right hand name, but the one with "import" does.
It does here. I can't present use cases right now, but I do recall several times where I thought this could have made the intent of the code clearer, I think I even proposed it myself some time ago. Georg

On 04/03/07, Scott Dial <scott+python-ideas@scottdial.com> wrote:
I have a concrete use case. My hobby MUD server needs to do a lot of parsing of players' input, as you'd expect. I use pyparsing to construct the grammar, but I then have the slightly hairy problem of dispatching to the correct method. Let's consider a single command, 'target', where the rest of the line can take one of three valid forms: 'set $name to word list here', 'clear $name', and 'list'. Presently, the code looks like this (minus some permission-checking complications and room sanity checks): def targetDistributor(actor, text): try: name, target = target_set_pattern.parseString(text) except ParseException: pass else: targetSet(actor, name.lower(), target) return try: name, = target_clear_pattern.parseString(text) except ParseException: pass else: targetClear(actor, name.lower()) return try: target_list_pattern.parseString(text) except ParseException: pass else: targetList(actor) return badSyntax(actor) Yuck. But, let's rewrite this using as-syntax and two new functions: #for patterns which return no useful results, but just need to match (whose results, when bool-ified, result in False) def matchnoresults(pattern, string): try: pattern.parseString(string) except ParseException: return False return True def matchwithresults(pattern, string): try: res = pattern.parseString(string) except ParseException: return False return res def targetDistributor(actor, rest, info): if matchwithresults(target_set_pattern, rest) as name, target: targetSet(actor, name, target) elif matchwithresults(target_clear_pattern, rest) as name,: targetClear(actor, name) elif matchnoresults(target_list_pattern, rest): targetList(actor) else: badSyntax(actor) I do think that the majority of use cases will be parsing, or in similar cases where one needs to both test for success and obtain results from the test.

Sam <free.condiments@gmail.com> wrote:
I'm going to agree with Scott Dial on this. You aren't going to need it often enough to warrant this syntax change. Coupled with the potential confusion that Jim Jewett pointed out, and this particular suggestion smells like a misfeature. Also, with the nonlocal declaration that is going to be making it into Python 3 (or was it 2.6?), you can use a closure without a list to do the same thing. def foo(): result = None def do_something(...): nonlocal result ... result = 8 return True if do_something(...): #do something with result ... Then again, closures, assignments in while/if, etc., all smell to me like a way of getting the features of object semantics, without actually using objects. Take your final example and use an object instead. def targetDistributor(actor, rest, info): matcher = Matcher() if matcher.match(target_set_pattern, rest): name, target = matcher.result targetSet(actor, name, target) elif matcher.match(target_clear_pattern, rest): name, = matcher.result targetClear(actor, name) elif matcher.match(target_list_pattern, rest): targetList(actor) else: badSyntax(actor) You know what? That works *today* AND is clearer and more concise than your original code. Even better, it doesn't require a language syntax change. Try using an object for this. You may find that it can do everything that the assignment thing could do. - Josiah

(apologies to Larry - the GMail interface is still confusing and arbirary) On 05/03/07, Larry Hastings <larry@hastings.org> wrote:
I could be using a different definition of 'scope' than you, but:
seems to suggest that except blocks don't set up a new scope to me.
with file('C:/foo.txt') as f: pass
print f <closed file 'C:/foo.txt', mode 'r' at 0x00B98800>
says the same thing about the with statement to me. So the variable defined with an if <expr> as <name>: statement outliving the end of the indented block would be absolutely no surprise according to the current semantics of other statements. --Sam

Sam wrote:
(What version of Python are you using, a 2.6 build? In 2.5 release, a file() is not a context manager, so 'with file() as f:' doesn't work. And in py3k the "except E, N" syntax is gone.) First off, you are absolutely right, the "with" and "except" statements do not open new scopes. I was wrong about that. But PEP 3110, "Catching Exceptions In Python:, describes "except E as N" this way: try: try_body except E as N: except_body ... gets translated to (in Python 2.5 terms) try: try_body except E, N: try: except_body finally: N = None del N ... http://www.python.org/dev/peps/pep-3110/ So the intent for "except E as N" is that N does *not* outlive the "except" block. And indeed that's what happens in my month-old Py3k. Also, in your example "with file(...) as f:", outside the "with" block "f" evaluated as "<closed file 'C:/foo.txt', mode 'r' at 0x00B98800>". The file was closed because "with file(...) as f:" wasn't simply assigning the result of the "with" expression to f; it assigns to f the result of calling __enter__() on the with with expression's result. It seems that post-Python 2.5 file.__enter__() returns the file. This isn't a bad thing, but it's a little misleading as "with A as X" doesn't usually wind up with X containing the "result" of A. So I can state: in Py3k, in both cases of "except E as N" and "with E as N", after the nested block has exited, N does not contain the direct result of E. I guess I'm starting to disagree with sprinkling "as" into the syntax as the defacto inline assignment operator. Quoting from PEP 343: So now the final hurdle was that the PEP 310 <http://www.python.org/dev/peps/pep-0310> syntax: with VAR = EXPR: BLOCK1 would be deceptive, since VAR does *not* receive the value of EXPR. Borrowing from PEP 340 <http://www.python.org/dev/peps/pep-0340>, it was an easy step to: with EXPR as VAR: BLOCK1 http://www.python.org/dev/peps/pep-0343/ Clearly the original intent with "as" was that it was specifically *not* direct assignment. Python added a *new keyword*, specifically because this was not direct inline assignment. So I assert a Python programmer should not see "as" and think "= with the two sides swapped". For my final trick, I will attempt to channel Guido. (Which is spooky, as he hasn't even Crossed Over To The Other Side.) Python was originally written in C, the poster child for inline assignment operators, so he was obviously familiar with the syntactic construct. But Python does *not* support inline assignment everywhere. AFAIK it specifically has supports for it in one place: allowing multiple assignments at once. This works: a = b = file(...) But this does not: if a = expression: # hooray, expression worked else: # boo, expression failed Since nobody likes to type, I'm sure people have requested inline assignment zillions of times before. Since Python does not support it, I'm guessing Guido doesn't want it for some reason. Perhaps it's to save programmers from the inevitable heartache of typing "if a = x" when they meant "if a == x" (or vice-versa); if so then that's a little surprising, considering the "consenting adults" design tenet, but at least "if E as N" would not have that problem. If there was some other reason, then I bet "if E as N" doesn't address it. Knock three times, /larry/

Larry Hastings wrote:
So the intent for "except E as N" is that N does *not* outlive the "except" block. And indeed that's what happens in my month-old Py3k.
That's only because the traceback is being attached to the exception, and there's a desire to avoid creating a cycle. There are indications that this idea might be dropped, in which case deleting the exception would no longer be necessary. In any case, it's still not really introducing a new scope, since if you use 'e' anywhere else in the function, it's the same variable.
I believe the main objection is that it would reintroduce the potential for accidentally writing if a = b: instead of if a == b: and having it go undetected. Using an 'as' clause would be one way of avoiding that. Using a different operator, such as if a := b: would be another way. A further possible objection is that allowing in-line assignments anywhere in any expression could lead to hard-to-follow code. That could be mitigated by only allowing them in certain places, such as the conditions of if and while statements.
Clearly the original intent with "as" was that it was specifically *not* direct assignment.
Each usage of "as" is unique. It's whatever it needs to be in each case. In general it's not direct assignment, but that doesn't mean that some of its uses couldn't be direct assignment if we wanted. -- Greg
participants (8)
-
Armin Ronacher
-
Georg Brandl
-
Greg Ewing
-
Josiah Carlson
-
Larry Hastings
-
Sam
-
Scott Dial
-
Talin