other python ideas

Douglas Alan nessus at mit.edu
Fri May 4 17:20:38 EDT 2001


"Andrew Dalke" <dalke at acm.org> writes:

>> alias StringIO = cStringIO
>> try:
>>   dummy = StringIO::someObject
>> except ImportError:
>>   alias StringIO = StringIO

> Doesn't this introduce the same problems you have with import?
> Specifically, you may end up with lots of "alias" statements which
> are unneeded by the module?

That's true, but "alias" would presumably be used significantly less
often, thus reducing the problem.  Also, since "alias" wouldn't really
do much of anything, it would be far more innocuous than loading lots
of unused modules.  (Although, I suppose that this excessive loading
problem could be fixed merely by making module loading work in a
"lazy" fashion.  I.e., the module is not actually loaded until it's
used.  In the meantime, the module object would be just a proxy
object.)

I really prefer the "::" module notation for the following reasons:
(1) It would be typically less verbose than requiring a separate
import statement.  (2) It would force the issue so that importing
inside functions and even tight loops would have to be made efficient.
Right now it isn't.  (3) Orthogonality is a nice goal in theory, but
sometimes it can be confounding.  The notation foo::bar makes it very
clear that "foo" is a module and not just any old object, while the
notation "foo.bar" doesn't.  In the case of modules, I would prefer
that distiction to be immediately apparent, since a module is *not*
just any ol' object.  (4) I don't really see the point of divorcing
the declaration of module use from actual module use in the typical
situation.  It causes a needless separation of two things that should
typically be one thing.  (5) Getting rid of import statements would
simplify the language.

> It's also somewhat awkward in that you need to know an object
> inside of the cStringIO/StringIO module for which to test.

Well, you could always do:

   alias StringIO = cStringIO
   try:
     dummy = StringIO::noSuchVariable
   except ImportError:
      alias StringIO = StringIO
   except NameError: pass

Or perhaps

   try: StringIO::
   except ImportError: ...

should be allowed.

> >or

> >try:
> >   StringIO = __import__("cStringIO")
> >except ImportError:
> >   StringIO = __import__("StringIO")

> Not as nice looking with more complicated imports

That's true.  I can't imagine that this sort of conditional importing
is *so* common that the solution to handle it has to be terribly
elegant.  I'm much more interested in optimizing for the typical case
than the exceptional case.

> So your construct of "X::Y" acts like

>   if X exists:
>     return X.Y
>   else:
>     __import__(X).Y

Yup.

> What about multilevel imports, say, xml.sax.handler?  Could I use

>   xml.sax::handler.ContentHandler

> or would I need to do

>   xml::sax::handler.ContentHandler ?

Something like that.  This hasn't been writen in stone!  It's just on
the back of a napkin.  I think I'd prefer the former, but either one
would do.

> What if xml.sax is really

> class sax:
>   def __getattr__(self, name):
>     import new
>     return new.module(name)

I think you mean "return new::module(name)".  Is there some reason
you'd want to do this?  In this case, it would seem that you'd get a
new empty module every time you refered to xml.sax, and consequently

   xml.sax.foo::bar

would always create a new module named "foo" and give you a NameError
looking up "bar" in foo.

> > Personally, I hate maintaining import lists.  As a general
> > rule, I think it is bad to have to maintain the same information in
> > two different places.

> But it isn't the same information.  One says that a name refers to a
> module, or a class, or an instance, or a function.  The other one
> says what to do with the name.

Almost any operation could be split into sub-operations, but that
doesn't mean that it is typically worthwhile to do so.  Having to say
"in the future I want to use module 'foo'", and then later saying
"okay, now I really want to use module 'foo'", seems to me to fall
into this category of needless splitting.  I should be able to just
use module "foo" without the unnecessary intermediary.

> Hmm.  Thinking about this.  How would you implement
>   import math
>   what_is(math)
> in your scheme?  Perhaps as
>   what_is(math::)
> ?

Sounds reasonable to me.

>>  Having to do so results in dead code that loads unused modules.

> There are tools which address unneeded imports.  One was announced
> just a few days ago.  A couple years ago I worked on mods to
> kwparser's lint program to support 1.5 syntax and gave Guido a list
> of a few dozen module references to delete.

I have nothing against tools.  I just think that the existence of
tools shouldn't be a deterrent to making languages as good as
possible.  (Of course maintaining compatibility with existing code,
documentation, and the mindset of a community *is* and *should* be
something of a deterrent.)

> Even with this there will be cruft functions, classes, etc.  which
> won't be fixed with this proposal, so it tries to solve part of the
> problem without really addressing the whole thing.  (Oh, and I
> helped with the code coverage program distributed in the Tools
> section to help that more general case :)

I think that if a feature has a number of benefits, you can't discount
it on the basis that each benefit isn't some sort of panacea that
completely fixes a problem.  You instead have to weigh the sum total
of the benefits it provides.  If it whittles away at a number of
problems, without adding much complexity, then I think it should be
seriously considered.  Since my idea would get rid of import
statements altogether, I think it actually simplifies the language,
while also providing benefits.  That makes it all the better.

> > Also, I know people who won't use Python because they want to be
> > able to reload modules reliably, as you can on a Lisp Machine.

> The quick answer is to let them work on Lisp Machines.

Well, perhaps, but one should consider carefully, I should think,
before dismissing a whole problem domain.  For some problem domains
the ability to reload modules is essential.

> Your proposal would change this to

> def f(x, eggs = spam::eggs):
>   return eggs(x) * eggs(x+1)

> but still have the same problem that a reload(spam) won't
> rebuild f.  So you don't fully solve the problem you want
> to solve.

That's true.  Solving the reloading problem is a difficult and subtle
problem.  My proposal would only whittle away at a piece of it.  And
you are correct that its hard to analyze how worthwhile this whittling
would be without seeing it in the context of a more complete solution.
For instance, a solution that proposes that reloading would modify
existing objects might make the whittling here superfluous.  I tend to
think not, however, because in Common Lisp an approach something like
this is what is taken, even though, I believe, in Common Lisp existing
objects are also modified by reloading.

Your above example would not typically be a problem in Common Lisp,
because in Common Lisp variables do not typically hold function
objects, but rather "symbols" that acts as names for functions.  The
"package" system that copes with such symbols, however, is terribly
complex.  And even with the complex "package" system of Common Lisp,
you *would* still get a similar problem if you provided an imported
object (as opposed to a function or class) as the default value for an
argument.  Common Lisp's solution for this piece of the puzzle is that
the object would end up be modified through the reloading process.

Indeed, reloading is a complicated problem to solve.  You might be
right that perhaps people who need to do this should stick to Common
Lisp.  On the other hand, I think it's an issue worth thinking about.

> And I must confess to not knowing anything other than the most
> elementary parts of Lisp.  Then again, I can point to precisely 1
> person in all of computational biology and chemistry which use
> it... which is as many people as I know using Prolog or Ruby, and
> 1/2 the number I know of who have used Smalltalk.  I suspect that
> means something.

I don't think it means as much as you think.  Languages succeed and
fail for all sorts reasons other than on their strict merits as a
language.  Lisp had already lost its momentum in the mainstream long
before Python ever existed.  This is because, in part, it was far
ahead of its time.  The mainstream back then wanted the speed of
Fortran or assembly language.  So the Lisp people went off on a
boondoggle of building special computers that would run Lisp as fast
as normal computers run Fortran.  Of course, these computers turned
out to be expensive and only useful for someone who was already
convinced of the merits of Lisp.  The companies that designed and
built Lisp Machines spent a lot of money and lost it all, so Lisp
became a dirty word in a lot of communities.  It took putting the
ideas that Lisp invented into other languages like Java and Python in
order to subversively sneak these ideas back into the mainstream.
Every time someone uses one of these languages, the spirit of Lisp
lives on, even if the language itself does not.

So, really, even though we don't use the name "Lisp" anymore, Lisp has
been just about as successful as a language could ever hope to be.  I
suspect that means something.

|>oug

Disclaimer: The author of this message does not clamor to make any
changes to Python.  He is only musing on how he thinks things should
have been, or might yet be in some future Python-like language.



More information about the Python-list mailing list