[Python-3000] PEP 3124 - more commentary

Guido van Rossum guido at python.org
Mon May 14 20:25:53 CEST 2007


First I'll try to explain why I don't like the sys._getframe() approach.

Phillip's current syntax is roughly:

  def flatten(x): ...      # this is the "base" function

  @overload
  def flatten(y: str): ...    # this adds an overloaded version

The implementation of @overload needs to use sys._getframe() to look
up the name of the function ('flatten') in the surrounding namespace.
I find this too fragile an approach; it means that I can't easily
write another function that calls overload to get the same effect; in
particular, I don't see how this code could work:

  def my_overload(func):
    "Shorthand for @some_decorator + @overload."
    return some_decorator(overload(func))

  @my_oveload
  def flatten(z: int): ...

If the overload decorator simply looked in the calling scope, it would
not find 'flatten' there, since that's the local scope of my_overload.
(If it devised some clever scheme of descending down the stack, I
would just have to create a more complicated example.)

I find the semantics of things that use sys._getframe() muddy and
would really much much rather avoid them. Using the approach in my old
sandbox/overload/overloading.py code, this objection is removed: the
function being overloaded is named explicitly in the decorator.

I realize that @overload is only a shorthand for @when(function). But
I'd much rather not have @overload at all -- the frame inspection
makes it really hard for me to explain carefully what happens without
just giving the code that uses sys._getframe(); and this makes it
difficult to reason about code using @overload.

My own preference for spelling this example would be

@overloadable
def flatten(x): ...

@flatten.overload
def _(y: str): ...

And for the combined decorator:

@my_overload(flatten)
def _(z: int): ...

******************

I also really don't like approaches based on patching the function
object's code in place. Again, it makes it hard to reason about
innocent-looking code.  It's one thing to say "we can prove property X
assuming no-one assigns a different function to my global f" (since
assigning to module globals from outside the module is an extremely
rare practice). It's quite another thing to say "we can prove propery
X assuming no-one overloads my global f". This is why I really really
really want to require flagging the overloadable function before it
can be overloaded. (And that's why I propose @flatten.overload instead
of @overload(flatten).)

******************

Next, I have a question about the __proceed__ magic argument. I can
see why this is useful, and I can see why having this as a magic
argument is preferable over other solutions (I couldn't come up with a
better solution, and believe me I tried :-).  However, I think making
this the *first* argument would upset tools that haven't been taught
about this yet. Is there any problem with making it a keyword argument
with a default of None, by convention to be placed last?

******************

Finally, I looked at the example of overloading a method instead of a
function.  The little dance required to overload a method defined in a
base class feels fragile, and so does the magic apparently required to
special-case the first argument. This is unfortunate because I imagine
this to be an important use case -- I certainly would expect that the
pretty-printing example would need some state that's most conveniently
stored on a "pretty-printer" object where one overloads the pprint
method, not a pprint function.

******************

Forgive me if this is mentioned in the PEP, but what happens with
keyword args? Can I invoke an overloaded function with (some) keyword
args, assuming they match the argument names given in the default
implementation? Or are we restricted to positional argument passing
only? (That would be a big step backwards.)

******************

Also, can we overload different-length signatures (like in C++ or
Java)? This is very common in those languages; while Python typically
uses default argument values, there are use cases that don't easily
fit in that pattern (e.g. the signature of range()).

-- 
--Guido van Rossum (home page: http://www.python.org/~guido/)


More information about the Python-3000 mailing list