strange behavor....

Mark Wooding mdw at distorted.org.uk
Sat Nov 13 18:28:36 EST 2010


Steven D'Aprano <steve at REMOVE-THIS-cybersource.com.au> writes:

> On Sat, 13 Nov 2010 20:01:42 +0000, Mark Wooding wrote:
> > 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.

I was attempting to define possibly unfamiliar terms as I went along.
Did you not notice?

> If you mean "primitive" in the sense of "built-in", your conclusion is 
> wrong. Such "primitive" types include dicts and lists,

Yes, those are precisely the ones I was thinking of.

> which are sophisticated container objects with internal "variables" to
> be assigned to:

They have internal state.  I don't think that internal state consists of
(Python) variables, though.  This is why I brought up the possibility of
a pure Python implementation, maintaining its state by assigning to
variables captured using closures.

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

I'm sorry.  I thought I defined those terms recently.  (It appears not.
Sorry.)  I meant to refer to assignments in which the target is an
identifier (to use the actual terminology from the manual).

> "Simple" and "complex" assignment -- I can guess you mean "name
> binding" = simple and "any other reference binding" = complex in
> Python terminology:

Again: assignment is not binding.  See the explanation below.

> The thing is, the semantics of assigning to built-in components are
> equally defined by the language as the semantics of name binding.

Did you read what I said?

> You are right to point out that for arbitrary types:
>
> x[2] = 5
>
> need not be an assignment, 

Good.  This was /precisely/ what I was pointing out: determining
/whether/ the value named by an identifier inhabits one of the built-in
types is, in general, hard.

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

The latter is not an assignment: it's a disguised method call.

> But putting aside such pathological cases, I don't think you can justify 
> distinguishing between "simple" and "complex" assignment.

To conflate them is to confuse two different levels of meaning.  Simple
assignments occur because the language is hard-wired that way; complex
assignments are disguised method calls which often mutate values.

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

Duh!  It assigns.  You're not usually this slow.  Fortunately I explain
below.

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

The exception name perpetuates the misunderstanding, alas; but it's
traditional, from Lisp, to say that a variable is `unbound' if it
contains no value.

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

Being wrong is perhaps justifiable, and is rectifiable by learning.
Being gratuitously different in such a case is to intentionally do a
disservice to those coming from or going to other communities where they
encounter more conventional uses for the terms in question.

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

There isn't a common term for that concept.  I chose `storage location'
because it's the term I remember seeing in the denotational semantics
for Scheme.  The phrase `bound to fresh locations' occurs frequently
elsewhere in the Scheme report.  It seemed apt.  Choose some other term
if you please; I merely wanted a term to describe a concept.

> with the interface. Objects in Python have no storage location, 

And now you're confusing `storage locations' (whatever you choose to
call them) with values (or `objects'), when my point was precisely that
the two are different.

> There is nothing you can write in pure Python that can tell whether the 
> storage location of an object has changed

Indeed.  Several locations may contain the same value.  I remembered
that the term `reference' was controversial so I avoided using it. ;-)

> or even whether "storage location" is a well-defined concept.

I disagree; though it can be a little subtle.  Consider:

        def make_cell(x):
          def s(y): nonlocal x; x = y
          return lambda: x, s

        def copy_cell(c):
          g, s = c
          return lambda: g(), lambda y: s(y)

Given two such cells (g, s) and (gg, ss), we can determine whether they
use the same storage location or not, despite possibly having been
constructed using copy_cell, perhaps one from the other, or perhaps
(indirectly) from a common proper ancestor.

        def same_location_p(g, s, gg, ss):
          fresh = object()
          old = g()
          try:
            s(fresh)
            return gg() is fresh
          finally:
            s(old)

Because of `copy_cell' you can't use `id' to solve this problem (at
least not without doing serious poking about inside code objects).

> Nothing in the semantics of Python demand that objects must be
> implemented as single contiguous blocks of data with a well-defined
> location. 

Indeed.  I didn't claim otherwise.

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

No.  `id' reveals object identity.  The clue is in the name.

-- [mdw]



More information about the Python-list mailing list