PEP 245

Alex Martelli aleaxit at yahoo.com
Wed Apr 4 06:14:09 EDT 2001


"Alex Shindich" <shindich at itginc.com> wrote in message
news:mailman.986351706.5602.python-list at python.org...
> >Yes: C++ sort-of-confuses "I implement an interface" and "I conveniently
> >use [some part of] a class in my own implementation" under the single
> >rubric of 'inheritance' -- pretty bad in itself, and the distinction
> >between public and private inheritance is a helpful band-aid given this
> >confusion.  Better, of course, not to have the confusion at all.  In
> >Python, we also lack the band-aid, so it's _particularly_ important to
> >avoid confusing these two concepts -- both important, but quite distinct.
>
> I personally rarely use inheritance in Python for anything else but to
> describe interface-like behavior. For code reuse I prefer containment...
But
> that is just me...

Would be nice if it were just you, but from my observation of Python
programmers with C++ backgrounds I notice this conceptually erroneous
and pragmatically inferior Python practice, while minoritarian, IS
affecting quite a few Pythonistas and hampering their potential (I
suspect an Eiffel background might have similar effects, but I have
no direct observation to offer here).

Nothing wrong with delegation (and/or containment, but the two issues
are really separable) as an implementation technique -- e.g., in
http://pythoncookbook.activestate.com/pythoncookbook/community/data/98525811
4
I show how to use _automatic_ delegation when inheritance is not
available (e.g., from an object that is not of instance-type).

But Python inheritance is *STRICTLY* about implementation and
*NOTHING* else.  Python gives *ABSOLUTELY* no indication that
a class A inheriting from another class B will offer anything
matching B's "interface" -- not even method signatures:

class B:
    def __init__(self, *data):
        self.data = data
    def amethod(self, x):
        print "B: amethod",self.data,x
    def another(self):
        print "B: another",self.data

class A(B):
    def another(self, x, y):
        print "A: another",self.data,x,y

Here, A is inheriting from B strictly for the purpose of getting
B's initialization, the 'data' attribute, and "amethod" -- it
does NOT intend to offer B's interface, and indeed it overrides
the 'another' method with a different and incompatible interface.

Sure, the effects of inheritance _could_ be laboriously simulated
by containment-and-delegation, but, here, there is no good reason
to do so!  Raise-to-integer-power can be laboriously simulated by
a loop using just multiplication, but WHY should one do so, when
the language offers raise-to-power as a built-in operator?!  Maybe
in the name of some confused "philosophy" that wants to keep the
'**' operator for complex-number operations only, or...?  Puh-leeze.

Python inheritance is essentially about implementation -- a _great_
implementation technique whenever it's appropriate.  And it cannot
be replaced with containment-and-delegation _everywhere_, quite
apart from it being silly to expend work to do so when feasible
but not needed.  C-&-D, for example, does not handle the crucial
case of the design-pattern "Template Method", where a base object
implements a higher-level operation in a method that calls upon
lower-level operations as methods on 'self', so that derived
objects can selectively override the lower-level methods and get
the desired higher-level effects.  Consider the standard sgmllib
module, for example: HOW will you effectively reuse the very
good code supplied in the SGMLParser class, in your implementation,
except by deriving from it and selectively overriding/adding
methods?  *THAT* is what it is designed for -- a very good example
of properly Pythonic use of inheritance!


So, we ARE without language constructs that clearly assert
"this object implements this protocol" or explicitly test
whether this protocol-implementation has been asserted.  It
is not sensible to reserve class inheritance for this purpose,
as above explained; it is confusing and inappropriate to use
inheritance to express BOTH I-implement-this-protocol AND the
normal I-use-this-in-my-implementation case, without even the
C++ band-aid of private vs public inheritance to help distinguish
the two very different cases.  The PEPs on interfaces and on
(bidirectional) protocol-adaptation address this situation,
from two different angles, and both, IMHO, do so rather well.


> >rules are respected, COM couldn't care less about HOW this is
> >brought about, nor WHEN (COM has *NO* concept of "compile time").
>
> OK!!!!!!!!!!!!!!!!! COM was a stupid example!!!!!!!!!!! Yes it only cares
> about the memory layout of the virtual tables. And YES, COM components can
> be written using anything that is capable of creating C++-compatible
virtual

(Not quite -- the methods in the almost-virtual-tables of COM need to use
calling-convention __stdcall, with 'this' aka 'self' as the first [implicit]
argument on the stack, while C++ compilers for Win32 platforms, depending
on their brand, normally use __fastcall [arguments in registers] or else
__thiscall ['this' aka 'self' in the CX register], for example -- a minor
issue, but let's not spread _further_ misinformation about COM, shall we:-).

> tables. It is just that I happened to write COM code using C++ on a very
> frequent basis (and no, I do not use Wizards...), and for the most part I
> implement multiple interfaces using multiple inheritance. Not that it is
the
> only way of doing it.

Definitely not, but it can be quite fast and space-saving when used
appropriately.  *Strictly* an implementation issue, OF COURSE -- how
much memory and machine-instructions will this approach use compared
to others, how easy it will be to express in your chosen implementation
language, etc.  But that's EXACTLY what inheritance is all about, when
used appropriately: fast, space-saving, convenient IMPLEMENTATION:-)


> And why don't we stop the whole COM nonsense here! I love COM, I write COM
> code all the time. And YES, I used it as a bad example. Let it rest
already.

No, since you keep making misleading observations about COM, I do not
think it would be appropriate to leave those observations unanswered --
they might engender confusion and misunderstandings in somebody not
fully conversant with this important technology (the existence of XPCOM,
an emerging open-source and cross-platform componentization technology
inspired from COM, only makes it MORE important to understand precisely
what is, and isn't, expressed or implied by each technology).

Further, COM provides an excellent counter-example to your main thesis,
that interfaces and "compile-time-safety" _need_ to have anything at all
to do with each other: COM itself is CRUCIALLY about interfaces, and
yet it does NOT define any "compile-time" distinction from "run-time"
(although, of course, some, but not all, of the _languages_ used for
COM implementation may choose to draw this distinction, not COM itself).
A proof-by-existence (and by-technical-success) is always impressive:-).


> If you know so much about COM and you feel like arguing, we can find
another
> forum for that. It still has nothing to do with the original point, what
is
> the value of adding interface support to Python.

It has a LOT to do with it, exactly because it shows how wonderful
interfaces are in an environment where "compile-time" is not defined:-).


> >There are more things in heaven and earth, Mr Shindich,
> >Than are dreamt of in your philosophy.
> I sense a lot of anger in you...

Do you say that to everybody who quotes Shakespeare, or only to
those who quote from his _tragedies_ specifically?-)


> >access and usage) will work -- constraints on object implementers
> >for the benefit of optimizations in the COM infrastructure (and,
> >to a lesser degree of importance, for the benefit of client-side
> >coders;
> Huh? "lesser degree of importance, for the benefit of client-side coders"?
> That is a very one-sided approach to the problem. Interface identity is

What's "interface identity"?  The semantic constraints are about
*object* identity.  I suspect we're talking at cross-purposes here,
yet I thought I was explaining VERY clearly (at least for somebody
fully conversant with COM) the constraints I was talking about.

> being used for marshaling, but there is more to it.

Not much more!  Proxying, as well as marshaling; and some minor
idioms enabled on the client-side (ability to hold _any_ one
interface to an object, rather than having to use a designated
specific one); that is about it.

> I see identity
> constraints as being useful because they enable the client coders to rely
on
> the particular semantics associated with the particular version of the
> interface.

This has nothing to do with object-identity semantics.  If client
code obtains an interface-pointer via the appropriate factory method
IServiceProvider::QueryService (which is *NOT* subject to usual COM
object-identity constraints!) rather than via QueryInterface
(which *IS* subject to them), it can still "rely" on exactly
the SAME _interface_ properties (which may not be much, given
how often COM design violates the interface-segregation principle
and thus ends up with lots of methods that will raise a not-implemented
exception if called -- but, that is another issue).  What client
code CANNOT do, when it has used QueryService rather than
QueryInterface, is rely on *object-identity* semantics -- there
is NO guarantee whatsoever that the service-provider has supplied
"itself" as the implementer of the requested interface; there are
no constraints that ensure interfaces can be requested in any
arbitrary order, or that multiple requests for the same interface
will all succeed or all fail, etc, etc, if the requests come via
QueryService -- while all of these things ARE guaranteed (and thus,
constrained) if the requests come via QueryInterface.

> I am sorry, at what point in time did I ask for a COM interface rules
> lecture?

When you make false assertions on Usenet, you implicitly ask for
technically correct information on the subject -- since readers
who are not able to distinguish falsehood from truth in a specific
technical area may well be following the discussion, it IS an
important service to them to ensure errors are appropriately
pointed out and corrected.

> WHAT DOES IT HAVE TO DO WITH THE DISCUSSION ON HANDS?

A lot!  I should be surprised that you do not see it.  PEP 245
hinges interface-compliance on "identity constraints" (albeit,
weakened ones wrt COM's, since an instance-object's class
membership CAN change in Python), while the (as-yet-not-
numbered, I believe) protocol-adaptation PEP has no identity
constraints (the wrapper or adapter may be ANY object...).

Understanding exactly what object-identity constraints buy
you (and what they DON'T) is important to evaluating both PEPs.
And COM is one good arena in which to discuss the effects of
object-identity semantic constraints, because it's so widespread,
well-specified, and well-understood -- of course, similarities
and differences between the sets of use-cases of COM and Python
need to be also well-understood, and taken into account.

And, more generally...:

Python is quite interesting from an OO point of view because
it decouples object-identity, object-state (the __dict__ of
an instance object can be modified), object class-membership
(ditto for the __class__), AND to some extent object-behavior
(since the object's __dict__ may contain bound-methods that
will 'override', so to speak, methods from the __class__).

It IS, of course, perfectly appropriate to debate which (if any)
of these decoupling-possibilities should not be used at all
(e.g., Moshe and others are on record as stating that they
strongly believe an object's __class__ should always change
if its behavior does -- i.e., they see the class/behavior
decoupling as an undesirable language feature, never to be
actually _used_), and/or _how_ they should be used if they
ever are.  The very existence of so many degrees of freedom
may easily engender confusion, particularly in people with
well-rooted backgrounds in languages and/or object-models
that offer nothing comparable.  One good way to get richer
perspective is to look from SEVERAL points of view, i.e.,
comparing and contrasting Python's unusually-decoupled Object
Model with that of SEVERAL other languages and OM's.  This,
of course, applies quite as much to proposed _modifications_
of Python's OM, as it does to understanding and optimal usage
of said OM as it stands today.


> >A _pragmatical_ person, on the other hand, takes into due
> >account these limitations _and still documents these kinds
> >of constraints_ as formally as feasible given whatever
> >technology is at hand.
> I use the technological marvel called text editor to document my code.

Unfortunately, your documentation, not being subject to any
constraint of formality, is quite apt to be as ambiguous and
insufficient as (for example) that of the Python library, where
it is often unclear (and occasionally incorrect) exactly WHAT
protocol is requested from a given object.  For example, often
a "file-like object" may be mentioned, but it may not be made
clear and unambiguous WHAT subset of builtin file-objects needs
to be supplied by the object in question (does it need to have
suitable .read, .readlines, .write, .writelines, .close, ...?
does it need to let these be called with an integer argument and
respect the semantics thereof? does it need to have a settable
attribute named .softspace?  etc, etc).  When the author of
client-code misinterprets exactly what the library author meant
for "file-like object", he or she will risk wastefully expending
much work implementing unneeded methods (or uneeded twists on
their semantics, e.g., the integer argument to .read &c), or
else may get surprised by not-always-crystal-clear exceptions
coming up from deep in the bowels of library-code.

Having protocols, aka interfaces, as a part of the language,
would help a lot with this.  The _exact_ characteristics of
the protocol required from the object that client-code supplies
may then be expressed formally, easily, and concisely, and
checked most simply and explicitly.  Bliss!  (_particularly_
with the protocol-adaptation PEP, but that's a different issue).

Sure, sure, it's not _indispensable_ to have the concept
of interface, or protocol, in the language -- it can be
laboriously kludged up as a series of conventions (but they
would have to pervade the standard library to be really
useful -- so, there would be no real gain in keeping them
out of the language!-).  But, once again, this is just like
the case of the '**' operator: we COULD kludge something up
if it wasn't there, but it's IMHO _much_ better to have it
in the language -- clearer, faster, easier.  Python is NOT
obsessively minimalist: it's a *pragmatist*'s language...:-).


> >is used instead of an array, and *THAT* is *OBVIOUSLY* not compile-time
> >type-safe since ANY object can be stuffed into it -- it doesn't take
> >much Java knowledge to point this out:-).
> Wait till Java generics become available...

That will be a substantial improvement to Java (I remember
experimentally using a Java dialect called 'Pizza', years
ago, and loving it; good generics were prominent in it).

It's also interesting to reflect on why Python does not NEED
generics (or, equivalently, C++ templates) -- and what is
needed to keep this happy state of affairs if and when some
kind of protocols, or interfaces, are introduced (basically,
as long as just about _everything_ remains 'first-class',
we're covered:-).


> >And whoever thinks that a good design is less valuable when a
> dynamically-typed language, rather than a
> >statically-typed one, is to be used for the coding, needs far more
> >reprogramming than I can supply:-).
> When did I say about good design being only needed for statically types
> languages? I think you are hallucinating...

Did I ever say you think that?  Who, exactly, is the one hallucinating?

> What I SAID, is that Python has it's own SET OF INTROSPECTION-BASED
PATTERNS
> that generally allow you to accomplish same things as patterns that are
more
> INTERFACE BASED!

In roughly the same sense as a loop of multiplications allows you
to accomplish the same things as an application of the ** operator.

If a design HINGES on interfaces, aka protocols, for a good part
of its structuring (and I stated that this is a GOOD way to base
the design of many software systems -- I did not see you express
either agreement or disagreement with this thesis), it's a real
pity that Python offers no good way to express that clearly and
explicitly in the language itself.  Laborious introspection is
not a GOOD, clear, simple expression; class inheritance is simply
not appropriate, as it is about implementation, not protocol.  We
WILL get excellent mileage from acknowledging protocols, also
known as interfaces, as an explicit part of the language -- quite
apart from any _further_ uses they might have in other languages.


> IN ALL HONESTY, I WOULD LIKE TO DISCONTINUE THE COM DISCUSSION!!! If you
> have something you would like to argue with me about, please mail me
> directly. YOU MADE YOUR POINT EXPLAINING THAT COM IS NOT
LANGUAGE-SPECIFIC,
> AND THAT COMPILE TIME CHECKING IS PROVIDED BY THE USE OF C++ AND NOT COM
> TECHNOLOGY ITSELF. BUT PLEASE, STOP COM LECTURES!!!! THIS IS A PYTHON
> NEWSGROUP!!!!

Please don't "shout".  I have no special interest in discussing
directly with you (and, indeed, I shouldn't be spending the
considerable amount of time this discussion on the newsgroup
is taking me -- but, I consider interfaces/protocols by far the
best thing that could possibly happen to Python, and I hope that
by showing that arguments against their introduction are weakly
based and leaking from all sides I may possibly help a little --
helping a certain number of readers understand some crucial
issues fully is always a better-leveraged use of time than doing
so one-on-one).  Yes, this IS a Python group, and the parallels
(and lack thereof) between Python and other languages and object
models (C++, COM, Java with and without generics, Haskell, etc)
play a crucial role in helping some readers to a better understanding,
both of things as they stand today, and of the potential effects of
any possible modifications.  And nobody is well-served if factually
incorrect technical assertions are left unchallenged in a public
forum: that can only help spread misinformation and confusion.


Alex






More information about the Python-list mailing list