[Python-3000] More wishful thinking

Talin talin at acm.org
Sat Apr 15 12:32:21 CEST 2006


Another grab-bag of language ideas / wishes. Some of these are
items that have been raised before on other lists - I want to get them
written down so that they can be rejected quickly :)

1) I don't know if this is already on the table, but I sure would like
to be able to have more options as far as mixing positional and
keyword arguments.

A common case that I run into is where I have a function that takes
a variable number of positional arguments, but also takes a couple
of optional keyword arguments.

What I would like to be able to write is:

   def function( *args, option=False ):
      ...

   # Puts (1, 2, 3) into args, option is False
   function( 1, 2, 3 )

   # Puts (1, 2, 3) into args, option is True
   function( 1, 2, 3, option=True )

In other words, any non-named arguments would be put into 'args',
and the 'option' argument would only be used if explicitly named.

However, Python gives me a syntax error when I try this - it wants the
*args to appear after all other arguments. But I can't write:

   def function( option=False, *args )

because now option will be assigned the value '1', and *args will
get (2, 3).

Generally what I end up having to do is:

   def function( *args, **kwargs ):
      option = kwargs.get( 'option', False )

...which is both clumsy and ugly.

2) A suggestion which I've seen others bring up before is the use of
the * operator for tuple packing / unpacking operations, i.e.:

    a, *b = (1, 2, 3)

Yes, I realize that you can also do:

    a, b = v[ 0 ], v[ 1: ]

or other variations. But it does seem like a logical extension. Note that
I'm only proposing this for lists, not dicts -- generalizing the **syntax
seems rather less useful.

2a) One particularly useful variation of *list (again, one which I have
brought up before, as have others), is the "yield *" syntax. The primary
use case is for chained generators, that is a generator that is yielding
the output of multiple sub-generators.

Here is an example where we have a list of patterns which we want to
check against all of the nodes of a tree, and yield all matching results.
Each tree node has a 'left' and 'right' attribute.

def search( pattern_list, tree ):
   for pattern in pattern_list:
       if pattern.match( tree.name ): yield tree
       if tree.left
          for result in search( pattern_list, tree.left ):
             yield result
       if tree.rght:
          for result in search( pattern_list, tree.right ):
              yield result

What's pessimal about this is that each result has to bubble up the stack -
so if you are checking a node that is 6 levels down the tree, each value
gets yielded 6 times, and the execution context gets changed 12 times -
6 times going up the stack and 6 times coming back down again.

What I'd like to write instead would be:

def search( pattern_list, tree ):
   for pattern in pattern_list:
       if pattern.match( tree.name ): yield tree
       if tree.left
          yield *search( pattern_list, tree.left )
          yield *search( pattern_list, tree.right )

The 'yield *' syntax basically means 'yield all of the values from the
specified iterator, one at a time.'

Ideally, this would be implemented via a "short-circuit", such that the
output of the lowest-level generator was feeding results directly into the
final consumer. In other words, the consumer would be temporarily
redirected to consume values directly from the innermost iterator, and
when that iteration was complete, the consumer's connection to the
original iterator would be restored.

(I suspect that the new yield behavior in 2.5 could be used to solve this
problem in a different way, however at this point I'm not clever enough
to be able to figure out what that might be.)

3) str.join should automatically stringify its arguments. I'm constantly
having to write:

    ' '.join( [ str( x ) for x in items ] )

when I ought to be able to write:

   ' '.join( items )

I suppose I could do something like:

   ' '.join( string_iter( items ) )

but having a string function automatically convert its input arguments to
strings seems intuitive to me.

4) I still want a prettier lambda. 'nuff said :)

5) At one point, I was a big fan of reduce(), untill I realized that 99% of
my use cases were just variations of str.join() :)

Even so, I've always wondered if it would be possible to invent a
"comprehension- style" syntax for reduce. I have absolutely no good
ideas on this one, even though I've been pondering it for years...

6) Although I would not want to propose that Python adopt C's 'assignment
is an expression' behavior, I find myself constantly wanting to combine the
following two statements into one:

   m = re.match( ... )
   if m:
      # do something with m

-- Talin




More information about the Python-3000 mailing list