Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

isinstance() considered harmful

50 views
Skip to first unread message

Kragen Sitaker

unread,
Jan 23, 2002, 5:11:22 PM1/23/02
to
I had previously posted this to the kragen-tol mailing list; that copy
is at
http://lists.canonical.org/pipermail/kragen-tol/2002-January/000659.html

There is an HTML version at
http://pobox.com/~kragen/isinstance/

My apologies for the somewhat idiosyncratic markup.

isinstance() considered harmful
===============================

This is mostly in reference to \(href
http://www.python.org Python) programs, and it may
be more or less true with reference to programs in
other languages.

Python is an object-oriented programming language,
which is a buzzword, and therefore can mean a wide
variety of different things. What it actually
means, in Python's case, is that you can implement
your objects any way you please; it's the
interface they support that determines whether or
not they'll work with existing code --- that is,
the methods they implement and the semantics of
those methods.

For example, you can write the first version of a
data type in pure Python; you can then rewrite it
as a Python extension that creates CObjects, and
expect that it will work everywhere the Python
version did, as long as it's implemented
correctly. This is a useful property; it often
results in being able to apply old code to
completely new problems.

For this promise to hold, the code your object
works with must refrain from peeking behind the
interface the object supposedly supports. There
are probably some times that this is not
desirable; transparent persistence and object
migration systems can probably work better most of
the time by not respecting published interfaces.

Some languages, like Java and C++, offer explicit
interface support. Python is not among them. It
offers \(href
http://www.shindich.com/sources/patterns/implied.html
implied interfaces) in places where other
languages would use explicit interfaces. This has
a variety of effects, good and bad.

In Python, what classes your object is derived
from is not a part of your object's interface.

\(b Every use of isinstance is a violation of this
promise), large or small. Whenever isinstance is
used, control flow forks; one type of object goes
down one code path, and other types of object go
down the other --- even if they implement the same
interface!

Bjarne Stroustrup often cited concerns like these
when defending C++'s decision not to provide
isinstance. (Now, of course, with RTTI in the C++
standard, C++ does provide isinstance.)

Sometimes, of course, violating this promise is
worth the payoffs --- isinstance, like goto, is
not pure evil. But it is a trap for new
programmers. Beware! Don't use isinstance unless
you know what you're doing. It can make your code
non-extensible and break it in strange ways down
the line.

Reasons for using isinstance
----------------------------

Isinstance is used for a variety of reasons:
\(ul
- to determine whether an object supports a
particular interface
- to keep people from writing possibly incorrect
programs, or to indicate possible bugs
(type-checking)
- to test the implementation of an object to see
if certain optimizations can be applied
- to write regression tests with knowledge of the
implementation
- to define things that should really be methods
outside of the class they operate on
- for no apparent reason whatsoever
)

To determine whether an object supports an interface
----------------------------------------------------

Using isinstance() to determine whether an object
supports a particular interface is always a bad
idea. You are, in essence, including inheritance
from a particular class in the interface; if
anyone wants to implement that interface, they
must derive their class from the class you
specify, which means they'll inherit its bugs,
they'll probably have to understand its
invariants, and, in Python, they'll have to be
careful not to collide with names it defines.
(This is especially bad if one of those names is
__getattr__ or __setattr__, but that's another
problem.)

It's not just overly conservative; it's also
overly liberal. Someone can override methods in
the interface with broken methods --- in Python,
they can even override them with non-callable
objects. An object's derivation from a particular
class doesn't guarantee that it implements all the
protocol that class does. (Still, breaking
protocols your base classes implement is almost
always a bad idea.)

Type-checking to find bugs
--------------------------

Using isinstance() for type-checking to find bugs
is a special case of the above. The bug you might
catch is that the object being checked doesn't
implement the interface the rest of your code
expects.

Here's a perfect example from
distutils.cmd.Command.__init__:
if not isinstance(dist, Distribution):
raise TypeError, "dist must be a Distribution instance"

In fact, all Command requires of 'dist' is that it
have attributes verbose, dry_run, get_command_obj,
reinitialize_command, and run_command, with the
appropriate semantics. There's no reason that it
should have to be derived from Distribution, which
is a monster class nearly a thousand lines long.

Determining whether optimizations are applicable
------------------------------------------------

Using isinstance() to determine whether or not a
particular abstraction-violating optimization is
applicable is a reasonable thing to do, but it is
often better to package that optimization into a
method and test for its existence:

try: optmeth = obj.optmeth
except AttributeError:
do_it_the_slow_way(obj, stuff)
return
optmeth(stuff)

You can also use hasattr() for this test.

This allows your abstraction-violating
optimization to work without violating
abstraction --- simply by providing a more
intimate interface into your objects.

There's an example of this in the standard library
in test_coercion.py:

class CoerceNumber:
def __init__(self, arg):
self.arg = arg
def __coerce__(self, other):
if isinstance(other, CoerceNumber):
return self.arg, other.arg
else:
return (self.arg, other)

The __coerce__ method would be better written as:
def __coerce__(self, other):
num = self.arg
try: return num, other.arg
except AttributeError: return num, other

As it's written, if you had instances of two
textual copies of the CoerceNumber class, you
wouldn't even be able to add them together in
Python 1.5, and in Python 2, the interpreter would
have to call both of their __coerce__ methods to
add them together. (Something like this is
actually not uncommon; see the comments below
about reload.)

Probably the best way to write this in Python 2.x
--- although it's marginally slower --- is as
follows:

def __coerce__(self, other): return self.arg, other

Of course, this probably doesn't matter; it isn't
very likely that someone will try to inherit from
CoerceNumber or add a CoerceNumber instance to an
instance of some other similar type, since it is,
after all, only a test case.

(__coerce__ is kind of a tough routine to write,
in general, though, and seems like one place where
dispatching on the type of other arguments might
be the least of all possible evils.)

More instances of this are scattered through the
UserDict, UserList, and UserString modules.

Adding methods to someone else's classes
----------------------------------------

Sometimes one person wants to write a piece of
code that accesses some data in someone else's
class. So they check to see if the argument
they're being applied to is of the correct type,
and then proceed to mess with it in ways its
public interface doesn't allow.

Generally, this is asking for trouble down the
road, but it can be useful for short-term hacks,
or for code that is unlikely to change.

Sometimes people even do it to their own classes,
and that's just bad code. Here's an example from
distutils.command.build_ext:

# The python library is always needed on Windows. For MSVC, this
# is redundant, since the library is mentioned in a pragma in
# config.h that MSVC groks. The other Windows compilers all seem
# to need it mentioned explicitly, though, so that's what we do.
# Append '_d' to the python import library on debug builds.
from distutils.msvccompiler import MSVCCompiler
if sys.platform == "win32" and \
not isinstance(self.compiler, MSVCCompiler):
template = "python%d%d"
if self.debug:
template = template + '_d'
pythonlib = (template %
(sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff))
# don't extend ext.libraries, it may be shared with other
# extensions, it is a reference to the original list
return ext.libraries + [pythonlib]

This stuff should probably live in a method of the
compiler object, so you can say
return self.compiler.get_libraries(ext.libraries)
but possibly the best solution is to say
if sys.platform == "win32" and not self.compiler.ismsvc():
or even
if sys.platform == "win32" and not hasattr(self.compiler, 'msvc'):

Writing regression tests
------------------------

If you implement an __add__ method that returns a
new instance of the same class, and you decide to
change it to return an instance of a different
class, you should probably modify your regression
tests to cover the new class. So it's perfectly
reasonable for them to fail in this case.
However, they should probably compare __class__
rather than using isinstance().

For no apparent reason
----------------------

I've seen isinstance used to check whether
something was None or a real object, and I've seen
it used in places where it apparently had no real
effect.

reload
------

Python's reload() function reloads modules of code
into the running interpreter, re-executing their
contents. This generally results in functions and
classes in those modules being replaced with fresh
versions, often identical fresh versions.
References to those functions and classes
elsewhere are not replaced, however, so old
objects retain their old classes.

The result is that the following code prints 0
twice, assuming that everything works perfectly:
import somemod
x = somemod.someclass()
reload(somemod)
print isinstance(x, somemod.someclass)
y = somemod.someclass()
print isinstance(y, x.__class__)

This means that any code whose correctness depends
on being able to tell that x is an instance of
somemod.someclass will fail in the presence of
reloading, and any code whose performance depends
on it will perform poorly in the presence of
reloading.

Prevalence of isinstance in the standard library
------------------------------------------------

It appears to be recognized that isinstance is
usually a bad idea; rather like Euclid's parallel
postulate, the proof of this is in the frequency
of its use.

In Python 2.1.1, there are 98586 lines in 447 .py
files in the standard library; 68 of those lines
mention 'isinstance'.

19 of those 68 are in the test suite, which
contains 19364 of the total lines; seven of these
(10% of the total standard-library use!) are
verifying that isinstance() itself works.

24 of these are in User*.py, which uses isinstance
to see if it's safe to bypass public interfaces
and use other objects' data members directly.
They should use try-except or hasattr instead.


Jason Orendorff

unread,
Jan 23, 2002, 8:36:44 PM1/23/02
to
Kragen Sitaker wrote:
> isinstance() considered harmful

I've been thinking about this, and I'd be a lot happier
with it if:

(a) code using "try: ... except AttributeError:" were even
remotely as clear as code using isinstance();
(b) it left room for writing much faster Python code in a
future version, the way explicit interfaces do; and
(c) there were a way to make sure that "x.seek" is really
a file.seek() method, and not something else.

I like the potential performance upside of static typing in
Python.

I wish I could say "if supports(object, wfile.write): ..."
instead of "if hasattr(object, 'write'): ..."
or the equivalent try/except/else incantation.

(In this pipe dream, wfile is a "builtin interface" that has
descriptors for write, writelines, flush, close, seek/tell,
softspace, and so on.)

Note also that the distutils policy of requiring extensions
to subclass Distribution has an important purpose: to ensure
that future versions of distutils can add methods to
Distribution, and Command can then safely use those methods.

## Jason Orendorff http://www.jorendorff.com/

Kragen Sitaker

unread,
Jan 24, 2002, 2:03:24 AM1/24/02
to
"Jason Orendorff" <ja...@jorendorff.com> writes:
> Kragen Sitaker wrote:
> > isinstance() considered harmful
>
> I've been thinking about this, and I'd be a lot happier
> with it if:
>
> (a) code using "try: ... except AttributeError:" were even
> remotely as clear as code using isinstance();

hasattr isn't too much worse, especially if the attribute is something
like fileno or keys. But it is less clear.

Generally, though, even the try: except AttributeError: code is a
mistake. There are exceptions.

> (b) it left room for writing much faster Python code in a
> future version, the way explicit interfaces do; and

Can you elaborate? Are you talking about vtables?

> (c) there were a way to make sure that "x.seek" is really
> a file.seek() method, and not something else.

Avoiding name collisions, you mean? I think that's a very reasonable
thing to want.

> I like the potential performance upside of static typing in
> Python.

Well, there are three proven ways to get fast compiled code by having
the compiler know what types it's compiling code for:
- you can have static types, either declared like C or inferred like ML
- you can do dynamic code optimization and specialization like Java and Self
- you can have optional type annotations like Common Lisp

There's also a fourth way, Stalin-style type inference, which doesn't
work as well as I'd like in my experience --- it's hard to tell why
the compiler is too stupid to understand that a particular piece of
code could be compiled much more efficiently and what you should
change in the source code to fix it.

> I wish I could say "if supports(object, wfile.write): ..."
> instead of "if hasattr(object, 'write'): ..."
> or the equivalent try/except/else incantation.

That would be really nice.

> Note also that the distutils policy of requiring extensions
> to subclass Distribution has an important purpose: to ensure
> that future versions of distutils can add methods to
> Distribution, and Command can then safely use those methods.

I understand that this is desirable, but I think there are usually
(always?) better ways to handle this.

ko...@aesaeion.com

unread,
Jan 24, 2002, 2:51:46 AM1/24/02
to
On 23 Jan 2002, Kragen Sitaker wrote:

> To determine whether an object supports an interface
> ----------------------------------------------------
>
> Using isinstance() to determine whether an object
> supports a particular interface is always a bad
> idea. You are, in essence, including inheritance
> from a particular class in the interface; if
> anyone wants to implement that interface, they
> must derive their class from the class you
> specify, which means they'll inherit its bugs,
> they'll probably have to understand its
> invariants, and, in Python, they'll have to be
> careful not to collide with names it defines.
> (This is especially bad if one of those names is
> __getattr__ or __setattr__, but that's another
> problem.)
>

However I do see it as useful to make base classes that esentially do
nothing but instead encapsulate some kind of behavior so that you can use
isinstance to test if an object implements that kind of behavior.
Otherwise how would you test if an object was able to work in a listish type
way. You don't want to check every method needs to indexing, len etc when
you work with the object all the time however if you put all those methods in
a base class with just pass for an implementation then you can test for an
insinstance of that class and find out very quickly if it is some kind of
list capable object.

I use this in zope a fair bit since I can easily check if an object is
catalogaware, persistent etc. Since each of those items actually
encomapasses a fair number of methods I don't really see any fast way to
check for all of these existance during the time I have to draw the
webpage and hand it to a user.

> It's not just overly conservative; it's also
> overly liberal. Someone can override methods in
> the interface with broken methods --- in Python,
> they can even override them with non-callable
> objects. An object's derivation from a particular
> class doesn't guarantee that it implements all the
> protocol that class does. (Still, breaking
> protocols your base classes implement is almost
> always a bad idea.)
>

Overall I have final say over all code that we use so someone breaking how
things work in an inherited class doesn't happen.


Alex Martelli

unread,
Jan 24, 2002, 3:50:24 AM1/24/02
to
<ko...@aesaeion.com> wrote in message
news:mailman.1011858574...@python.org...
...

> way. You don't want to check every method needs to indexing, len etc when
> you work with the object all the time however if you put all those methods
in
> a base class with just pass for an implementation then you can test for an

You don't need EVERY method -- you only need the methods you need. If
you never call x.extend, for example, why put x's author to the useless
burden of implementing extend?

Most of the time, "just try to use it that way, catch or propagate the
exceptions if any" works, and it's simple and highly Pythonic. When you
need some kind of "atomicity of state change" (you don't want a couple
of calls to e.g. append to succeed and then a later call to extend to
fail, you need them to work all-or-none), you can try Accurate-LBYL,
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52291 . Between
them, the simple and ALBYL approaches remove most need for type-testing
and free you to enjoy Python's wonderful signature-based-polymorphism
approach, aka, "if it quacks like a duck".

We can do better. The dream scenario from my POV would be the passing
of http://python.sourceforge.net/peps/pep-0246.html, the Object
Adaptation PEP. But it seems it's almost a year nobody's even been
looking at it, so I wouldn't hold my breath.

Another useful idea (whose time has not quite come yet, but might
be drawing near) we can take from Haskell's typeclasses. A Haskell
typeclass differs from, e.g., a Java interface, in these essential
ways:
a) a typeclass is not limited to asserting, like an interface,
"methods A, B and C exist with these signatures". Rather,
it can predicate HOW a method, say A, is typically to be
implemented in terms of the others, B and C... *and vice
versa* -- how B is implemented in terms of A and C, etc.

You can see this as a generalization of the Template
Method design patternt that does not arbitrarily have to
guess at one method as "more fundamental" than others.
Note that this typically leaves a typeclass with 'loops
of dependency' which must be broken to 'implement' it.

b) a typeclass T can be asserted of an existing class C retroactively
and non-invasively, just by supplying implementations of
some subset of T's methods in terms of C's methods (and/or
other functions etc), such that all loops of dependency
are broken.

In Haskell, statically typed and compiled, it's the compiler that
takes on the burden of verifying b). In Python, it would be a
very natural role of the object-adaptation infrastructure that
must underly the proposed function adapt() of PEP 246. To
summarize PEP 246, one would do:
objectouse = adapt(objectpassed, thetypeclassneeded)
getting a TypeError of adaptation is not feasible, the
objectpassed itself if it already meets the protocol of the
typeclass, and some suitable wrapper or augmentation thereof
if it can be made to meet thetypeclassneeded but only that way.

Let me give a tiny example of typeclass use, in the "list
protocol" context. We might want to do something like:

class likealist:
def append(self, item):
self.extend([item])
def extend(self, sequence):
for item in sequence:
self.append(item)

and so on. We need some more cleverness to add further methods
"the right typeclass way", e.g. to say that if extend exists,
__add__ can be just like extend, and, vice versa, if __add__
exists, then extend can be just like it.

But I hope the point is clear even in this toy-level example.
As 'likealist' stands, I could 'inject' it right into any
existing instance of a classic-class, e.g.:

def bruteforce_adapt(aninstance, atypeclass):
theoldclass = aninstance.__class__
anewclass = new.classobj(theoldclass.__name__,
(theoldclass,atypeclass), {})
return new.instance(anewclass, aninstance.__dict__)

or, somewhat less brutally [e.g. return aninstance unchanged
if already isinstance(aninstance, atypeclass)], but that's
not the issue. Rather, the issue is that, if aninstance
implements neither append nor extend methods, still the
bruteforce_adapt(aninstance, likealist) "succeeds"... and
gives me an object that does implement both methods in an
unending, and quite useless, mutually recursive way. The
first call to either method will burn a few cycles, then
raise a RuntimeError for "maximum recursion depth
exceeded"... not very productive. In this "brute force"
approach, there is no checking that the "loops of dependency"
are indeed broken by 'concrete' methods, so that TypeError
can be raised if adaptation is unfeasible.


I think Python already has all needed tools to implement
some rough version of typeclasses -- although it will remain
a sterile exercise without PEP-246 like infrastructure to
hang it on. PEP 246 the kind of thing that only becomes useful
if some libraries and frameworks start using it... and they
won't unless it's accepted in core Python. Oh well!


Alex

Alex Martelli

unread,
Jan 24, 2002, 4:42:33 AM1/24/02
to
"Jason Orendorff" <ja...@jorendorff.com> wrote in message
news:mailman.101183655...@python.org...
...

> I wish I could say "if supports(object, wfile.write): ..."
> instead of "if hasattr(object, 'write'): ..."
> or the equivalent try/except/else incantation.

Much better still would be having PEP 246, and code:

writemethod = adapt(object, wfile_protocol).write

You'd get a TypeError here if object was not adaptable to
the wfile_protocol. Or, you could pass a third argument
to adapt and have it returned (rather than an exception
raised) in case adaptation is impossible, thus enabling
other useful idioms. *sigh* how I dream of PEP 246...


> (In this pipe dream, wfile is a "builtin interface" that has
> descriptors for write, writelines, flush, close, seek/tell,
> softspace, and so on.)

In my vision, the protocols would be a bit more granular
than that. For example, ability to tell and seek would
not be in the same protocol as those connected to writing
nor of those connected to reading. If protocols are often
to be joined, some future incarnation of adapt could grow
to help that, while splitting a protocol into sensible
pieces is harder, and fat interfaces have some issues.

In my dreams, wfile_protocol might be something like (it
might well be built-in, but equivalent to Python source):

class wfile_protocol(Typeclass):

# one of these must be overridden (mutual dependency):
def write(self, data):
self.writelines([data])
def writelines(self, lines):
for data in lines:
self.write(data)
__mutual_dependencies__ = ( (write, writelines), )

# methods that may but need not be overridden:
def flush(self): pass
def close(self): pass


Alex

Jason Orendorff

unread,
Jan 24, 2002, 2:14:45 PM1/24/02
to
Kragen Sitaker wrote:
> Generally, though, even the try: except AttributeError: code is a
> mistake. There are exceptions.

Yes, I agree with that.

> > (b) it left room for writing much faster Python code in a
> > future version, the way explicit interfaces do; and
>
> Can you elaborate? Are you talking about vtables?

Yes.

> Well, there are three proven ways to get fast compiled code by having

> the compiler know what types it's compiling code for: [...]


>
> There's also a fourth way, Stalin-style type inference,

I didn't know there was a programming language named after Stalin.
How does it differ from Java?

I'll talk more about interfaces later. My question is,
are explicit interfaces considered harmful too?

Kragen Sitaker

unread,
Jan 24, 2002, 5:07:05 PM1/24/02
to
Kosh writes:
> However I do see it as useful to make base classes that esentially
> do nothing but instead encapsulate some kind of behavior so that you
> can use isinstance to test if an object implements that kind of
> behavior. Otherwise how would you test if an object was able to
> work in a listish type way. You don't want to check every method

> needs to indexing, len etc when you work with the object all the
> time however if you put all those methods in a base class with just
> pass for an implementation then you can test for an insinstance of

> that class and find out very quickly if it is some kind of list
> capable object.

Well, if you use it as if it were a list, and it isn't, it will raise
an exception. Which I assume is what you do if the isinstance() test
fails, right? The only difference is that the exception will be an
AttributeError: __len__ or something like that, instead of whatever
hopefully more specific exception you raise if your isinstance()
fails, which will hopefully make it easier for the person who wrote
the calling code to fix things.

You can achieve the same thing by replacing
length = len(somearg)
with
try: length = len(somearg)
except AttributeError: raise TypeError, "somearg must be a sequence"

Implementing all those methods in a base class with "pass" is a really
stupid idea, unless it's semantically correct for that method to do
nothing, because it turns broken code that would raise exceptions into
broken code that silently gives wrong answers. If someone implements,
say, append() but not extend(), and their object gets passed to a
routine that does both, then that routine will think it has added
stuff with both append() and extend(), but actually only the stuff it
added with append() will get there. And you'll be debugging (or maybe
trying to explain to a customer why they got the wrong answer) to try
to figure out where those other values went.

> I use this in zope a fair bit since I can easily check if an object
> is catalogaware, persistent etc. Since each of those items actually
> encomapasses a fair number of methods I don't really see any fast
> way to check for all of these existance during the time I have to
> draw the webpage and hand it to a user.

Well, if it's OK for a particular object not to be catalogaware, you
can say:

try: do_something_involving_cataloging(theobject)
except: pass

Generally I hate except: pass, but it expresses what you want in this
case.

> > It's not just overly conservative; it's also
> > overly liberal. Someone can override methods in
> > the interface with broken methods --- in Python,
> > they can even override them with non-callable
> > objects. An object's derivation from a particular
> > class doesn't guarantee that it implements all the
> > protocol that class does. (Still, breaking
> > protocols your base classes implement is almost
> > always a bad idea.)
>
> Overall I have final say over all code that we use so someone
> breaking how things work in an inherited class doesn't happen.

If that's the case, then someone passing in an argument that isn't the
right type won't happen either.


Kragen Sitaker

unread,
Jan 24, 2002, 5:07:05 PM1/24/02
to
Jason Orendorff writes:
> > There's also a fourth way, Stalin-style type inference,
>
> I didn't know there was a programming language named after Stalin.
> How does it differ from Java?

Stalin is not a programming language but a compiler --- for Scheme, to
be specific. It can often compile Scheme code into C code that runs
very nearly as fast as if you had written the program in C in the
first place, but when it doesn't, it isn't easy to figure out why.

Scheme is a purely dynamically typed language with no type
declarations, much like today's Python.

> I'll talk more about interfaces later. My question is,
> are explicit interfaces considered harmful too?

I really like the flexibility in Python that *any* value --- with a
few exceptions --- can be of any type that implements the right
interface. Explicit interfaces could provide this level of
flexibility, but in Java, they don't.


Jason Orendorff

unread,
Jan 24, 2002, 11:13:16 PM1/24/02
to
Alex Martelli wrote:
> You don't need EVERY method -- you only need the methods you need. If
> you never call x.extend, for example, why put x's author to the useless
> burden of implementing extend?

-1

This makes your function less of an abstraction. The caller
now needs to know exactly which methods you're going to use.

Better to define an interface and ask people to implement it.

I like interfaces with default implementations:

interface wfile:
def write(self):
pass
write = abstractmethod(write) # syntax could be better (C:

def writelines(self, lines):
for line in lines:
self.write(line)

def flush(self):
pass

[However, I can do without recursive definitions. Usually,
one operation is the primitive and the others are sugar.
But even if not, it is still redundant to define x() in terms of
y() and also define y() in terms of x(). Either definition
alone suffices to illustrate the relationship.]

Raymond Hettinger

unread,
Jan 25, 2002, 12:27:31 AM1/25/02
to
Since I've made many uses of isinstance(), Kragen Sitaker's post inspired me
to scan my code and review each occurance.

I found three categories:
1) isinstance is necessary and helpful
2) isinstance needed to be replaced with hasattr()
3) isinstance() was the least worst alternative in the absence of PEP-246.

Helpful and Necessary Cases:
---------------------------------

1. Since Python does not directly support multiple-dispatch like CLOS and
Dylan, isinstance() provides a way to handle multiple argument types. For
instance, Mat.__mul__(self, other) behaves differently depending on whether
'other' is a scalar, a vector, or another matrix. isinstance(other,Mat) and
isinstance(other,Vec) were used to switch methods -- note that Vec and Mat
have many of the same methods and are not readily differentiated by their
attributes.

2. Several pieces of code used the Composite pattern and needed a way to
differentiate between complex and atomic arguments. The most basic and
common case occurs in code recursing through nested lists --
isinstance(elem, list) provided the necessary test (try solving basic LISP
problems without a test to discriminate between atoms and lists). A more
complex version of this case occurs when the container is a user defined
class rather than built-in type (sort of a Russian dolls phenomenon).

3. Adding functionality with a Decorator pattern, it's easy to end up
re-wrapping an object more than once. To avoid multiple wraps, it's helpful
to use isinstance() to see if the argument is already of the right type. In
my code, the Table class added elementwise operations to built-in lists.
Everywhere I needed a Table, the user could have easily supplied either a
Table or a List, so used something like: arg = isinstance(arg,Table) and
arg or Table(arg).

4. Some code needs to fork paths when the class indicates an intrinsic
quality not revealed by the attributes. Implementing matrix exponentation,
__pow__(self,exponent), efficiently requires testing
isinstance(exponent,int) because the float and complex cases are to be
computed differently.

In the same package, lower triangular matrices were a sub-class of
generalized matrices. Occasionally, it was necessary to assert that a
matrix was of a certain type eventhough it supported exactly the same
methods as general matrices (for instance the Mat.LU method carries an
assertion, isinstance(L, LowerTri)).


Cases Where hasattr() is Better:
------------------------------------
1. Supplying a default result when the required methods are not available
for all possible argument types. For example, .conjugate() is only
available for complex numbers but has meaning for other numeric types.
Defining myConj(z) as: hasattr(z,'conjugate') and z.conjugate() or z.
Similar definitions can be supplied for .imag and .real so that any numeric
type will "do the right thing" when fed to the functions. For example:
map( myConj, [ 3+4j, 5, 6.0, 7L ] )

2. In a similar vein, I needed to make sure that an iteration interface
was supplied even when xrange or sequence types were supplied as arguments:
gens = [ type(s)==types.GeneratorType and s or iter(s) for s in
sequences]
Was better replaced by:
gens = [ hasattr(s,'next') and s or iter(s) for s in sequences]


Case Where isinstance() Was the Least Worst Alternative
-----------------------------------------------------------------
In the following code, the goal is to accept a regular expression pattern in
the form of a string or in the form of an already compiled expression.
isinstance() gives a direct way of checking to see if a string was supplied.
hasattr(pattern,'search') was a weak alternative because it did not check
for other attributes such as .lastgroup, .group, and .end. Wrapping the
whole thing in a try/except was ugly for two reasons. First, to handle the
exception, the argument would have to be coerced and then the whole function
either needed to be repeated or recursed. Second, 'except AttributeError:'
is dangerous in that it also traps other programming errors which should be
made visible -- for instance, misspelling pattern.seeearch() would be
trapped eventhough you would want to know that there is no such attribute.

def tokenize(pattern, string=''):
if isinstance(pattern, str):
pattern = compile(pattern)
m = pattern.search(string, 0)
while m:
yield (m.lastgroup, m.group(m.lastgroup))
m = pattern.search(string, m.end())


Raymond Hettinger

"Kragen Sitaker" <kra...@pobox.com> wrote in message
news:83n0z44...@panacea.canonical.org...
> isinstance() considered harmful
> ===============================
[snipped]


> \(b Every use of isinstance is a violation of this
> promise), large or small. Whenever isinstance is
> used, control flow forks; one type of object goes
> down one code path, and other types of object go
> down the other --- even if they implement the same
> interface!

>

Jason Orendorff

unread,
Jan 25, 2002, 2:34:18 AM1/25/02
to
Raymond Hettinger wrote:
> 2. In a similar vein, I needed to make sure that an iteration interface
> was supplied even when xrange or sequence types were supplied as
> arguments:
> gens = [ type(s)==types.GeneratorType and s or iter(s) for s in
> sequences]
> Was better replaced by:
> gens = [ hasattr(s,'next') and s or iter(s) for s in sequences]


I think you'll find that "iter(s) is s" for iterator objects. So:
gens = map(iter, sequences)


> In the following code, the goal is to accept a regular expression
> pattern in the form of a string or in the form of an already

> compiled expression. [...]


>
> def tokenize(pattern, string=''):
> if isinstance(pattern, str):
> pattern = compile(pattern)
> m = pattern.search(string, 0)
> while m:
> yield (m.lastgroup, m.group(m.lastgroup))
> m = pattern.search(string, m.end())

I think hasattr(pattern, 'search') would have worked here.
If it has a search method, you presume it's a regex; a working
regex will return match objects that have lastgroup.

Anyway, by using isinstance(pattern, str) you're losing
unicode support.

How about...

def tokenize(pattern, string='', flags=0):
if not hasattr(pattern, 'iterfind'):
pattern = re.compile(pattern, flags)
for m in pattern.iterfind(string):
yield (m.lastgroup, m.group(m.lastgroup))

Interesting commentary overall; thanks.

Alex Martelli

unread,
Jan 25, 2002, 4:41:14 AM1/25/02
to
"Raymond Hettinger" <oth...@javanet.com> wrote in message
news:a2qqd1$7q6$1...@bob.news.rcn.net...

> Since I've made many uses of isinstance(), Kragen Sitaker's post inspired
me
> to scan my code and review each occurance.

Very interesting and instructive: thanks! Sometimes one is prone to make
general observations just because they apply in (say) 99% of the cases, but
thorough analysis such as yours may reveal the wider purview.

For example, one might reasonably assert "don't smoke, it's always bad
for your health". However, right after an earthquake in Central Italy
a few years ago, one of the art experts who were examining the damage
in an Assisi church took a cigarette break and stepped out of the
church, where her colleagues were still examining the damaged frescoes
while perched up on some rigged ladders etc. She was the only one to
survive, as another earthquake hit right then and collapsed the ladders,
killing the other experts. If you're about to be shot dead by a firing
squad in some foreign country, and you're offered a last cigarette, you
might reasonably take it -- maybe an order for a stay of execution will
arrive just in the minute or two you gain by accepting it, after all.

So, 'always' is a tad too strong here. Similarly, 'never' is too
strong in the admonition 'never typetest in Python', and you do a
great job of ferreting out some cases where 'never' is too strong.
I don't entirely agree with all of your specific conclusions, and
I'm going to quibble about some, but, you DO get me to admit that
SOME uses of typetesting are preferable to some alternatives, which
IS quite a feat:-).


> I found three categories:
> 1) isinstance is necessary and helpful
> 2) isinstance needed to be replaced with hasattr()
> 3) isinstance() was the least worst alternative in the absence of PEP-246.
>
> Helpful and Necessary Cases:
> ---------------------------------
>
> 1. Since Python does not directly support multiple-dispatch like CLOS and
> Dylan, isinstance() provides a way to handle multiple argument types. For

Even as a sworn enemy of type-testing, I have to concede this one: when
I need to synthesize multi-dispatch, I do typetest (via isinstance or
issubclass, that's quite a minor issue). Which is part of why I try to
avoid using multi-dispatch in languages that don't support it (C++'s
"Visitor" horrors, type-testing in Python, ...) -- but sometimes it's
still the most viable architecture despite the unsighly plumbing.

> 2. Several pieces of code used the Composite pattern and needed a way to
> differentiate between complex and atomic arguments. The most basic and
> common case occurs in code recursing through nested lists --
> isinstance(elem, list) provided the necessary test (try solving basic LISP
> problems without a test to discriminate between atoms and lists). A more
> complex version of this case occurs when the container is a user defined
> class rather than built-in type (sort of a Russian dolls phenomenon).

You need a way to code isatom(x), yes. But isinstance is not the
optimal way in my experience. Potentially more helpful:

def issequencelike(x):
try:
for item in x: break
except: return 0
else: return 1

def isstringlike(x):
try:
x+''
except: return 0
else: return 1

def isatom(x):
return isstringlike(x) or not issequencelike(x)

These can no doubt be improved, but isinstance would not substantially
improve them -- why not let clientcode use UserStrings in lieu of
strings, UserLists in lieu of lists, and so on?


> 3. Adding functionality with a Decorator pattern, it's easy to end up
> re-wrapping an object more than once. To avoid multiple wraps, it's
helpful
> to use isinstance() to see if the argument is already of the right type.
In
> my code, the Table class added elementwise operations to built-in lists.
> Everywhere I needed a Table, the user could have easily supplied either a
> Table or a List, so used something like: arg = isinstance(arg,Table) and
> arg or Table(arg).

If you _add_ functionality, hasattr seems just about right. If you
_modify_ functionality, so that the Decorated object has no extra
attributes wrt the unDecorated ones, then that doesn't help -- and
if you do all your Decoration with [subclasses of] the same Decorator
class, then typetesting for that is reasonable here.

Choosing typetesting in this case may impede framework augmentation
"from the outside". If client-code needs to provide some kind of
SuperTable, it must subclass it from Table, which may mean carrying
around unwanted baggage. One approach (to keep typetesting) is to
make Table into a no-extra-baggage abstract class -- either a pure
interface, or an interface plus some helpful Template methods, or
a full-fledged Haskell-like typeclass, but still without possibly
unwanted data attributes. Another possibility might be to add to
Table an attribute (_yesiamalreadydecoratedthankyouverymuch would
seem to be the natural name for it) and replace the typechecking
with a check for that attribute: this still lets client-code choose
to subclass Table, but offers client-code the extra possibility of
reimplementing its own decorators from scratch -- it just has to
supply that 'flag' attribute as well to assert it knows what it's
doing. Admittedly, such a measure of generality may be excessive
and unwarranted, but it's a possibility to keep in mind when a
framework of this kind is evolving -- even if you strictly relied
on typetesting in version N, you can provide backwards-compatible
greated flexibility in version N+1 by switching to using a flag
attribute instead.


> 4. Some code needs to fork paths when the class indicates an intrinsic
> quality not revealed by the attributes. Implementing matrix
exponentation,
> __pow__(self,exponent), efficiently requires testing
> isinstance(exponent,int) because the float and complex cases are to be
> computed differently.

Wouldn't we want in this case to treat X**1.0 the same as X**1 the
same as X**1L...? A test such as exponent==int(exponent) would
then seem to be more general and useful than isinstance(exponent,int).


> In the same package, lower triangular matrices were a sub-class of
> generalized matrices. Occasionally, it was necessary to assert that a
> matrix was of a certain type eventhough it supported exactly the same
> methods as general matrices (for instance the Mat.LU method carries an
> assertion, isinstance(L, LowerTri)).

Admittedly a harder case. Optimization doth make typetesters of us
all. If hasattr does not apply, and along a bottleneck of the code
a frequent and important special case may be shunted along a different
and faster computational track by typetesting, I'll accept typetesting
as one of the many complications, obfuscations and rigidifications one
may endure in optimization's name (I would expect the general case to
be handled along another slower path, but I'll accept you had good
reasons to raise an assertion error instead in your code).


> Cases Where hasattr() is Better:
> ------------------------------------
> 1. Supplying a default result when the required methods are not available
> for all possible argument types. For example, .conjugate() is only
> available for complex numbers but has meaning for other numeric types.
> Defining myConj(z) as: hasattr(z,'conjugate') and z.conjugate() or z.
> Similar definitions can be supplied for .imag and .real so that any
numeric
> type will "do the right thing" when fed to the functions. For example:
> map( myConj, [ 3+4j, 5, 6.0, 7L ] )

A very good point, and of wide applicability. Many Python built-ins work
conceptually along this pattern. For example, testing X for truth checks
X for a __nonzero__ method to call, and proceeds to other cases if no such
method is available.

As a quibble, this case is often better handled by try/except rather
than if/else -- and I don't think that squashing the test into one
expression is worth it. Take exactly the example you suggest:

def myConj(z): return hasattr(z,'conjugate') and z.conjugate() or z

this only works because, if z.conjugate() is false, then z==z.conjugate().
When generalized, e.g., to .real, it breaks down:

>>> def myReal(z): return hasattr(z,'real') and z.real or z
...
>>> print myReal(0+4j)
4j

"Oops". An if/else or try/except doesn't run such risks:

def myReal1(z):
if hasattr(z, 'real'):
return z.real
else:
return z

or

def myReal2(z):
try:
return z.real
except AttributeError:
return z

The try/except form avoids coding the name 'real' in two places -- maybe
that is the reason it appeals so much more to me (allergy against coding
the same thing twice being even stronger in me than dislike for
typetesting:-).

More generally I don't like the structure

if <there is a solution>:
return <find the solution>
else:
return <indicator of 'no solution exists'>

and similar ones. Determining whether there is a solution at all
is often tantamoung to finding it, after all, so getting used to
coding this way implies that one will sometimes do double work
or end up with subtle, mysterious caching schemes whereby the
<there is a solution> function squirrels away the solution for
the benefit of the following <find the solution> call. I think
it's more straightforward to have <find the solution>'s contract
be: return the solution, or, if there is none, then raise an
appropriate "there is no solution" exception; then, code
try:
return <find the solution>
except <there is no solution>:
return <indicator of 'no solution exists'>

i.e., "easier to get forgiveness than permission" rather than the
widespread "look before you leap" approach.


> 2. In a similar vein, I needed to make sure that an iteration interface
> was supplied even when xrange or sequence types were supplied as
arguments:
> gens = [ type(s)==types.GeneratorType and s or iter(s) for s in
> sequences]
> Was better replaced by:
> gens = [ hasattr(s,'next') and s or iter(s) for s in sequences]

Functions that are idempotent when applied to already-OK arguments
are preferable -- just as I wouldn't code:

x = isinstance(x, tuple) and x or tuple(x)

even apart from issues of and/or, and typechecking, but just

x = tuple(x)

knowing that this does return x if x is already a tuple.

Similarly, in your case,
gens = map(iter, sequences)
should work just fine! We don't need to worry about whether s
can be false in the and/or construct, etc, etc.

"Potentially-idempotent adapter functions are a honking great
idea -- let's do more of those", to paraphrase the timbot. PEP 246
can be seen in that light, too: function adapt(object, protocol)
returns object if object ALREADY satisfies the protocol.


> Case Where isinstance() Was the Least Worst Alternative

Actually, I would tend to classify typetesting as a "least of evils" even
in the cases above where I've agreed that other alternatives are worse:-).


> In the following code, the goal is to accept a regular expression pattern
in
> the form of a string or in the form of an already compiled expression.

But we'd also like to accept a UserString or other string-workalike
object, without forcing the user to call us with str(pattern) in
those cases, no? And Unicode objects? And...?

> isinstance() gives a direct way of checking to see if a string was
supplied.

But the above-coded function isstringlike would do the job better.

However, there IS an approach that is even better, and we have
just finished talking about it: *potentially idempotent adapting
functions*! re.compile just happens to be one of those, thanks be:

>>> a=re.compile(r'\d+')
>>> b=re.compile(a)
>>> a is b
1

So just code
pattern = re.compile(pattern)
and live happily ever after.

PEP 246 would give a systematic approach to this, but until and unless
it's passed, we can still use the specific cases where this works.


I think this discussion is highlighting TWO key points:

a) there ARE cases where typetesting is the least of evils in Python today
(rare and marginal ones, but, they DO exist);

b) if you think you have found one such case, look again, and reflect a lot
about it, because it may well LOOK to you like you've found one, when in
fact you're still better off without typetesting, through any of the
several alternatives discussed here;

c) PEP 246 would help a lot [well, OK, THREE key points...].


Alex

Kragen Sitaker

unread,
Jan 25, 2002, 6:10:41 AM1/25/02
to
"Raymond Hettinger" <oth...@javanet.com> writes:
> Helpful and Necessary Cases:
> ---------------------------------
>
> 1. Since Python does not directly support multiple-dispatch like CLOS and
> Dylan, isinstance() provides a way to handle multiple argument types. For
> instance, Mat.__mul__(self, other) behaves differently depending on whether
> 'other' is a scalar, a vector, or another matrix. isinstance(other,Mat) and
> isinstance(other,Vec) were used to switch methods -- note that Vec and Mat
> have many of the same methods and are not readily differentiated by their
> attributes.

Yeah, I've done this too. My __coerce__ example was kind of treading
on thin ice for this reason --- I think there are lots of times you
need multiple dispatch for optimal numeric coercion, although I don't
think they generally show up with Python's native numeric types.

> 2. Several pieces of code used the Composite pattern and needed a way to
> differentiate between complex and atomic arguments. The most basic and
> common case occurs in code recursing through nested lists --
> isinstance(elem, list) provided the necessary test (try solving basic LISP
> problems without a test to discriminate between atoms and lists). A more
> complex version of this case occurs when the container is a user defined
> class rather than built-in type (sort of a Russian dolls phenomenon).

Yep, if you're writing things Lispishly you need isinstance(). If the
code that takes an argument that could either be atomic or complex has
only one such argument, you could make it a method of the atomic or
complex data, which is what I often do --- but since Python doesn't
let you add new methods to built-in types the way CLOS does (I think?
I don't know CLOS) that only works if the items *aren't* built-in
lists or strings, and your code is often a lot simpler if they are.

> 3. Adding functionality with a Decorator pattern, it's easy to end up
> re-wrapping an object more than once. To avoid multiple wraps, it's helpful
> to use isinstance() to see if the argument is already of the right type. In
> my code, the Table class added elementwise operations to built-in lists.
> Everywhere I needed a Table, the user could have easily supplied either a
> Table or a List, so used something like: arg = isinstance(arg,Table) and
> arg or Table(arg).

(side note: x and y or z is different from x ? y : z if y is false!)

My usual Python solution for this uses hasattr(), and in fact usually
tries to call a method on the object to be wrapped --- the wrapper
objects simply return themselves with this method, while other objects
will tend not to have this method, so I fall back to wrapping them.

> 4. Some code needs to fork paths when the class indicates an intrinsic
> quality not revealed by the attributes. Implementing matrix exponentation,
> __pow__(self,exponent), efficiently requires testing
> isinstance(exponent,int) because the float and complex cases are to be
> computed differently.

Yeah, I don't have a better solution for that either.

> In the same package, lower triangular matrices were a sub-class of
> generalized matrices. Occasionally, it was necessary to assert that a
> matrix was of a certain type eventhough it supported exactly the same
> methods as general matrices (for instance the Mat.LU method carries an
> assertion, isinstance(L, LowerTri)).

I assume this is to verify that LU factorization did successfully
produce a lower-triangular matrix and the matrix factory function
recognized this? That sounds like an excellent reason for
isinstance() to me --- closely akin to regression testing.

> Cases Where hasattr() is Better:
> ------------------------------------

> ...


> 2. In a similar vein, I needed to make sure that an iteration interface
> was supplied even when xrange or sequence types were supplied as arguments:
> gens = [ type(s)==types.GeneratorType and s or iter(s) for s in
> sequences]
> Was better replaced by:
> gens = [ hasattr(s,'next') and s or iter(s) for s in sequences]

I'd just say gens = [ iter(s) for s in sequences ] --- iter() uses the
same strategy I described above for wrappers, even though an iterator
isn't a Decorator.

> Case Where isinstance() Was the Least Worst Alternative
> -----------------------------------------------------------------
> In the following code, the goal is to accept a regular expression pattern in
> the form of a string or in the form of an already compiled expression.

> ...


> Second, 'except AttributeError:' is dangerous in that it also traps
> other programming errors which should be made visible -- for
> instance, misspelling pattern.seeearch() would be trapped eventhough
> you would want to know that there is no such attribute.

Yeah, in cases like this, try/except is kind of a pain. When I want
to find out something specific, I try to put as little code in the try
block as possible, to minimize the chance of such false hits. But I
always worry.

> def tokenize(pattern, string=''):
> if isinstance(pattern, str):
> pattern = compile(pattern)

Well, re.compile will return the original RE object if you pass it an
RE object instead of a string, precisely for cases like this. But it
takes a while to do so --- about 23 microseconds on my laptop,
compared to about 1.6 microseconds to do a null function call. So
leaving out the if isinstance() check leaves you with more-correct
code (in this case, it accepts both Unicode and 8-bit strings) that is
simpler, but runs slightly slower. This sort of illustrates the point
of the original post. :)

Of course, re.compile is doing something much like isinstance --- in
Python 2.1, it's actually checking type(pattern) not in
sre_compile.STRING_TYPES.

Thank you for a very interesting and thought-provoking post.

Alex Martelli

unread,
Jan 25, 2002, 5:32:24 AM1/25/02
to
"Jason Orendorff" <ja...@jorendorff.com> wrote in message
news:mailman.101193231...@python.org...

> Alex Martelli wrote:
> > You don't need EVERY method -- you only need the methods you need. If
> > you never call x.extend, for example, why put x's author to the useless
> > burden of implementing extend?
>
> -1
>
> This makes your function less of an abstraction. The caller
> now needs to know exactly which methods you're going to use.

You need to document that, yes. Documenting that you use (or
reserve the right to use in the future) all the methods and
operations of a list is one possibility, but it's surely
not the only one.


> Better to define an interface and ask people to implement it.
>
> I like interfaces with default implementations:
>
> interface wfile:
> def write(self):
> pass
> write = abstractmethod(write) # syntax could be better (C:
>
> def writelines(self, lines):
> for line in lines:
> self.write(line)
>
> def flush(self):
> pass
>
> [However, I can do without recursive definitions. Usually,
> one operation is the primitive and the others are sugar.

Actually, it's more frequent that two (occasionally more)
operations need to be in a certain connection with each other,
but none of them is 'intrinsically' primitive wrt the others.

Having to arbitrarily pick one of .append and .extend as
"more primitive" in the definition of a list typeclass is
quite inelegant, for example.

> But even if not, it is still redundant to define x() in terms of
> y() and also define y() in terms of x(). Either definition
> alone suffices to illustrate the relationship.]

Theoretically, perhaps, if the 'definition' is invertible, a
superb and totally hypothetical compiler could infer the other
definition (by definition of 'invertible', I guess:-). In
practice, *explicit is better than implicit*: better to state
out the 'redundant' case and avoid all difficulty and
ambiguity for both the compiler and the human reader.

I guess this boils down to aesthetic style preferences, but it
seems to me that typeclasses (where default implementations
are allowed to be supplied for all methods in terms of others)
are superior to 'enriched interfaces' (where default
implementations are only allowed for a subset such that the
dependency graph has no loops) by about as much as such
enriched interfaces are superior to 'pure' ones (where default
implementations are disallowed, as e.g. in Java).


Alex

Alex Martelli

unread,
Jan 25, 2002, 7:25:23 AM1/25/02
to
"Jason Orendorff" <ja...@jorendorff.com> wrote in message
news:mailman.101190003...@python.org...
...

> > Well, there are three proven ways to get fast compiled code by having
> > the compiler know what types it's compiling code for: [...]
> >
> > There's also a fourth way, Stalin-style type inference,
>
> I didn't know there was a programming language named after Stalin.

It's a compiler for the programming language Scheme, and it's
intended to mean "steel" in Russian rather than refer to any
historical figure adopting that as a codename:-).

> How does it differ from Java?

http://www.swiss.ai.mit.edu/projects/scheme/

It's very hard to find anything in common between Scheme and Java.

Scheme and Python, despite totally-different syntax, do have
similarities, such as the lack of declarations (which makes
type inference technology developed for Scheme potentially
also interesting for Python).


> I'll talk more about interfaces later. My question is,
> are explicit interfaces considered harmful too?

In my viewpoint they have both pluses and minuses. Depending
on what I'm comparing with, and on what other technology and
infrastructure is available, the pluses may outweigh the minuses,
or vice versa. For example, in Python, if PEP 246 was
implemented, then 'explicit interfaces' would IMHO become
a wonderful way to express 'protocols'. Without PEP 246,
I think introducing interfaces might perhaps lead to a damaging
proliferation of typetesting and end up breaking more code
than it repairs -0- but maybe I'm just being pessimistic.


Alex

Alex Martelli

unread,
Jan 25, 2002, 7:03:34 AM1/25/02
to
"Kragen Sitaker" <kra...@pobox.com> wrote in message
news:mailman.1011910117...@python.org...
...

> > I'll talk more about interfaces later. My question is,
> > are explicit interfaces considered harmful too?
>
> I really like the flexibility in Python that *any* value --- with a
> few exceptions --- can be of any type that implements the right
> interface. Explicit interfaces could provide this level of
> flexibility, but in Java, they don't.

To provide this level of flexibility, you have to be able to
supply a post-facto adapter for interfaces and classes that
were developed separately, and arrange for it to be used.
This is really needed for implicit interfaces, too, in fact.

Say I have an interface, or its implicit equivalent:
class kotrable:
# semantic specs snipped
def kotr(self): raise NotImplementedError
defined/used in framework X, eg in the crucial function:
def eat(lotsofstuff):
for x in lotsofstuff:
result = x(23)
result.kotr()
implying each item in lotsofstuff is a callable that,
when called with argument 23, returns a kotrable instance;

and a class defined/generated in framework Y:
class almostkotrable:
# mucho stuff snipped
def practicallykotr(self): print 'almost right'
e.g, with factories such as:
def afactory(x): return almostkotrable()
def another(x,y=45):
if x>y: return None
else: return almostkotrable()
and so on.

Now, I'd like to call X.eat((Y.afactory, Y.another)) or
something of the kind. I can't, of course. I need to
wrap each factory callable in Y into an adapter to
transform the almostkotrable instances into full-fledged
kotrable instances (or convincing ersatzes thereof,
in as much as X doesn't typetest), e.g.:

class myadapter(kotrable, almostkotrable):
kotr = practicallykotr
def __init__(self, wrapped):
self.__dict__ = wrapped.__dict__

def adaptingwrapper(factory):
def adapted(*args):
return myadapter(factory(*args))
return myadapter

and then I can finally code:

X.eat((myadapter(Y.afactory), myadapter(Y.another)))


Unfortunately, not all creational situations lend themselves
to wrapping/adapting so easily. Wouldn't it be wonderful if,
when framework X needed something respecting its X.kotrable
protocol, it did:

def eat(lotsofstuff):
for x in lotsofstuff:
result = x(23)
result = adapt(result, kotrable)
result.kotr()

and I could just register myadapter with the protocol
adaptation framework as the way to have Y.almostkotrable
instances adapted into fully X.kotrable ones? Ah, what a
bliss it would be...!


This is basically what PEP 246 proposes. Unfortunately, it
seems to be "just sleeping there", since almost a year. I
wonder if or how it could be helped along...


Alex

Jason Orendorff

unread,
Jan 25, 2002, 12:02:07 PM1/25/02
to
> However, there IS an approach that is even better, and we have
> just finished talking about it: *potentially idempotent adapting
> functions*! re.compile just happens to be one of those, thanks be:
>
> >>> a=re.compile(r'\d+')
> >>> b=re.compile(a)
> >>> a is b
> 1

This is not a documented feature.

Raymond Hettinger

unread,
Jan 25, 2002, 1:10:54 PM1/25/02
to
"Alex Martelli" <al...@aleax.it> wrote in message
news:a2r97q$dpi$1...@serv1.iunet.it...

This is a tough design decision. The considerations are:
1. It would be great to keep open the possibility of a SuperTable.
2. hasattr() bugs me because I want to ensure that all of the expected
methods are available not just one. Also, passing the attribute name
as a string has a bad feel to it though I can't say why.
3. except AttributeError runs the risk of trapping real errors. It is too
all inclusive to be used safely as a means of making sure an object
has the required interface.
4. Of all of the uses of type testing, having a class be able to recognize
one of its own is amoung the least aggregious.
5. For all its faults, isinstance() has the virtue of clarity. Anyone
(including
myself) reading my code and finding isinstance(x,Table) will more
readily grasp intent of the code (preventing double decoration) than
they
would with hasattr() or except AttributeError.

Executive Summary: It would be darned nice if there were a straightforward
way of assuring a that a particular interface were available but not require
a
particular object class.

>
> > 4. Some code needs to fork paths when the class indicates an intrinsic
> > quality not revealed by the attributes. Implementing matrix
> exponentation,
> > __pow__(self,exponent), efficiently requires testing
> > isinstance(exponent,int) because the float and complex cases are to be
> > computed differently.
>
> Wouldn't we want in this case to treat X**1.0 the same as X**1 the
> same as X**1L...? A test such as exponent==int(exponent) would
> then seem to be more general and useful than isinstance(exponent,int).

Good idea, I made the change! This avoids an explicit type test while
recognizing that integers do have special properties and sometimes you
need to test for being integerlikeness.

> As a quibble, this case is often better handled by try/except rather
> than if/else -- and I don't think that squashing the test into one
> expression is worth it. Take exactly the example you suggest:
>
> def myConj(z): return hasattr(z,'conjugate') and z.conjugate() or z
>
> this only works because, if z.conjugate() is false, then z==z.conjugate().
> When generalized, e.g., to .real, it breaks down:
>
> >>> def myReal(z): return hasattr(z,'real') and z.real or z
> ...
> >>> print myReal(0+4j)
> 4j
>
> "Oops". An if/else or try/except doesn't run such risks:

I made this change right away. The and/or style is a bug!
Try/except or hasattr() both leave the interface open for UserComplex.

>
> > 2. In a similar vein, I needed to make sure that an iteration
interface
> > was supplied even when xrange or sequence types were supplied as
> arguments:
> > gens = [ type(s)==types.GeneratorType and s or iter(s) for s in
> > sequences]
> > Was better replaced by:
> > gens = [ hasattr(s,'next') and s or iter(s) for s in sequences]
>
> Functions that are idempotent when applied to already-OK arguments
> are preferable -- just as I wouldn't code:
>
> x = isinstance(x, tuple) and x or tuple(x)
>
> even apart from issues of and/or, and typechecking, but just
>
> x = tuple(x)
>
> knowing that this does return x if x is already a tuple.
>
> Similarly, in your case,
> gens = map(iter, sequences)
> should work just fine! We don't need to worry about whether s
> can be false in the and/or construct, etc, etc.

Hmmph! I didn't know that. My code was written to avoid unnecessarily
wrapping an iterator around an iterator. I made this change immediately.

I haven't looked inside the source code for iterator, but would expect to
see something similar to the test hasattr(s,'next'). So, the point is still
valid. The test is necessary; it is just hidden inside the function call.

>
> "Potentially-idempotent adapter functions are a honking great
> idea -- let's do more of those", to paraphrase the timbot. PEP 246
> can be seen in that light, too: function adapt(object, protocol)
> returns object if object ALREADY satisfies the protocol.
>

Here, here!

>
>
> However, there IS an approach that is even better, and we have
> just finished talking about it: *potentially idempotent adapting
> functions*! re.compile just happens to be one of those, thanks be:
>
> >>> a=re.compile(r'\d+')
> >>> b=re.compile(a)
> >>> a is b
> 1
>
> So just code
> pattern = re.compile(pattern)
> and live happily ever after.

Humph! I didn't know that either. Idempotence is cool!

Raymond Hettinger


Jason Orendorff

unread,
Jan 25, 2002, 1:02:51 PM1/25/02
to
Alex Martelli wrote:

> Jason Orendorff wrote:
> > [However, I can do without recursive definitions. Usually,
> > one operation is the primitive and the others are sugar.
>
> Actually, it's more frequent that two (occasionally more)
> operations need to be in a certain connection with each other,
> but none of them is 'intrinsically' primitive wrt the others.
>
> Having to arbitrarily pick one of .append and .extend as
> "more primitive" in the definition of a list typeclass is
> quite inelegant, for example.

You're treading dangerously close to preferring a mathematician's
sense of elegance to a programmer's sense of clarity.
I'd guess that for the ordinary programmer, append() feels
more primitive (perhaps because it deals with atoms and not
molecules; and perhaps because it's used more often).

Actually I guess I would define extend() in terms of
__setitem__(self, slice), and append() in terms of insert().

I wouldn't bother defining __setitem__() in terms of
insert() and __delitem__(). It just seems a bit much to me.

> I guess this boils down to aesthetic style preferences [...]

Yeah, pretty much. (C:

Anyway, in Java the idiom is to provide an interface and
then provide an abstract base class that implements most
of the interface (recursively, if you like; I think some
java.io class does this). This means you can't add methods
to the interface later, but it's still IMHO more useful
than what Python has (no explicit interfaces at all).

Alex Martelli

unread,
Jan 25, 2002, 4:55:32 PM1/25/02
to
Raymond Hettinger wrote:
...

> 1. It would be great to keep open the possibility of a SuperTable.

Yes.

> 2. hasattr() bugs me because I want to ensure that all of the expected
> methods are available not just one. Also, passing the attribute name
> as a string has a bad feel to it though I can't say why.
> 3. except AttributeError runs the risk of trapping real errors. It is too
> all inclusive to be used safely as a means of making sure an object
> has the required interface.

You don't need to pass the attribute name as a string, by using
'except AttributeError' in a way that avoids the risks of point 3. I.e.,
rather than:

if hasattr(x, 'myattr'):
return floop(x.myattr(fleep()))
else:
return 2323

you could code:

try:
myattr = x.myattr
except AttributeError:
return 2323
else:
return floop(myattr(fleep()))

with basically the same semantics. The first case has (to me) the
slight stylistic defect of doing (basically) the same work twice, in
that the work needed for hasattr(x,'myattr') somewhat duplicates
that later needed for the fetching of x.myattr. The second one is
far from perfect either, of course, since the 'main' case is further
removed from where it should be (right up front). But the rough
and concise alternative:
try: return floop(x.myattr(fleep()))
except AttributeError: return 2323
while having the great stylistic advantage of 'main case up front'
does run the risk of your point 3 -- hiding a "real" AttributeError
due to some bug inside functions fleep or floop or method
myattr (sigh).

> Executive Summary: It would be darned nice if there were a
> straightforward way of assuring a that a particular interface were
> available but not require a particular object class.

Yes. PEP 246 would, I think, be just the right way of providing
it: rather than just giving a yes/no answer it has the further
chance of wrapping the object as and if needed.

> I haven't looked inside the source code for iterator, but would expect to
> see something similar to the test hasattr(s,'next'). So, the point is

Hmmm, it's subtler than that -- see PyObject_GetIter in
Objects/abstract.c. Summing up, from slot tp_iter of the object's
type a pointer to a function is retrieved (if null, there's a further
check for a sequence, which is then iter-wrapped differently).
That function is called on the object and must return an
iterator (this is indeed checked, but throught the type again).

When the next-step is performed, this is how:

static PyObject *
slot_tp_iternext(PyObject *self)
{
static PyObject *next_str;
return call_method(self, "next", &next_str, "()");
}

which is basically just a call -- no hasattr check; an exception,
if need be, is just propagated (a NULL return, and the global
exception settings appropriately set).


Alex

Alex Martelli

unread,
Jan 25, 2002, 5:12:53 PM1/25/02
to
Jason Orendorff wrote:
...

>> Having to arbitrarily pick one of .append and .extend as
>> "more primitive" in the definition of a list typeclass is
>> quite inelegant, for example.
>
> You're treading dangerously close to preferring a mathematician's
> sense of elegance to a programmer's sense of clarity.

That's a risk one runs when in mental "Haskell mode", yes, because
Haskell's elegance is so stunning.

> I'd guess that for the ordinary programmer, append() feels
> more primitive (perhaps because it deals with atoms and not
> molecules; and perhaps because it's used more often).

Refusing to guess in presence of ambiguity is one of Python's
tenets, though.


> Actually I guess I would define extend() in terms of
> __setitem__(self, slice), and append() in terms of insert().

You could define them all in terms of slice setting, yes.

I think in this sense slice setting is 'the' most primitive
mutator of Python mutable sequences -- you can most
easily define all others in its terms but not vice versa.


> Anyway, in Java the idiom is to provide an interface and
> then provide an abstract base class that implements most
> of the interface (recursively, if you like; I think some
> java.io class does this). This means you can't add methods
> to the interface later, but it's still IMHO more useful
> than what Python has (no explicit interfaces at all).

Since those abstract base classes, in Java, cannot be
mixin-inherited from, I disagree that they're more useful
than what Python has. If you like implements/extends
as the only way to build objects acceptable to a given
library, then (while not mathematically elegant) Python's
lack of distinction between classes and interfaces (like
C++'s) is pragmatically sufficient -- and overall more
useful thanks to multiple inheritance.


Alex

Jason Orendorff

unread,
Jan 25, 2002, 5:24:46 PM1/25/02
to
Alex Martelli wrote:
> Jason Orendorff wrote:
> > I'd guess that for the ordinary programmer, append() feels
> > more primitive (perhaps because it deals with atoms and not
> > molecules; and perhaps because it's used more often).
>
> Refusing to guess in presence of ambiguity is one of Python's
> tenets, though.

It's the language that doesn't guess. The language designer
is notorious for picking one thing, seemingly arbitrarily,
from a number of reasonable alternatives.

> Since those abstract base classes, in Java, cannot be
> mixin-inherited from, I disagree that they're more useful
> than what Python has.

Point taken.

Kragen Sitaker

unread,
Jan 27, 2002, 12:50:54 PM1/27/02
to
"Jason Orendorff" <ja...@jorendorff.com> writes:

> Alex Martelli wrote:
> > Actually, it's more frequent that two (occasionally more)
> > operations need to be in a certain connection with each other,
> > but none of them is 'intrinsically' primitive wrt the others.
> >
> > Having to arbitrarily pick one of .append and .extend as
> > "more primitive" in the definition of a list typeclass is
> > quite inelegant, for example.
>
> You're treading dangerously close to preferring a mathematician's
> sense of elegance to a programmer's sense of clarity.
> I'd guess that for the ordinary programmer, append() feels
> more primitive (perhaps because it deals with atoms and not
> molecules; and perhaps because it's used more often).
>
> Actually I guess I would define extend() in terms of
> __setitem__(self, slice), and append() in terms of insert().
>
> I wouldn't bother defining __setitem__() in terms of
> insert() and __delitem__(). It just seems a bit much to me.

MetaPy.Sugar.Sequence is a typeclass for Python sequences; it's in
MetaPy, which is at http://pobox.com/~kragen/sw/MetaPy-7.tar.gz or
.zip.

In MetaPy.Sugar.Sequence, I define everything in terms of
simple_getitem, simple_setitem, simple_delitem, insert, and __len__.
Sequences whose lengths aren't mutable don't define simple_delitem and
insert (but then they must define __add__, __radd__, and __mul__
themselves.) The simple_* operations don't need to handle slices.

append is defined in terms of extend, which is defined in terms of
__setitem__ with a slice, which is defined in terms of simple_setitem
and (when the length changes) simple_delitem and insert.

The idea behind this long chain is that redefining any operation (for
example, extend or __setitem__ with a slice) in a more efficient way
should make the maximal set of operations more efficient.

There's a fair bit of typetesting is there to distinguish slice
indices from integer indices; is there a better way of doing it?

I'm pondering what to rename things; I'm thinking:
MetaPy -> multipy "Multi-paradigm Python"
MetaPy.Sugar -> multipy.typeclass
MetaPy.Sugar.Sequence -> multipy.sugar.seq
MetaPy.Sugar.Mapping -> multipy.sugar.mapping

Any thoughts?

Jason Orendorff

unread,
Jan 27, 2002, 1:53:31 PM1/27/02
to
> In MetaPy.Sugar.Sequence, I define everything in terms of
> simple_getitem, simple_setitem, simple_delitem, insert, and __len__.
> [...]
> append is defined in terms of extend [...]

Wouldn't this be better?

def append(self, obj):
self.insert(self.__len__(), obj)

Kragen Sitaker

unread,
Jan 27, 2002, 11:00:46 PM1/27/02
to
"Jason Orendorff" <ja...@jorendorff.com> writes:
> > In MetaPy.Sugar.Sequence, I define everything in terms of
> > simple_getitem, simple_setitem, simple_delitem, insert, and __len__.
> > [...]
> > append is defined in terms of extend [...]
>
> Wouldn't this be better?
>
> def append(self, obj):
> self.insert(self.__len__(), obj)

Maybe so. I can't really imagine a case where it would be noticeably
worse.

Aahz Maruch

unread,
Feb 1, 2002, 12:46:07 PM2/1/02
to
In article <a2r97q$dpi$1...@serv1.iunet.it>, Alex Martelli <al...@aleax.it> wrote:
>
>Wouldn't we want in this case to treat X**1.0 the same as X**1 the
>same as X**1L...? A test such as exponent==int(exponent) would
>then seem to be more general and useful than isinstance(exponent,int).

I'd tend to code exponent==long(exponent) if I were targetting versions
of Python < 2.2.
--
--- Aahz <*> (Copyright 2002 by aa...@pobox.com)

Hugs and backrubs -- I break Rule 6 http://www.rahul.net/aahz/
Androgynous poly kinky vanilla queer het Pythonista

"The more you drive, the less intelligent you are." --_Repo Man_

Aahz Maruch

unread,
Feb 1, 2002, 12:56:25 PM2/1/02
to
In article <mailman.1011997845...@python.org>,
Jason Orendorff <ja...@jorendorff.com> wrote:

>Alex Martelli wrote:
>>
>> Refusing to guess in presence of ambiguity is one of Python's
>> tenets, though.
>
>It's the language that doesn't guess. The language designer is
>notorious for picking one thing, seemingly arbitrarily, from a number
>of reasonable alternatives.

Mind expanding that? (I could guess, but I don't want to flame you
until I know for sure what you're saying. ;-)

Jason Orendorff

unread,
Feb 1, 2002, 9:43:35 PM2/1/02
to
Aahz wrote:

> Jason Orendorff wrote:
> > Alex Martelli wrote:
> > >
> > > Refusing to guess in presence of ambiguity is one of Python's
> > > tenets, though.
> >
> > It's the language that doesn't guess. The language designer is
> > notorious for picking one thing, seemingly arbitrarily, from a number
> > of reasonable alternatives.
>
> Mind expanding that? (I could guess, but I don't want to flame you
> until I know for sure what you're saying. ;-)

Oh, I don't remember the context exactly. It had to do with how
useful the recursive quality of typeclasses is.

My point was that when the BDFL has to choose between a number
of reasonable alternatives, he often picks one without seriously
trying to justify the choice. (Hence "BDFL pronouncements"
in many PEPs: 201, 202, 223, 255...) In PEP 255: "No argument
on either side is totally convincing, so I have consulted my
language designer's intuition."

Or if you prefer: in the role of designing Python's behavior in
the presence of ambiguous input, the BDFL prefers to have Python
throw exceptions. But when the BDFL himself is presented with
ambiguous input (or internal ambivalence), he (often enough)
makes a decision and moves on.

Flame away... :)

Aahz Maruch

unread,
Feb 2, 2002, 9:45:41 AM2/2/02
to
In article <mailman.1012618084...@python.org>,

Jason Orendorff <ja...@jorendorff.com> wrote:
>Aahz wrote:
>> Jason Orendorff wrote:
>>> Alex Martelli wrote:
>>>>
>>>> Refusing to guess in presence of ambiguity is one of Python's
>>>> tenets, though.
>>>
>>> It's the language that doesn't guess. The language designer is
>>> notorious for picking one thing, seemingly arbitrarily, from a number
>>> of reasonable alternatives.
>>
>> Mind expanding that? (I could guess, but I don't want to flame you
>> until I know for sure what you're saying. ;-)
>
>My point was that when the BDFL has to choose between a number of
>reasonable alternatives, he often picks one without seriously trying to
>justify the choice. (Hence "BDFL pronouncements" in many PEPs: 201,
>202, 223, 255...) In PEP 255: "No argument on either side is totally
>convincing, so I have consulted my language designer's intuition."

Yes. And nine times out of ten, if you think you disagree with Guido's
intuition, you'll change your mind later. As Alex (I think it was Alex)
pointed out a few months ago, Guido's ability to do good design work
vastly outstrips his ability to *explain* his thought processes.

Therefore, as long as you emphasize "*seemingly* arbitrary", you won't
get flamed by me.

Jason Orendorff

unread,
Feb 2, 2002, 9:52:21 AM2/2/02
to
Aahz wrote:
> [...] nine times out of ten, if you think you disagree with Guido's

> intuition, you'll change your mind later. As Alex (I think it was Alex)
> pointed out a few months ago, Guido's ability to do good design work
> vastly outstrips his ability to *explain* his thought processes.
>
> Therefore, as long as you emphasize "*seemingly* arbitrary", you won't
> get flamed by me.

I agree with all that. Not to take any credit away from the BDFL,
but I also think that about 4 times out of 10, what's important is not
his intuition, but simply that one (any) of the alternatives be chosen
and endorsed, so we can get on with it.

Courageous

unread,
Feb 2, 2002, 11:43:41 AM2/2/02
to

>Yes. And nine times out of ten, if you think you disagree with Guido's
>intuition, you'll change your mind later.

This is more or less true, yup. Just consider the vast hordes of
programmers who, upon encountering Python, say "yuck! whitespace
sensitive language!" who, only after trying it for a while, see
the light. I was in that boat several years ago. It kept me from
learning Python for a while. But then. But thennnnnnn. I _tried_
it.

I _still_ don't like yield, however. :) :) :)

And yes, I've tried it. :)

C//

Aahz Maruch

unread,
Feb 2, 2002, 1:18:52 PM2/2/02
to
In article <mailman.1012661825...@python.org>,

Oh. Okay, fair enough. And I'm sure you'll agree that this is a
*necessary* skill in a project leader.

Andrew Koenig

unread,
Feb 2, 2002, 1:28:08 PM2/2/02
to
> This is more or less true, yup. Just consider the vast hordes of
> programmers who, upon encountering Python, say "yuck! whitespace
> sensitive language!" who, only after trying it for a while, see the
> light. I was in that boat several years ago. It kept me from
> learning Python for a while. But then. But thennnnnnn. I _tried_ it.

It seems to be universal among programmers that they get hung up about
tiny syntax details until they get used to those details. For example,
C and C++ programmers still argue about where to put the curly braces
in their programs. At least Python doesn't have that particular problem...

--
Andrew Koenig, a...@research.att.com, http://www.research.att.com/info/ark

Tim Peters

unread,
Feb 2, 2002, 2:35:29 PM2/2/02
to
[Andrew Koenig]

> It seems to be universal among programmers that they get hung up about
> tiny syntax details until they get used to those details. For example,
> C and C++ programmers still argue about where to put the curly braces
> in their programs.

That's because they don't have Guido to force them to do it the right way:

if (guido) {
tab(indent);
}

If Guido had written the first C compiler, that's the only style it would
have accepted <0.5 wink>.

> At least Python doesn't have that particular problem...

Oddly enough, none of the Python developers argue about brace placement,
except for the occasional radical who sneaks in

} else {
}

instead of

}
else {
}

The indentation of "case" wrt an enclosing "switch" is so hopeless nobody
even bothers to argue.

What the Python developers do get testy about is living with Guido's
seemingly inconsistent demand that Python code use 4-space indents with no
hard tabs, C code use 8-space hard-tab indents. Oddly enough, the more
intelligent of us agree with him, but nobody can explain why <wink>.

to-call-it-a-matter-of-taste-would-be-too-forgiving-ly y'rs - tim


John J. Lee

unread,
Feb 2, 2002, 4:46:55 PM2/2/02
to

On Sat, 2 Feb 2002, Tim Peters wrote:
[...]

> What the Python developers do get testy about is living with Guido's
> seemingly inconsistent demand that Python code use 4-space indents with no
> hard tabs, C code use 8-space hard-tab indents. Oddly enough, the more
> intelligent of us agree with him, but nobody can explain why <wink>.

Intelligence here is as judged by preferred indentation style, I presume?

Hypothesis:

Long C type names -->

tendency to use short variable names -->

tendency to write too much on one line, or use too many indentation
levels, on those lines containing no type names -->

increase indentation to counter this tendency?

Of course, the 8 character tab width is axiomatic.

(I confess I did debate the indentation here, and the positioning of the
arrows)

Deadly serious,


John

Courageous

unread,
Feb 2, 2002, 7:11:43 PM2/2/02
to

> } else {
> }
>

I _always_ put braces like this

something
{
}
somethingelse
{
}

Why? So that when I hit the % key in vim, it takes me to the exact same
indentation level brace, giving me a clue whether or not I matched the
right one or not (which can get screwed up if the wrong thing is commented
out, dontcha know).

C//

Tim Hammerquist

unread,
Feb 2, 2002, 8:40:12 PM2/2/02
to
Tim Peters <tim...@home.com> graced us by uttering:

> [Andrew Koenig]
>> It seems to be universal among programmers that they get hung up about
>> tiny syntax details until they get used to those details. For example,
>> C and C++ programmers still argue about where to put the curly braces
>> in their programs.
>
> That's because they don't have Guido to force them to do it the right way:
>
> if (guido) {
> tab(indent);
> }

I was required to use BSD-style for school, but the "wasted" vertical
space offended my senses, so I settled on K&R. Never could understand
GNU or Whitesmith style, though. =)

BTW, less wasted v-space is one very attractive feature of Python.

My eyes just can't parse:

if ( some_condition() ) {
{
for (i = first_i();i <= 10; i++)
{
initialize_some_stuff();
while ( some_other_condition() )
{
do_some_more_stuff()
}
}
}

...yet I have no trouble parsing

if ( &some_condition ) {
for $i (&first_i .. 10) {
&initialize_some_stuff;
&do_some_more_stuff while &some_other_condition;
}
}

Imagine a "Lisp-style" indent!

if ( some_condition() ) {
i = first_i();
for (i = first_i(); i <= 10; i++) {
initialize_some_stuff();
while ( some_other_condition() ) {
do_some_more_stuff() } } }

<Real-programmers-use-1TBS>-ly y'rs,
Tim Hammerquist
--
Note that by displacing from "I got confused" to "It got confused", the
programmer is not avoiding responsibility, but rather getting some analytical
distance in order to be able to consider the bug dispassionately.
-- Jargon File 4.3.1

Chris Jones

unread,
Feb 2, 2002, 10:05:20 PM2/2/02
to
Tim Hammerquist <t...@vegeta.ath.cx> writes:

[...]

My eyes just can't parse:

if ( some_condition() ) {
{
for (i = first_i();i <= 10; i++)
{
initialize_some_stuff();
while ( some_other_condition() )
{
do_some_more_stuff()
}
}
}

I can, and I notice you've got unbalanced braces (look at your first two
lines).

Courageous

unread,
Feb 2, 2002, 11:05:40 PM2/2/02
to

>Imagine a "Lisp-style" indent!
>
>if ( some_condition() ) {
> i = first_i();
> for (i = first_i(); i <= 10; i++) {
> initialize_some_stuff();
> while ( some_other_condition() ) {
> do_some_more_stuff() } } }

Yeah, one of the reasons that I really have grown
to dislike Lisp is that you basically can't write
Lisp code using standard-style Lisp formatting
unless you also have a Lisp-aware editor (namely,
Emacs). That truly irks me.

C//

dman

unread,
Feb 2, 2002, 10:36:32 PM2/2/02
to
On Sun, Feb 03, 2002 at 01:40:12AM +0000, Tim Hammerquist wrote:

| BTW, less wasted v-space is one very attractive feature of Python.

Yes, but it still doesn't mean you should hide the braces (in the
inferior languages) on other lines.

| My eyes just can't parse:
|
| if ( some_condition() ) {
| {
| for (i = first_i();i <= 10; i++)
| {
| initialize_some_stuff();
| while ( some_other_condition() )
| {
| do_some_more_stuff()
| }
| }
| }

I have trouble with that too. My preference (FWIW, probably nothing) :

if ( some_condition() )
{
for (i = first_i();i <= 10; i++)
{
initialize_some_stuff();
while ( some_other_condition() )
{
do_some_more_stuff()
}
}
}


Notice how the matching braces are horizontally aligned and at the
same level as the line that initiates the bock? If the braces are
removed, and colons added it will look like python (with some
extraneous parens).

| ...yet I have no trouble parsing
|
| if ( &some_condition ) {
| for $i (&first_i .. 10) {
| &initialize_some_stuff;
| &do_some_more_stuff while &some_other_condition;
| }
| }

This is hard for me because I can't see what the '}' goes with.
Combine this with leaving out the braces on one-line compound
statements, and I'll have real problems.

-D

--

A perverse man stirs up dissension,
and a gossip separates close friends.
Proverbs 16:28


Courageous

unread,
Feb 2, 2002, 11:30:11 PM2/2/02
to

>This is hard for me because I can't see what the '}' goes with.
>Combine this with leaving out the braces on one-line compound
>statements, and I'll have real problems.

Well, the argument in favor "but you can: the trailing brace
matches the statement at its indentation level" makes some sense,
it's just that you and I probably use matching tools. If my
matching tool "took me from the trailing brace to the beginning
of the first matching statement just prior to the initiating brace"
I might be able to deal with it. But no.

C//

Mark Hadfield

unread,
Feb 3, 2002, 4:07:49 PM2/3/02
to
"Tim Peters" <tim...@home.com> wrote in message
news:mailman.1012678565...@python.org...

> What the Python developers do get testy about is living with Guido's
> seemingly inconsistent demand that Python code use 4-space indents with no
> hard tabs, C code use 8-space hard-tab indents. Oddly enough, the more
> intelligent of us agree with him, but nobody can explain why <wink>.

Who *are* the more intelligent of you?

---
Mark Hadfield
m.had...@niwa.co.nz http://katipo.niwa.co.nz/~hadfield
National Institute for Water and Atmospheric Research

Fredrik Lundh

unread,
Feb 3, 2002, 3:11:35 PM2/3/02
to
Tim Peters wrote:
> Oddly enough, none of the Python developers argue about brace placement,
> except for the occasional radical who sneaks in
>
> } else {
> }

that should of course be:

> } else {
> }

(how come Guido ignores K&R and Indian Hill, but still insists
on Python programmers using the GvR style guide? ;-)

> What the Python developers do get testy about is living with Guido's
> seemingly inconsistent demand that Python code use 4-space indents
> with no hard tabs, C code use 8-space hard-tab indents.

really? I thought the Python C code used 4-space hard-tab
indents...

</F>


Courageous

unread,
Feb 3, 2002, 4:37:59 PM2/3/02
to

>Who *are* the more intelligent of you?

I saw an interesting bit of sociological trivia the other day:

Some researchers took a subject population and divided it into
smaller groups. They than asked each group to rate their abilities
against all the other groups. Interestingly, each group always
rated themselves as more capable than the other.

It would therefor follow that _all_ of Tim's fellows are the
more intelligent of them, yes?

And at that, I'll leave you with a movie quote for the day:

"Have you ever heard of Plato? Aristotle? Socrates?"
"Yes."
"Morons."

C//

Chris Gonnerman

unread,
Feb 3, 2002, 4:34:39 PM2/3/02
to
----- Original Message -----
From: "Mark Hadfield" <m.had...@niwa.co.nz>
>
> Who *are* the more intelligent of you?
>

Looking for a flamewar? :-)

Anyone graced by being accused of being a robot would have to qualify;
other than that, who can say?

Tim Peters

unread,
Feb 3, 2002, 5:51:26 PM2/3/02
to
[Tim]
> Oddly enough, the more intelligent of us [Python developers] agree with
> him [Guido], but nobody can explain why <wink>.

[Mark Hadfield]


> Who *are* the more intelligent of you?

Those of us who agree with Guido, of course.

leaving-it-a-secret-whether-i-agree-with-him-ly y'rs - tim

Tim Peters

unread,
Feb 3, 2002, 5:54:40 PM2/3/02
to
[/F]

> really? I thought the Python C code used 4-space hard-tab
> indents...

Guido actually uses 7-space hard-tab indents, but his comments actually
align better if you use 8 <0.9 wink>.


Paul Rubin

unread,
Feb 3, 2002, 8:31:17 PM2/3/02
to
Courageous <jkr...@san.rr.com> writes:
> And at that, I'll leave you with a movie quote for the day:
>
> "Have you ever heard of Plato? Aristotle? Socrates?"
> "Yes."
> "Morons."

"Remember how bad Einstein's grades were in school? Well, mine are
even worse!" (Calvin & Hobbes).

Tim Hammerquist

unread,
Feb 4, 2002, 4:55:41 AM2/4/02
to
dman <dsh...@rit.edu> graced us by uttering:

> On Sun, Feb 03, 2002 at 01:40:12AM +0000, Tim Hammerquist wrote:
[ snip ]

> I have trouble with that too. My preference (FWIW, probably nothing) :
>
> if ( some_condition() )
> {
> for (i = first_i();i <= 10; i++)
> {
> initialize_some_stuff();
> while ( some_other_condition() )
> {
> do_some_more_stuff()
> }
> }
> }
>
>
> Notice how the matching braces are horizontally aligned and at the
> same level as the line that initiates the bock? If the braces are
> removed, and colons added it will look like python (with some
> extraneous parens).

Ah yes. BSD-style. My second choice, and preferred by one of my college
professors.

>| ...yet I have no trouble parsing
>|
>| if ( &some_condition ) {
>| for $i (&first_i .. 10) {
>| &initialize_some_stuff;
>| &do_some_more_stuff while &some_other_condition;
>| }
>| }
>
> This is hard for me because I can't see what the '}' goes with.
> Combine this with leaving out the braces on one-line compound
> statements, and I'll have real problems.

A habit I got out of...

ie:

/* BAD! */
if(expression)
stuff();

/* Better */
if(expression) {
stuff();
}

...much easier and less error-prone to add one+ more statements to the
if() block. I like that Perl removed that particularly error-prone piece
of syntactic sugar...

Tim Hammerquist
--
guru, n: a computer owner who can read the manual.

Tim Hammerquist

unread,
Feb 4, 2002, 5:05:40 AM2/4/02
to
Chris Jones <c...@theWorld.com> graced us by uttering:

Spot on! Yup, hard to break those K&R habits, even when trying to
demonstrate the alternatives.

I guess that just goes to show it's truly more a matter of what you're
used to than what's "better", whatever that is. <wink>

Thx.
Tim Hammerquist
--
Usenet is essentially a HUGE group of people passing notes in class.
-- R. Kadel

Chris Jones

unread,
Feb 4, 2002, 11:33:50 AM2/4/02
to
"Fredrik Lundh" <fre...@pythonware.com> writes:

[...]

(how come Guido ignores K&R and Indian Hill, but still insists
on Python programmers using the GvR style guide? ;-)

As the punchline to the joke goes, "Because he can!"

James_...@i2.com

unread,
Feb 4, 2002, 2:34:05 PM2/4/02
to

[Tim Hammerquist]

>Imagine a "Lisp-style" indent!
>
>if ( some_condition() ) {
> i = first_i();
> for (i = first_i(); i <= 10; i++) {
> initialize_some_stuff();
> while ( some_other_condition() ) {
> do_some_more_stuff() } } }

Although one rarely encounters the above indentation style in C/C++/C#/Java
code -- and, in fact, one occasionally sees the suggestion of said style
accompanied by giggling ridicule -- it is amusing to reminisce about the
path taken by the Smalltalk-80 development team. Smalltalk-80 has block
delimiters "[" and "]". When we first started implementing the
Smalltalk-80 system (all of which was written in Smalltalk itself, save the
VM, object memory, and primitives) we had the usual debates about delimiter
style. As with C coders since the dawn of time, each style had strong
advocates. And each developer used his/her style of choice. Until ...
Dan Ingalls, who was the lead architect for Smalltalk-80, had a very strong
preference for the above "indentation-based" style. And he was clever
enough to write a pretty-print routine that converted code to his preferred
style *and* to add a call to his pretty-printer to the "compile" menu in
the editor. Voila. No more inconsistency and no more debates. Although
not everyone immediately appreciated this direct approach to "solving" the
"inconsistent style" problem, the end result -- interestingly -- was that
*all* early Smalltalk-80 code had a very nice "Pythonic"
indentation-oriented look and feel (assuming that you ignored a little bit
of cruft at the end of each line <wink>). Note that finding matching
brackets was not an issue because the editor had a feature whereby if you
clicked inside a left bracket it would highlight all text to the matching
right bracket.

(And, I suppose, the next step should have been to put the pretty-print/tab
stuff in the compiler itself <wink>)

Jim


0 new messages