Programming intro book ch1 and ch2 (Windows/Python 3) - Request For Comments

Alf P. Steinbach alfps at start.no
Sat Dec 19 02:17:12 EST 2009


* Steven D'Aprano:
> On Sat, 19 Dec 2009 04:04:51 +0100, Alf P. Steinbach wrote:
> 
>> * Steven D'Aprano:
>>> On Fri, 18 Dec 2009 19:00:48 +0100, Alf P. Steinbach wrote:
>>>
>>>> In fact almost no Python
>>>> code does, but then it seems that people are not aware of how many of
>>>> their names are constants and think that they're uppercasing constants
>>>> when in fact they're not. E.g. routine arguments
>>> Routine arguments are almost never constants, since by definition they
>>> will vary according to the value passed by the caller.
>> I'm sorry, but that requires a definition of "constant" that explicitly
>> excludes routine arguments, 
> 
> /s/explicitly/implicitly
> 
> 
>> which is like saying horses are not mammals, just "because".
> 
> No, it's like saying that horses are not apes, because the definition of 
> apes excludes horses.

On the contrary. Apes are not superclass of horses.


> But in any case, rather than continue my argument (which was absolutely 
> brilliant and flawless, I might add... *wink*), I'm going to skip ahead 
> to the place where the penny drops and I understand what you were trying, 
> but failed, to say.
> 
> 
>> Consider some C++ code:
>>
>>    void foo( SomeType const v )
>>    {
>>        // Here the name v is constant: that name's value can't change.
>>        // (Except that in C++ you can do anything by using low-level
>>        stuff.)
>>    }
> 
> *penny drops*
> 
> Ahaha!!! Now I get it! You want to make the *parameter* of the function a 
> constant, rather than the *argument* passed to the function. In other 
> words, you want to prohibit something like this:
> 
> def f(x):
>     y = x + 1  # do something with the value passed by the caller
>     x = 0  # changing the binding will fail INSIDE the function
>     
> while still allowing the caller to call the function with variables.

I would want the possibility of having that enforced, of course, because the 
more you know about what can't take place in the code, i.e. the more easy-to-see 
constraints there are, the easier it is to understand and reason about the code, 
not to mention modifying it without breaking original assumptions.

But I wasn't talking about such a language feature (which I believe could also 
greatly improve Python's efficiency by removing the need for many lookups).

Rather, I was remarking that in the absence of such a language feature, to be 
consistent those who insist on using naming conventions to indicate design level 
constraints should use those naming conventions also for this case and others. 
For there's little point in making some occurrences of a constraint visually 
explicit and others not. That just means that one cannot rely on the convention 
to tell where the constraint is (meant to be) in effect or not.


> Right -- now what you say makes sense, and is a perfectly reasonable 
> thing to do in languages that support constants.
> 
> You weren't clear about what you wanted, and the only thing I could think 
> of which matched your description was something completely bizarre: given 
> some function f with one parameter, you wanted to declare that parameter 
> as a constant with some value (say 42), so that calling the function with 
> an argument of any other value would be an error, e.g.:
> 
> x = 42
> f(x)  # succeeds
> x = 43
> f(x)  # fails
> 
> E.g. implemented something like this:
> 
> def f(x):
>     if x != 42:
>         raise ConstantError('x is not the constant 42')
>     ...
> 
> except that the test is done automatically by the compiler.
> 
> 
>> To be pedantic, original routine names are usually absolutely not meant
>> to be assigned to.
>>
>> If you have
>>
>>    def foo(): whatever()
>>
>> you simply shouldn't, in general, do
>>
>>    foo = bar
>>
>> I think you understood that, i.e., that the above comment of yours is
>> just rhetoric?
> 
> First of all, I think you're underestimating the usefulness and frequency 
> of rebinding names to functions. I'll accept that it's uncommon, but it's 
> not *that* uncommon to justify "absolutely not meant to be assigned to".

Yeah, hence the weasel term "in general".


> In fact, it's so common that we have special syntax for one special case 
> of it. Instead of:
> 
> def f():
>     ...
> 
> f = g(f)
> 
> we can write:
> 
> @g
> def f():
>     ...
> 
> While decorator syntax can only be used at function-definition time, the 
> concept of function decoration is far more general. You can decorate any 
> function, at any time, and doing so is very, very useful for (e.g.) 
> debugging, logging, monkey-patching, introspection, error-checking, and 
> others. And decoration itself is only one special case of function 
> rebinding.

But usually this wrapping occurs before the function is first used by other 
code, i.e. it's usually part of initialization  --  isn't it?

For debugging purposes you might want to replace a function on-the-fly, in the 
middle of the program execution.

But debugging does all sorts of things not commonly accepted for normal execution.


> As for your second point, my earlier comment is mostly aimed at what I 
> see as your misleading habit of referring to *names* as being constants, 
> rather than the value assigned to the name. Such terminology is 
> misleading. So if you want to call that rhetoric, I won't argue.

Not sure what that second point was (lost in response stack and snippage 
somewhere), but as I remarked else-thread, in response to you, I'm sorry, but 
your view about names is incorrect.

Quote from §4.1 "Naming and binding" of the Python 3.1.1 language spec:

"If a name is bound in a block, it is a local variable of that block, unless 
declared as nonlocal. If a name is bound at the module level, it is a global 
variable. (The variables of the module code block are local and global.) If a 
variable is used in a code block but not defined there, it is a free variable."

It's not just terminology that a Python name "is" a variable, and vice versa.

Constness of the referred to value is an appropriate concept when that value is 
conceptually mutable, like

   v = [1, 2, 3]

This particular case is supported for the referent,

   v = (1, 2, 3)    # Voila, constant referent

which expresses and enforces the constness of the referent.

But to express the constness of the variable, which is what matters most of all 
(for without that top level constness all other constness, such as of direct 
referent, can be easily and inadvertently circumvented) all you can do is to 
e.g. name it V instead of v, or put its creation in a region of text clearly 
signalling "these are constants", or some such  --  conventions.

Since a variable is a name, and in Python a name is a variable, "constness of a 
variable" means "constness of a name". In Python. And as discussed above it's 
about any name binding whatsoever, while the PEP 8 naming convention is limited 
to communicating the intended constraint in just a few special cases, and hence 
is unreliable by design (in addition to introducing lots of visual gruff).


>>> As far as I know, no programming language provides a standard facility
>>> for renaming entities, be they data or routines:
>> Eiffel (IIRC) and C++ come pretty close.
>>
>> E.g., in C++:
>>
>>    int a;
>>    int& b = a;    // A new name for a.
>>
>>    b = 123;  // Assigns to a.
> 
> No, that's not a renaming operation. a still exists; you haven't told the 
> compiler "stop accepting a as the name for this memory location, and 
> accept b instead". It's an aliasing operation: name b and name a both 
> refer to the same memory location and hence the same value.

Well, you can always do

   int& b = a;
   #define a __NO_SUCH_NAME__

He he. :-)

Without the preprocessor you can do things like (or more elaborate)

   int a = 666;
   {
       int& b = a;  struct a;
       // Only name b useful at this point. Most uses of a will be flagged.
   }

And with the preprocessor that kind of renaming can be automated.

But seriously I thought you were referring to aliasing, because changing a name 
mid-code generally makes no sense  --  other than perhaps in an obfuscation 
contest (where of course techniques like the #define shown above /are/ employed, 
although typically with single letter names; it's a yearly contest for C...).


[snip]
> 
>> Well, you're off on the wrong track as far as convincing me about
>> something is concerned. First, your belief about renaming not being
>> supported by any languages is largely incorrect, as shown above.
>> Secondly, I was not talking about renaming things  --  that creative
>> interpretation is pretty meaningless...
> 
> But if you talk about mutating *names*, then what else could it mean than 
> that you want to change the *name*? I can only respond to what you write, 
> not what you were thinking.

In Python names are variables, per the language spec and in practice.

The language spec's saying:

"If a name is bound in a block, it is a local variable of that block, unless 
declared as nonlocal. If a name is bound at the module level, it is a global 
variable. (The variables of the module code block are local and global.) If a 
variable is used in a code block but not defined there, it is a free variable."

Plus that of course, given this, the spec generally uses "variable" and "name" 
as interchangeable synonyms, with just a little context-dependent favoring of 
one or the other  --  if you want example quotes I can provide a host of them.

And most variables, at least in code I'm familiar with, are constants, in the 
sense that they're not intended to be re-bound after initialization.

And that's what I wrote, "most names are constants", and it was actually meant 
to be precise: I was not talking about referred to objects, only about name 
re-binding, that is, about changing what you get by id(N) for a name N.



Cheers & hth. (all cleared up now?),

- Alf



More information about the Python-list mailing list