Return value of an assignment statement?

C

Carl Banks

I don't understand your reasoning. If assignment operators are so
terrible, why do you think the terribleness disappears in this specific
case?

I don't.

The assignment operator is terrible but the idiom itself isn't.


[snip narrowly applicable counterexamples that don't really mean
anything because they're too narrow]
Perl's treatment of assignment as an operator tempts the programmer to
write quick-and-dirty code.

(The perl example wasn't using an assignment operator.)
Python discourages that sort of behaviour,
and encourages programmers to factor their code in a structured way.
That's a feature, not a bug.

The set-and-test idiom is not necessarily quick and dirty, and for
many things it's more readable and a lot easier to follow than any of
indirect methods that are proposed.

The fact that sometimes an assignment operator is used to do this
doesn't change the usefulness of the idiom itself.

It can be done without assignment expressions. I gave a hypothetical
syntax for how it might be done in Python without them.

I can't help but to think that a lot of people's distaste for this
natural way to write certain logic is simply defensiveness about one
minor little thing that Python doesn't support (without workarounds).


Carl Banks
 
S

Steve Holden

Marc said:
Exactly! Names aren't variables. The unit of a name, an address, and a
value are a variable.


What is a binding when it's not an association between a name and an
object!? So names are associated with objects. There are no names
without objects in Python. If a name is not bound to any object, how could
the name exist? That would be like a dangling pointer, a beast that
doesn't exists in Python.

<nitpick>Okay there are local names that are known and therefore somehow
"exist" before they get bound, but that's IMHO an implementation


It has. You just can't substitute the term "name" with "variable" and
expect it to behave like in C. A variable is not just the name but also
the value and the storage space and how those are connected.
"Does" ... "Doesn't" ... "Does so!".

You guys are merely arguing about what you want to call the Python
assignment semantics you both understand perfectly well. This isn't
going to help anyone.

The fact of the matter is that when a Python name is bound to a value
the value is normally created in heap storage (with a few exceptions
like the pre-allocated objects such as None and the small integers, but
*never* directly in the namespace in which the name is being bound), and
the name is associated with a reference to the value.

I've said before that Python names are very similar to automatically
dereferenced pointer variables, and I suppose the same old arguments
will be trotted out against that utterance now I've said it again.

But for the purposes of comprehension, particularly by C and C++
programmers who haven't come across this particular semantic before it
should server to aid comprehension. The fact that objects exist
independent of the dynamically created scopes of function calls and the
like is precisely what stops Python from suffering the same out-of-scope
(dangling) pointer issues that C++ is famous for.

The fact remains that name binding in Python (and binding to container
items too) doesn't "return a value", and bindings were deliberately not
allowed as a term in a broader expression to avoid some common
programming errors.

regards
Steve
 
S

Steve Holden

Carl said:
The syntax is the way it is precisely to discourage that kind of clever
idea.

Don't be ridiculous. Assignment operators are maybe one of the worst
things in existence, but this particular use case (running a sequence
of tests like the above) is perfectly useful and good.

Some Pythonistas will swear to their grave and back that should be
done by factoring out the tests into a list and iterating over it, and
NO OTHER WAY WHATSOEVER, but I don't buy it. That's a lot of
boilerplate--the very thing Python is normally so good at minimizing--
when it might not be needed. It would be the right thing for a
complex, pluggable, customizable input filter; but is rarely a better
solution for a simple text processing script.

Quick, at a glance, which code snippet will you understand faster
(pretend you know Perl):


if (/name=(.*)/) {
$name = chop(\1);
} elsif (/id=(.*)/) {
$id = chop(\1);
} elsif (/phone=(.*)/) {
$phone = chop(\1);
}


vs.


def set_phone_number(m):
phone = m.group(1).strip()

def set_id(m):
id = m.group(1).strip()

def set_name(m):
name = m.group(1).strip()

_line_tests = [
(r"phone=(.*)", set_phone_number),
(r"name=(.*)", set_name),
(r"id=(.*)", set_id),
]

for pattern,func in _line_tests:
m = re.match(pattern,line)
if m:
func(m)


At this small scale, and probably at much larger scales too, the Perl
example blows the Python example out of the water in terms of
readability. And that's counting Perl's inherent unreadableness.
I'm supposed to overlook the fact that your example in Python omits the
"untested" it clearly deserves, I take it? I'm not sure what you are
trying to do with the assignments inside the function body.

The brevity of the Perl has something to commend it, but I am always
suspicious about whether algorithms like that should really be data
driven. It's all too easy to add further tests as new field
possibilities are added. It's also unpleasant in that it leaves two
variables in an undetermined state.

Let's assume that your Python functions were correctly assigning to
attributes of some object that was being passed in or global, at least
then it would be possible to add an else condition to each iteration to
set the attribute's default value somehow.

So I think your example is perhaps not the best one you could have
chosen to make your case.

I will admit that idiomatic usages are acceptable ways to perform common
tasks, but I still think that Guido's decision to eschew assignments as
expression terms is a sound one, and one that encourages better program
construction.

Hey, call me (or my assertions) ridiculous if you want. It remains that
allowing such terms will inevitably lead to hard-to-debug confusion
between assignment and equality testing once the difference becomes a
single equals sign.
If it were a priority, Python could support this set-and-test idiom,
and without an assignment operator. (Notice Perl doesn't use
assignment operator here.) For example, a syntax like this (where the
scope of m is local to the if-condition and the body of the if-
statement:

if m where m = re.match(r"name=(.*)",line):
name = m.group(1).strip()
elif m where m = re.match(r"id=(.*)",line):
id = m.group(1).strip()
elif m where m = re.match(r"phone=(.*)",line):
phone = m.group(1).strip()
[I'll presume you've already established default values for name, id and
phone just to quiet the alarms ringing in the background]. Still looks
to me like it might be better data-driven with a setattr() in there
somewhere. As far as I can see all this would achieve would be to limit
the scope of the assignment, and I don't really see what advantage that
provides.
This won't happen because the set-and-test idiom is relatively minor
and not deemed worthy of syntax support. But if it were there,
there'd really be nothing clever about it.
Note also that I'm not saying an experienced programmer can't get these
things right. But at any given time half the programmers in the world
are newbies, and Python tries to help them by steering them in safer
directions.

Maybe we could allow it if you had a digital certificate asserting that
you'd passed your metaclass abuse test ... <0.75 wink>

regards
Steve
 
S

Steven D'Aprano

(The perl example wasn't using an assignment operator.)

Hmmm... I see. Obviously I didn't pretend to understand Perl well enough.

(I assume you're ignoring the assignments $name = chop(\1) etc. Fair
enough.)


[...]
I can't help but to think that a lot of people's distaste for this
natural way to write certain logic is simply defensiveness about one
minor little thing that Python doesn't support (without workarounds).

But Python certainly does support set-and-test. You just have to separate
the set from the test with a newline:

m = re.match(r"name=(.*)",line) # set
if m: # test
name = m.group(1).strip()


This does the same thing as your proposed syntax

if m where m = re.match(r"name=(.*)",line):
name = m.group(1).strip()

except that it doesn't create a new scope. I'm not sure that the benefit
of having a new scope is worth the new keyword. Maybe it is, maybe it
isn't.

I think I have a better idea of what you are trying to say. Despite first
impressions, you weren't defending the proposed "assign-and-test" idiom
suggested by Stephen Gross:

pat = re.compile('some pattern')
if m = pat.match(some_string): # doesn't work in Python
do_something(m)

on account of it needing an assignment expression, which is Bad. But you
were defending the principle of set-and-test, if we can use something
other than an assignment expression to do the set.

E.g. Perl's magic syntax "if /pattern/ { }" (everything in Perl is magic
syntax), or your proposed "if m where m = expression".

Okay, I can agree with that, particularly since Python already supports
it using the plain old boring, old fashioned idiom of "assign, then test".
 
A

Aahz

FWIW, it's IMHO a real wart - given Python's pretention at readability -
that augmented assignement has been implemented that way for lists.

This was debated extensively when augmented assignment was created, and
it was decided that practicality beat purity. It's just too convenient
to be able to write

L += ['foo']

without rebinding L.
 
A

Aahz

So what is the "variable?" Or is Python the first HLL I've ever heard
of that didn't have variables?

Whether Python has "variables" depends on your perspective. Python
certainly does *not* have variables with anything like the semantics of
C/C++ variables. For that reason, it's often convenient to shift the
vocabulary to avoid misunderstading. However, the vast majority of
Python programmers do use "variable" in casual conversation (I certainly
do); it's only when trying to discuss the Python object model that
there's a strong tendency to switch to using "names".
 
J

Jeff Schwab

George said:
"Not an operator?" Then what is it?

In this context, it's just the token used for the assignment
statement. In short, if 'a' is an identifier, the statement means
"bind the name 'a' to the object 'b' (in the local or global
namespace)". It doesn't say anything about memory allocation,
initialization or copying. The only case where assigning an identifier
affects memory is the following [1]:

"""
The name is rebound if it was already bound. This may cause the
reference count for the object previously bound to the name to reach
zero, causing the object to be deallocated and its destructor (if it
has one) to be called.
"""

[1] http://docs.python.org/ref/assignment.html

OK, thanks for trying to make it clear.

I'm about through with this discussion, but FWIW, this is a real gotcha
for me and many others. This is a case where Python does not do what
many programmers expect, and it at least takes some getting used-to.
 
S

Steven D'Aprano

Bruno Desthuilliers said:
FWIW, it's IMHO a real wart - given Python's pretention at readability -
that augmented assignement has been implemented that way for lists.

This was debated extensively when augmented assignment was created, and
it was decided that practicality beat purity. It's just too convenient
to be able to write

L += ['foo']

without rebinding L.


*scratches head*

Wouldn't L.append('foo') or L.extend(['foo']) be even more convenient,
and have perfectly obvious behaviour without leading to the confusion of
augmented assignments?

Personally, I think the confusion of augmented assignments is not worth
the benefit of saving typing a couple of characters. I think Guido's
first decision, to leave += etc out of the language, was the right
decision.
 
P

Paul Rubin

Steven D'Aprano said:
Personally, I think the confusion of augmented assignments is not worth
the benefit of saving typing a couple of characters. I think Guido's
first decision, to leave += etc out of the language, was the right
decision.

It quite helpful to be able to say

foo['bar'+lookup(baz)][blob(a)+frob(b)] += 1

without having to split it into separate statements to avoid repeating
the function calls and their possible side effects.
 
G

George Sakkis

(The perl example wasn't using an assignment operator.)

Hmmm... I see. Obviously I didn't pretend to understand Perl well enough.

(I assume you're ignoring the assignments $name = chop(\1) etc. Fair
enough.)

[...]
I can't help but to think that a lot of people's distaste for this
natural way to write certain logic is simply defensiveness about one
minor little thing that Python doesn't support (without workarounds).

But Python certainly does support set-and-test. You just have
to separate the set from the test with a newline:

A single "set-and-test" operation is not equivalent to two consecutive
operations, "set" and "test".
m = re.match(r"name=(.*)",line) # set
if m: # test
name = m.group(1).strip()

For a single set-and-test the inconvenience is minimal, but stack a
bunch of them together (even more if there are 'else' clauses in the
mix) and the syntactic inefficiency becomes quite visible.

George
 
D

Dennis Lee Bieber

I'm about through with this discussion, but FWIW, this is a real gotcha
for me and many others. This is a case where Python does not do what
many programmers expect, and it at least takes some getting used-to.

As opposed to the twice monthly shocked newbie discovering that a
mutable as a function default doesn't reset on the next invocation?

In that aspect, it all comes down to the difference between mutables
and immutables in Python.
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
M

Marc 'BlackJack' Rintsch

It's just too convenient to be able to write

L += ['foo']

without rebinding L.

<nitpick>But ``+=`` does rebind.</nitpick>

Doesn't matter in this case but we've had confused programmers asking
questions here when `L` is a class attribute and it's rebound to the
instance, or if they tried it on a list in a tuple. Extending a list
that's a read only property doesn't work either.

Ciao,
Marc 'BlackJack' Rintsch
 
B

Bob Martin

in 335100 20080222 123210 Steven D'Aprano said:
But Python objects don't have names, so by your own definition, they
aren't variables. Names are associated with namespaces, not objects. A
name must have one and only one object bound to it at any one time;
objects on the other hand can be bound to one name, or no name, or a
thousand names. The object itself has no way of knowing what names it is
bound to, if any.

Or, to put it another way... Python doesn't have variables.

In that case neither does any other OO language.
 
P

Paul Rudin

Steven D'Aprano said:
But Python objects don't have names, so by your own definition, they
aren't variables. Names are associated with namespaces, not objects. A
name must have one and only one object bound to it at any one time;
objects on the other hand can be bound to one name, or no name, or a
thousand names. The object itself has no way of knowing what names it is
bound to, if any.

Or, to put it another way... Python doesn't have variables.

Of course it all depends on how you choose to define "variable". Every
programming language has slightly different semantics for
variables. It would be perverse to describe something as a variable if
it lacked any similarities with such in other programming languages,
but I think it is misleading to say "python doesn't have variables".
 
J

Jeff Schwab

Dennis said:
As opposed to the twice monthly shocked newbie discovering that a
mutable as a function default doesn't reset on the next invocation?

In that aspect, it all comes down to the difference between mutables
and immutables in Python.

You know what's illuminating the discussion? Everybody thinks they
understand this issue, but the explanations are contradictory. It seems
like half the folks think this is an issue of mutability vs.
immutability, and the other half believe that has nothing to do with it.
 
S

Steve Holden

Paul said:
Steven D'Aprano said:
Personally, I think the confusion of augmented assignments is not worth
the benefit of saving typing a couple of characters. I think Guido's
first decision, to leave += etc out of the language, was the right
decision.

It quite helpful to be able to say

foo['bar'+lookup(baz)][blob(a)+frob(b)] += 1

without having to split it into separate statements to avoid repeating
the function calls and their possible side effects.

And that was the reason for eventually including them. I remember being
very surprised when I learned that rebinding was possible at the option
of the implementing object, but of course rebinding is inevitable when
you have immutable objects that implement augmented assignments.

regards
Steve
 
J

Jeff Schwab

What you want is:

for astring, afunc in ((some_string, do_something), (other_string,
do_other_thing)):
m = pat.match(astring)
if m:
afunc(m)
break
else:
do_default_thing()

That looks like the first realistic alternative I've seen. I find the
flow a little hard to follow, but I think that's mostly just because I'm
not accustomed to the syntax.

Your approach fits in my head a little more comfortably if none of the
lines are longer than eighty columns, if the for-loop isn't given an
else-clause (which still looks to my untrained eye like it should match
the preceding if), and if the break-statement is replaced with a
return-statement:

actions = (
('some_string', do_something),
('other_string', do_other_thing))

def find_action(pattern):
for string, action in actions:
m = pattern.match(string)
if m:
return action
return do_default_thing

find_action(re.compile('some pattern'))()
 
J

Jeff Schwab

Jeff said:
Yes, but there is valid syntax for the common case you mentioned:

y = x = 3

What you can't do (that I really miss) is have a tree of assign-and-test
expressions:

import re
pat = re.compile('some pattern')

if m = pat.match(some_string):
do_something(m)
else if m = pat.match(other_string):
do_other_thing(m)
else:
do_default_thing()


This is apparently section 1.9 of the Python Cookbook:

http://www.oreilly.com/catalog/pythoncook2/toc.html

Martelli suggests something similar to the "thigamabob" technique I use
(he calls it DataHolder). It's really more like the "xmatch" posted by
Paul Rubin.

Martelli also says, though, that if you need this, you're not thinking
Pythonically. I don't know what the Pythonic alternative is. The
iterating-over-pairs approach suggested by Bruno is the only alternative
I've seen.
 
P

Paddy

What you want is:

for astring, afunc in ((some_string, do_something), (other_string,
do_other_thing)):
m = pat.match(astring)
if m:
afunc(m)
break
else:
do_default_thing()

The Bruno transform? :)
 
A

Arnaud Delobelle

     actions = (
             ('some_string', do_something),
             ('other_string', do_other_thing))

     def find_action(pattern):
         for string, action in actions:
             m = pattern.match(string)
             if m:
                 return action
         return do_default_thing

     find_action(re.compile('some pattern'))()

You don't need to pass the pattern, just pass the match function:

def find_action(match, actions=actions, default_action=None):
for string, action in actions:
if match(string):
return action
return default_action

find_action(re.compile('some pattern').match)()
 

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
474,432
Messages
2,571,681
Members
48,796
Latest member
Greg L.

Latest Threads

Top