Return value of an assignment statement?

J

Jeff Schwab

Steve said:
The syntax is the way it is precisely to discourage that kind of clever
idea. Of course, people nevertheless manage to work around the
restriction to try and make their Python read like some other language
they are more familiar with, and most of the time they get away with it.

The fat remains that in programming there *is* such a thing as being too
clever, and Python's syntax deliberately discourages that.

This isn't "clever." It's what most of us were taught, from the
beginning, was best practice. In some languages (including C and C++)
the above is extremely common and perfectly acceptable.
 
G

George Sakkis

Steve said:
Jeff said:
(e-mail address removed) wrote: [...]
Now there's no reason to feel nervous about this. All you have to
remember is that Python never copy anything unless explicitely asked
for.
It's not that simple. After a statement like:
a = b
Whether a and b denote the same object depends on what kind of object
b represented in the first place.
Surely this is exactly wrong. Is there a single example you can think of
where

a += b (my bad)
assert a is b, "Oops!"
would raise and exception? Perhaps you meant to use an augmented
assignment operation?

Why, yes I did! Sorry about that.

It seems less surprising when you keep in mind that "+=" (and friends)
can be syntax sugar for calling a method on the right hand side
object: a += b <=> a.__iadd__(b). It's up to the class of 'a' to do
whatever within this method (whether it's a good idea to do anything
else other than modify 'self' in place is another thing). Would you be
able to say anything about a.foo(b) without knowing what 'a' is ?

The only difference is that for types that don't implement an
augmented operator, "a `op`= b" translates to "a = a `op` b" for a
binary operator `op`. There's no formal notion of mutable and
immutable objects with respect to these operators; any class that
doesn't implement them is "immutable" as far as augmented assignment
goes (of course it may be mutated in other ways, e.g. by fiddling with
a.__dict__ directly).

On the other hand, "a = b" does always the same thing; unlike C++, '='
is not an operator and therefore it cannot be overriden by the class
of 'a'.

George
 
A

Aahz

Thank you for the clarification. For some reason, I had it in my head
that ints were packed directly into the C structures that represent
Python variables, in the same (union?) member that otherwise would store
a pointer.

Notice very very carefully that Bruno is not using "variable". Many
expert Python programmers strongly prefer to talk about "names" instead
of "variables" (especially when explaining the Python object model)
precisely because using "variable" leads to incorrect expectations.

http://starship.python.net/crew/mwh/hacks/objectthink.html
 
J

Jeff Schwab

George said:
Steve said:
Jeff Schwab wrote:
(e-mail address removed) wrote:
[...]
Now there's no reason to feel nervous about this. All you have to
remember is that Python never copy anything unless explicitely asked
for.
It's not that simple. After a statement like:
a = b
Whether a and b denote the same object depends on what kind of object
b represented in the first place.
Surely this is exactly wrong. Is there a single example you can think of
where
a = b
a += b (my bad)
assert a is b, "Oops!"
would raise and exception? Perhaps you meant to use an augmented
assignment operation?
Why, yes I did! Sorry about that.

It seems less surprising when you keep in mind that "+=" (and friends)
can be syntax sugar for calling a method on the right hand side
object: a += b <=> a.__iadd__(b). It's up to the class of 'a' to do
whatever within this method (whether it's a good idea to do anything
else other than modify 'self' in place is another thing). Would you be
able to say anything about a.foo(b) without knowing what 'a' is ?

Yes: I would know that it didn't rebind a.

The only difference is that for types that don't implement an
augmented operator, "a `op`= b" translates to "a = a `op` b" for a
binary operator `op`. There's no formal notion of mutable and
immutable objects with respect to these operators; any class that
doesn't implement them is "immutable" as far as augmented assignment
goes (of course it may be mutated in other ways, e.g. by fiddling with
a.__dict__ directly).

Thanks for explaining that.

On the other hand, "a = b" does always the same thing; unlike C++, '='
is not an operator and therefore it cannot be overriden by the class
of 'a'.

"Not an operator?" Then what is it?
 
J

Jeff Schwab

Aahz said:
Notice very very carefully that Bruno is not using "variable". Many
expert Python programmers strongly prefer to talk about "names" instead
of "variables" (especially when explaining the Python object model)
precisely because using "variable" leads to incorrect expectations.

http://starship.python.net/crew/mwh/hacks/objectthink.html

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

Paul Rubin

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

I don't know what other HLL's you use, but some languages don't even
have mutable values.
 
T

Torsten Bronger

Hallöchen!

Jeff said:
Aahz said:
[...]

Notice very very carefully that Bruno is not using "variable".
Many expert Python programmers strongly prefer to talk about
"names" instead of "variables" (especially when explaining the
Python object model) precisely because using "variable" leads to
incorrect expectations.

http://starship.python.net/crew/mwh/hacks/objectthink.html

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

Since Python objects, names, their operations, and the syntax used
for it resemble very closely (but not completely) what is called
"variable" in other languages, Python names are mostly called
"variables", too.

But there is this one thing of changing mutable objects which may
also change the "value" of other "variables". This can only be
understood if your nomenclature is strictly correct.

I find Python's model is this area great. It makes many things
simpler and only one or two things more complicated. I had my bad
experiences with it, too (e.g. initialising two lists with
"x_values=y_values=[]"). But only once per year I stumble over it.

Tschö,
Torsten.
 
G

George Sakkis

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

HTH,
George

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

Ben Finney

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

I don't know what HLLs you've heard of. I would bet that some of them
are languages which don't have "variables" *with all that the term
implies to most programmers*.

You don't have to go very far to find Python programmers using the
term "variable". What Aahz is pointing out is that the concepts that
people sloppily refer to by the term "variable" don't behave in Python
the way a programmer might expect who has used that term in reference
to concepts native to other languages.

Hence the term requires careful definition, and it's often best simply
not to use it and use the better analogy of "names" and "binding"
instead.
 
M

Marc 'BlackJack' Rintsch

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

Relax, Python has variables. It's just sometimes a good advice for people
coming from languages like C to forget about that term for a while because
they have the wrong impression of what "variable" means. A "variable" in
programming languages is composed of a name, a memory location, possibly a
type and a value. In C-like languages, where you put values in named and
typed "boxes", the memory location and type are attached to the name. In
Python both belong to the value.

Ciao,
Marc 'BlackJack' Rintsch
 
C

Carl Banks

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.

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


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.


Carl Banks
 
C

Carl Banks

Yep, this is exactly what I am (was) trying to do. Oh well.... Any
clever ideas on this front?


Yeah, I got something clever. I don't recommend using it, biut


def call_if_true(cond):
if cond:
return lambda func: func(cond)
return lambda func: None


@call_if_true(re.match(r"name=(.*)"))
def set_name(m):
name = m.group(1).strip()

@call_if_true(re.match(r"id=(.*)"))
def set_id(m):
id = m.group(1).strip()

@call_if_true(re.match(r"phone=(.*)"))
def set_phone(m):
phone = m.group(1).strip()



This decorator might be more to the point:

def call_if_match(regexp):
m = re.match(regexp)
if m:
return lambda func: func(m)
return lambda func: None



These have the drawback of being a little dense, and the functions
can't rebind variables in the caller's local namespace, but it is
clever.

P.S. I don't recommend using it.


Carl Banks
 
D

Duncan Booth

Jeff Schwab said:
a += b

Whether a refers to the same object before and after that statement
depends on what type of object it referred to before the statement.
Yes but the rule followed by the builtin types is pretty simple: if 'a' can
still refer to the same object then it does.

Immutable objects make this impossible most of the time (unless 'b' is 0,
(), or similar), and there may be other exceptions I've missed, but this is
the general principle, so mutable objects will mutate and immutable objects
won't.

Whether the same rule is followed outside the core is, of course, up to the
individual developers.
 
P

Paul Rubin

Carl Banks said:
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.

I've been known to do stuff like:

class xmatch:
def set(self, v):
self.v = v
return v

then:

s = xmatch()

if s.set(pat.match(some_string)):
do_something(s.v)
elif s.set(pat.match(other_string)):
do_other_thing(s.v)
...
 
D

Duncan Booth

Carl Banks said:
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.

I'll swear to my grave that there is always a better way than a lot of
nested regex conditions, and that may or may not involve a list but
there are plenty of other ways. That's why this is such a hard question
to answer definitively: every situatiuon has a different answer.
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);
}

I get a headache with that: somehow I have to either magically know
which variable out of name, id and phone exists or I have to set all 3
variables with suitable defaults for the unset ones. Perl I believe will
allow access to unset variables but Python doesn't, so it looks like
I'll have a bunch of extra code to make sure they all get set.

PATTERN = re.compile('(?:name=(.*))|(?:id=(.*))|(?:phone=(.*))')
....
m = PATTERN.match(argument)
if not m:
raise FormatError('bad input: %s' % argument)
name, id, phone = m.groups()

oops, not so much extra code to set them after all. Actually in practice
I'd probably use something more like:

PATTERN = re.compile('''(?:name=(?P<name>.*))
|(?:id=(?P<id>.*))
|(?:phone=(?P<phone>.*))''', re.VERBOSE)
....
m = PATTERN.match(argument)
if not m:
raise FormatError('bad input: %s' % argument)
values = m.groupdict()

as I find named groups much less error prone.
 
B

Bruno Desthuilliers

Jeff Schwab a écrit :
(e-mail address removed) wrote: (snip)
Explicitely using list.extend would make things clearer:

def invoke_some_fct(parent):
parent.x.extend(['world'])

Whether you use += or extend has nothing to do with it.

Mmm... Really ?
You omitted the
relevant part. Using extend, it would look like:

y = parent.x
y.extend(['world'])

The confusing part is that performing an operation on y may or may not
alter parent.x, depending on whether the initial type of parent.x is
immutable.

given that Python doesn't copy anything unless explicitelly asked for, the

y = parent.x

statement has the very clear semantic of making y an alias of parent.x.
Mutability has nothing to do with it, except for the fact that if
parent.x is immutable there's indeed no way to mutate it.
If parent.x is immutable, y is a copy of the value
represented by parent.x,

No, by no way.
True

y is *not* "a copy of x", it is another name bound to the very same object.
and modifying y has not effect on the value of
parent.x.

Your problem is with the semantic of "modifying". In Python, (re)binding
a name and mutating an object are two very distinct things.

If (the object referenced by) y is immutable, you *can not* modify (=>
mutate) it. Period.

And if you *rebind* y, this *won't* affect p.x, whether it's mutable or not:
>>> p = Parent()
>>> p.x = ['allo']
>>> y = p.x
>>> y is p.x True
>>> y = ['la terre']
>>> y ['la terre']
>>> p.x ['allo']
>>> y is p.x False
>>>


IOW - and while, you're right about this, I skipped the part that
trouble you, that is the aliasing of parent.x -, the observation that
using list.extend (that is, clearly a mutator method call) instead of
augmented assignment which *looks like* it's rebinding y (and would
effectively rebind it if y was immutable).

FWIW, it's IMHO a real wart - given Python's pretention at readability -
that augmented assignement has been implemented that way for lists.
If (OTOH) parent.x is mutable, then x and y are really
references to the same object, and modifications to that object via y
can be observed via x. In C, you use pointers to get this effect.

Not quite the same thing. C variables are boxes (containing values),
with pointer's values being the coords of another box, while Python's
'variables' are only labels on objects - they *never* 'contains'
anything. (FWIW, someone recently posted a link to a very good
explanation of this, but I don't have it under hand right now - could
someone help ?)

With pointers, a function can modify the content of a box defined in the
caller's scope. This is not something you can do in Python - that is,
rebinding a formal parameter to a different object withing a function
won't affect the bindings in the caller's scope:

def rebinder(x):
print "before: x = %s (id: %s)" % (x, id(x))
x = ['bar']
print "after: x = %s (id: %s)" % (x, id(x))


def caller():
a = ['foo']
print "before: a = %s (id: %s)" % (a, id(a))
rebinder(a)
print "after: a = %s (id: %s)" % (a, id(a))

caller()

It's not that simple. After a statement like:

a = b

Whether a and b denote the same object depends on what kind of object b
represented in the first place.

No. You can bet your life on this : after this statement, a and b are
two labels for the very same object, *always*, *whatever* the type of b.

Now this is a very frequent cause of confusion for C/C++ programmers,
and it has been explained again and again here - usually far better than
I just did, so you may want to google for this.

HTH
 
S

Steven D'Aprano

Steve Holden wrote: [...]
The syntax is the way it is precisely to discourage that kind of clever
idea. Of course, people nevertheless manage to work around the
restriction to try and make their Python read like some other language
they are more familiar with, and most of the time they get away with
it.

The fat remains that in programming there *is* such a thing as being
too clever, and Python's syntax deliberately discourages that.

This isn't "clever." It's what most of us were taught, from the
beginning, was best practice. In some languages (including C and C++)
the above is extremely common and perfectly acceptable.


Speak for yourself. *I* was never taught to abuse assignment by making it
an expression, and in the languages I have used, it isn't.

I'm sorry that your programming ability has been permanently harmed by
learning C at too early an age, but please don't imagine the rest of us
suffer from this disability. *wink*
 
S

Steven D'Aprano

A "variable" in
programming languages is composed of a name, a memory location, possibly
a type and a value. In C-like languages, where you put values in named
and typed "boxes", the memory location and type are attached to the
name. In Python both belong to the value.

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

Steven D'Aprano

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.

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

The above idiom leads to one of the most common errors in C code: writing
= when you mean ==. "Running a sequence of tests" isn't immune to that
problem, it's especially vulnerable to it.

Compare the suggested pseudo-Python code:

pat = re.compile('some pattern')
if m = pat.match(some_string):
do_something(m)


with the actual Python code:

pat = re.compile('some pattern')
m = pat.match(some_string)
if m:
do_something(m)


The difference is exactly one newline plus one extra reference to the
name "m". And this is a problem?


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.

Well, putting a sequence of tests into a list is the natural way to deal
with a sequence of tests. What else would you do?

That's a lot of boilerplate

What boilerplate are you talking about?


--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.
Huh?


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.


Why would you do that test in such an overblown fashion, then try to
pretend it is an apples-and-apples comparison with the Perl code? It
doesn't even work: you have three functions that set a local name, then
throw it away when they return.

Pretending I understand Perl, here's a fairer, more direct translation of
the Perl code:


name, id, phone = [None]*3 # Closest thing to an unset variable in Perl.
name = re.match(r"name=(.*)", line)
if name: name = name.group(1).strip()
else:
id = re.match(r"id=(.*)", line)
if id: id = id.group(1).strip()
else:
phone = re.match(r"phone=(.*)", line)
if phone: phone = phone.group(1).strip()

Six lines for Perl against nine for Python, eight if you dump the "unset"
line. Hardly a big difference.

The main difference is that Python's handling of regexes is a little more
verbose, and that the indentation is compulsory. But here's a better way
to do the same test:

tests = [ (r"name=(.*)", 'name'),
(r"id=(.*)", 'id'), (r"phone=(.*)", 'phone')]
for (test, name) in tests:
m = re.match(t, line)
if m:
globals()[name] = m.group(1).strip()
break

Down to seven lines, or six if the I didn't split the tests over two
lines.



Here's an even better way:

tests = [ "name", "id", "phone"]
for t in tests:
m = re.match(t + r"=(.*)", line)
if m:
globals()[t] = m.group(1).strip()
break

Six lines for Perl, six for Python, and the Python version is far more
readable.

Perl's treatment of assignment as an operator tempts the programmer to
write quick-and-dirty code. Python discourages that sort of behaviour,
and encourages programmers to factor their code in a structured way.
That's a feature, not a bug.
 
M

Marc 'BlackJack' Rintsch

But Python objects don't have names, so by your own definition, they
aren't variables.

Exactly! Names aren't variables. The unit of a name, an address, and a
value are a variable.
Names are associated with namespaces, not objects. A name must have one
and only one object bound to it at any one time;

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
detail. said:
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.

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.

Ciao,
Marc 'BlackJack' Rintsch
 

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,772
Messages
2,569,593
Members
45,108
Latest member
AlbertEste
Top