strange behavor....

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Sat Nov 13 17:32:39 EST 2010


On Sat, 13 Nov 2010 20:01:42 +0000, Mark Wooding wrote:

> Terry Reedy <tjreedy at udel.edu> writes:
> 
>> On 11/13/2010 11:29 AM, Mark Wooding wrote:
>>
>> > Alas, Python is actually slightly confusing here, since the same
>> > notation `=' sometimes means assignment and sometimes means mutation.
>>
>> I disagree somewhat. An object is mutated by an internal assignment.
> 
> Some object types are primitive, provided by the runtime system; there
> are no `internal' variables to be assigned in these cases.

You seem to be making up your own terminology here, or at least using 
terminology that isn't normally used in the languages I'm used to. In OO 
languages such as Python, Java, and (I believe) Ruby, it is conventional 
to distinguish between objects and "primitives". Primitives are 
implemented as low-level C-like variables.

In that sense, Python has no primitives. Ints, floats, etc. (which are 
primitives in Java) are full-blown objects in Python.

If you mean "primitive" in the sense of "built-in", your conclusion is 
wrong. Such "primitive" types include dicts and lists, which are 
sophisticated container objects with internal "variables" to be assigned 
to: alist[0] = None assigns the value None to the first position in the 
list. Just because these "variables" aren't *named* variables doesn't 
mean they don't vary, or that they can't be assigned to.

If you mean "primitive" in some other sense, I'm afraid I can't guess 
what you mean. 


>> "ll[0] = 1" assigns 1 to the 0 slot of ll. "o.a = 1" assigns 1 to the
>> 'a' attribute of o. This which might be implemented by assigning 1 to
>> the 'a' slot of o.__dict__, just as "a=1" might be implemented by
>> assigning 1 to the 'a' slot of a namespace dict.
> 
> There's a qualitative difference here: simple assignment has semantics
> defined by the language and provided by the implementation, and can
> therefore be understood in isolation, using only lexically apparent
> information; whereas the complex assignments are implemented by invoking
> methods on the objects mentioned on the left hand side.

Again, you're using unfamiliar terminology. "Simple" and "complex" 
assignment -- I can guess you mean "name binding" = simple and "any other 
reference binding" = complex in Python terminology:

name = value  # simple assignment
dict[key] = value  # complex assignment
obj.attr = value  # complex

The thing is, the semantics of assigning to built-in components are 
equally defined by the language as the semantics of name binding. If x is 
a built-in list or dict, the semantics of:

x[something] = value

is written in stone and part of the language semantics. Whether that is 
implemented by a method or not is an irrelevant implementation detail, 
but for what little it's worth, I believe the CPython implementation is 
written in a fully procedural fashion, with no methods in sight.

One can write:

[0, 1, 2, 3, 4][2] = 5

and the language semantics tell you *exactly* what will happen: the value 
of the list in position 2 will be unbound and the object 5 will be bound 
in its place. Who cares whether the *implementation* happens to be 
written as a C function or a Java method, or something else?

You are right to point out that for arbitrary types:

x[2] = 5

need not be an assignment, since if x is not a built-in x.__setitem__ 
will be called, and it may do anything it likes. By contrast, name 
binding:

x = 5

*never* calls a method on any object, and therefore is fully defined by 
the language semantics. The distinction between the two count as an 
important proviso to the discussion: x[2] = 5 is only an assignment if 
the type of x is non-pathological and not broken.

But putting aside such pathological cases, I don't think you can justify 
distinguishing between "simple" and "complex" assignment. Names are only 
one type of reference in Python, not the only one, and assignments other 
than name-binding can be fully understood from the language semantics 
*provided you know the type is a built-in*.


>> Assignment *always* binds an object to a target.
> 
> No!  Assignment /never/ binds.

A shocking claim that requires more explanation. If it doesn't bind, what 
does it do?


> There is syntactic confusion here too,
> since Python interprets a simple assignment in a function body -- in the
> absence of a declaration such as `global' to the contrary -- as
> indicating that the variable in question should be bound to a fresh
> variable on entry to the function.

Well, there seems to be some confusion here, but I don't think it's 
ours... local variables in functions aren't bound on *entry* to the 
function. How could they be? They are bound *when the assignment is 
executed*, which may be never -- hence it is possible to get an 
UnboundLocalError exception, if you try to retrieve the value of a local 
which hasn't yet had a value bound to it.

> But assignment itself doesn't
> perform binding.  (This is a persistent error in the Python community;
> or, less charitably, the Python community gratuitously uses the word in
> a different sense from the wider programming-language-theory community.
> See Lisp literature passim, for example.)

*Less* charitably? I'm sorry, you think that being *wrong* is better than 
being *different*? That's not a moral judgment I can agree with.

 
> There's a two step mapping: names -> storage locations -> values.
> Binding affects the left hand part of the mapping; assignment affects
> the right hand part.

That gratuitously conflates the implementation ("storage locations") with 
the interface. Objects in Python have no storage location, they float in 
the aether, free to move if the implementation chooses to move them... 
the CPython implementation doesn't, but some other implementation might 
do so, but either way *you can't tell the difference* from Python.

There is nothing you can write in pure Python that can tell whether the 
storage location of an object has changed, or even whether "storage 
location" is a well-defined concept. Nothing in the semantics of Python 
demand that objects must be implemented as single contiguous blocks of 
data with a well-defined location. If you can think of an alternative, 
you're free to write an implementation that uses it.

The closest you can come is that CPython exposes the memory location as 
the id(), but that's not a language promise: Jython and IronPython do not.



-- 
Steven



More information about the Python-list mailing list