is python Object oriented??

Steven D'Aprano steven at REMOVE.THIS.cybersource.com.au
Wed Feb 4 02:10:26 EST 2009


On Tue, 03 Feb 2009 12:09:46 +0100, Bruno Desthuilliers wrote:

>> I love Python, and I'm greedy and want it all: I want a dynamic,
>> easy-to- use language *and* a compiler that can protect me from myself
> 
> That's not what compilers are for.

So you say.

 
>> and bad data.
> 
> I definitly fail to see how a compiler could protect you from *runtime*
> issues ???

I'm surprised you can't. Any time a compiler prevents an error by 
detecting it at compile-time (say, attempting to store a 96-bit float 
into the memory space allocated for a 32-bit int), it has prevented a 
runtime error.

Perhaps what you are thinking is that I meant compilers can protect you 
from "all" runtime issues. That would be silly. Although, in principle, 
we can get close, for some definition of "close".


Here's a toy function, and a specification:

def inverse(x):
    return 1.0/x

Specification: input must be a float, output must be a float.

Is that function correct? No, because it will raise an exception if 
x==0.0. Python's compiler is dumb and will allow you to write incorrect 
functions, but a smart compiler could look at that function and see that 
it was incorrect. The fix would then be to change the code, or the 
specification, or both.

Smart compilers turn some runtime errors into compile-time errors. The 
earlier you catch the error, the easier it is to fix it.

Now, that's a toy example. Languages like Ada make correctness proofs, 
well, perhaps not easy, but merely difficult compared to impossible for 
languages like Python.

To bring it back to private/public attributes, side-effects make 
correctness proofs difficult. Modifications of attributes are side-
effects. When attributes are subject to being modified by arbitrary code, 
it is harder to reason about the correctness of the code:

class Inverter(object):
    def __init__(self, x):
        # Pre-condition: x is always a float
        if x:
            self.x = x
        else:
            raise ValueError("x must not be zero")
        # Post-condition: if self.x exists, it is a non-zero float
    def invert(self):
        return 1.0/self.x


Is method invert correct?

No, it is not, because the invariant that self.x is non-zero may have 
been broken by some arbitrary piece of code elsewhere. Our ability to 
reason about the correctness of the code is weakened significantly. 
Sticking an underscore in front of x does not help: there's nothing to 
stop some arbitrary function elsewhere changing _x to zero.

There are two usual approaches to dealing with that problem which are 
available to Python programmers:

(1) Paranoia. Make the developer responsible for checking everything all 
the time:

    def invert(self):
        x = self.x
        if isinstance(x, float) and x:
            return 1.0/self.x
        else:
            raise MyError('post-condition x a non-zero float violated')

You can use decorators to decrease the amount of boilerplate, but you 
have to remember to use the decorators; or you can use a metaclass to 
automatically apply decorators, but that's hard and not very flexible. 
Whichever way you do it, you're still responsible, and whatever way, you 
still have to pay the performance penalty.

This also turns an unexpected exception into an expected exception, but 
is otherwise rather useless. It doesn't tell you when the post-condition 
was violated, or by what function, and that's the information you need in 
order to fix the bug.



(2) Hope the error never occurs, and if it does, let the caller deal with 
it. Hopefully you aren't your own caller.

That's often the Python approach. I've already written about the glorious 
freedom such an approach gives the developer. I'm not being sarcastic: 
even if you are your own caller, you get to put off worrying about a bug 
which might never happen. This is a great strategy when the worst thing 
that a bug will do is display an icon in the wrong position. Fix it in 
the next release.

But that's not a viable strategy when the consequences of a bug might 
include giving the patient 20,000 rads of radiation instead of 200:

http://www.ccnr.org/fatal_dose.html


There is a third strategy, sadly not available to Python programmers: 
prevention by catching potential errors at compile-time. (Python does 
catch some errors at compile-time, but only syntax errors.) If x can only 
be modified in a few places, (that is, it is effectively hidden or 
private) then it is much easier to reason about program correctness, 
avoid bugs, and debug those bugs which do occur.

No, this is not a panacea which will prevent every imaginable bug. Nor is 
it a replacement for unit-testing (which also does not prevent every 
imaginable bug). It is a compliment to unit-testing.

Is it subject to over-use or misuse? Of course it is. So are properties, 
and decorators, and metaclasses, and OO, and Singletons, and, well, 
everything. Let's not close our eyes to *either* the costs or the 
benefits, and let's not limit what Python might become in the future. 
Python 3 has type annotations and Guido is encouraging people to 
experiment with type systems. This was unthinkable a year or two ago. 
What might Python 4000 bring?



-- 
Steven



More information about the Python-list mailing list