Experiences/guidance on teaching Python as a first programming language

Steven D'Aprano steve at pearwood.info
Wed Dec 18 09:18:50 CET 2013


On Tue, 17 Dec 2013 22:49:43 -0500, Paul Smith wrote:

> On Wed, 2013-12-18 at 01:33 +0000, Steven D'Aprano wrote:
>> And "What does 'implementation-specific undefined behaviour' actually
>> mean in practice?", another common question when dealing with C.
> 
> Only asked by people who haven't had it explained.  There's "undefined
> behavior", and there's "implementation-specific behavior", but it is
> impossible to have "implementation-specific undefined behavior".

Of course it is possible. An implementation has to do *something*, even 
if it's not defined anywhere. Even if that "something" is crash, or emit 
no code, or display a compile-time error. I think you're making a 
distinction that doesn't apply to the plain-English meaning of the words 
I was using: the behaviour is undefined by the standard and specific to 
that implementation.


> And, the definitions are simple to understand: "undefined behavior"
> means that if your program invokes it, there is no definition of what
> will happen.  This is buggy code.

Yes, it is buggy code, but nevertheless it often works the way people 
expect it to work, and so through carelessness or ignorance programmers 
rely on it. If you've ever written i+1 without a guard for the case that 
i is INT_MAX, you're guilty of that too.

The C99 standard lists 191 different kinds of undefined behavior, 
including what happens when there is an unmatched ' or " on a line of 
source code.

You want to know why programs written in C are so often full of security 
holes? One reason is "undefined behaviour". The C language doesn't give a 
damn about writing *correct* code, it only cares about writing 
*efficient* code. Consequently, one little error, and does the compiler 
tell you that you've done something undefined? No, it turns your validator 
into a no-op -- silently:

http://code.google.com/p/nativeclient/issues/detail?id=245

No compile-time error, no run-time error, just blindingly fast and 
correct (according to the standard) code that does the wrong thing.


> "Implementation-specific" behavior means that the standard requires the
> implementation to do some well-defined thing, but the standard does not
> define exactly what it must be.  You can go look up what your
> implementation will do in its documentation (the standard requires that
> it be documented), but you can't assume the same thing will happen in
> another implementation.  This is non-portable code.

So much for the promise of C to be portable :-)


> It's a very rare language indeed that has no undefined or
> implementation-specific behaviors.

Java? Ada?

But indeed, most languages do have odd corners where odd things happen. 
Including Python, as you point out. But C has so many of them, and they 
affect *nearly everything*.

The aim of C is to write fast code, and if it happens to be correct, 
that's a bonus. C compilers will compromise on safety and correctness in 
order to be fast. Then end result is usually one of two outcomes:

- the programmer spends a lot of time and effort to manually guard 
against the undefined behaviour, thus slowing down the code;

- or he doesn't, and has bugs and security vulnerabilities in the code.


> Python gets to "cheat" by having one
> reference implementation.  Every time you've had to go try something out
> in the Python interpreter because the documentation didn't provide the
> details you needed, that WAS implementation-specific behavior.

The situation is quite different though. Python makes at least one 
implicit promise: nothing you write in pure Python can possibly cause a 
segfault. No buffer overflows for you!

We don't know what locals()['spam'] = 42 will do inside a function, but 
unlike the C case, we can reason about it:

- it may bind 42 to the name "spam";

- it may raise a runtime exception;

- it may even be a no-op;

But even if it is a no-op, the Python compiler doesn't have carte blanche 
to do anything it likes with the entire function, as a C compiler has. C 
has more indeterminacy than Python.



>> > You never have to wonder what the
>> > lifetime of an object is,
>> 
>> Since C isn't object oriented, the lifetime of objects in C is, um, any
>> number you like. "The lifetime of objects in <some language with no
>> objects> is ONE MILLION YEARS!!!" is as good as any other vacuously
>> true statement.
> 
> The implication that only an "object oriented" language could have a
> concept of object lifetimes is false.

Only object-oriented languages have *objects*. C does not have objects, 
it has values.

And yes, I'm being pedantic.


[...]
>> > or be mystified by which of the 7 signatures of Foo.foo() are going
>> > to get called,
>> 
>> Is that even possible in C? If Foo is a struct, and Foo.foo a member, I
>> don't think C has first-class functions and so Foo.foo can't be
>> callable.
> 
> Of course that's valid C.  It's true that C doesn't have first-class
> functions, but it supports invoking functions through pointers and you
> can store functions in data members, pass functions as arguments, and
> return functions from other functions, so Foo.foo can certainly be
> callable.

Okay, fair enough, that's why I prefixed my statement with a question.



-- 
Steven



More information about the Python-list mailing list