What could 'f(this:that=other):' mean?

Jeff Shannon jeff at ccvcorp.com
Thu Jan 6 21:43:18 CET 2005

Jonathan Fine wrote:

> Jeff Shannon wrote:
>> Jonathan Fine wrote:
>>> Giudo has suggested adding optional static typing to Python.
>>> (I hope suggested is the correct word.)
>>>   http://www.artima.com/weblogs/viewpost.jsp?thread=85551
>>> An example of the syntax he proposes is:
>>>  > def f(this:that=other):
>>>  >     print this
> <snip>
>>> I'm going to suggest a different use for a similar syntax.
>>> In XML the syntax
>>>  >  <element this:that='other'>
>>> is used for name spaces.
>>> Name spaces allow independent attributes to be applied to an
>>> element.  For example, 'fo' attributes for fonts and layout.
>>> XSLT is of course a big user of namespaces in XML.
>>> Namespaces seems to be a key idea in allow independent
>>> applications to apply attributes to the same element.
>>> [...]
>>> Here's an example of how it might work.  With f as above:
>>>  > f(this:that='value')
>>> {'that': 'value'}
>> I fail to see how this is a significant advantage over simply using 
>> **kwargs.  It allows you to have multiple dictionaries instead of just 
>> one, that's all.  And as you point out, it's trivial to construct your 
>> own nested dicts.
> This argument could be applied to **kwargs (and to *args).  In other
> words, **kwargs can be avoided using a trivial construction.

The use of *args and **kwargs allows functions to take a variable 
number of arguments.  The addition of ***nsargs does not add 
significantly.  Note that *args and **kwargs should always be used 
together, because to do otherwise would require the function caller to 
know details of the function's implementation (i.e. which arguments 
are expected to be positional and which must be keywords).  Since we 
*want* the caller to not need to know this, then ***nsargs would 
always need to be used together with *args and **kwargs.  (A function 
defined 'def f(***nsargs): ...' could not be called with 'f(1)'.  This 
means that all you're gaining is an extra bag to put variable numbers 
of arguments in.  The value here is that it maintains a parallel with 
*args and **kwargs when one allows 'namespaced' arguments -- if one 
allows that, then ***nsargs is required for consistency's sake, but it 
does not simplify anything by itself.

So really, we need to look at what gains we'd get from having 
'namespaced' arguments.  What's the real advantage here?

When using 'namespace' arguments, instead of standard keyword 
arguments, the function body would be given a dictionary instead of a 
set of local variables, right?  'def f1(arg1, arg2, arg3, arg4)' 
creates four names in locals(), where 'def f2(ns1:arg1, ns1:arg2, 
ns1:arg3, ns1:arg4) creates a single dict named ns1 in locals(), which 
contains four items (keyed on 'arg1', 'arg2', etc.), and 'def 
f3(ns1:arg1, ns1:arg2, ns2:arg3, ns2:arg4)' creates two dicts (ns1 and 
ns2) with two entries each.

Okay, now, let's take a look at how these functions will be used.

    f1(1, 2, 3, 4)
    f1(arg1=1, arg2=2, arg3=3, arg4=4)

Note that f1 doesn't care which of these methods of calling is 
employed -- both result in the same situation inside of f1().

So what's the intended way of calling f2()?  I'd presume that it 
shouldn't care whether keywords or namespaces are specified, so that 
the following should all be equivalent:

    f2(1, 2, 3, 4)
    f2(1, 2, arg3=3, arg4=4)
    f2(1, 2, arg3=3, ns1:arg4=4)

Note that this doesn't *add* any utility.  The function caller hasn't 
gained anything.  Since arg4 is unambiguous regardless of whether it's 
referred to as arg4 or ns1:arg4, the only time that the caller has any 
reason to specify the namespace is if argnames within different 
namespaces clash -- that is, if we allow something like 'def 
f4(ns1:arg1, ns1:arg2, ns2:arg1, ns2:arg2)'.

Now, though, we've lost the ability to specify *only* the argname and 
not the namespace as well -- that is, you *cannot* call f4 with 
keywords but not namespaces.  From the caller's vantage point, this 
means that they need to know the full namespace spec of the function, 
which makes it no different than simply using longer (but unique) 
keyword names.

So, we can see that allowing namespaces and ***nsargs doesn't add any 
utility from the caller's perspective.  How much does the callee gain 
from it?

Well, the following functions would be equivalent:

     def g1(arg1, arg2, arg3, arg4):
         ns1 = {'arg1':arg1, 'arg2':arg2, 'arg3':arg3, 'arg4':arg4}
         return ns1
     def g2(ns1:arg1, ns1:arg2, ns1:arg3, ns1:arg4):
         return ns1

You might say "Wow, look at all that repetetive typing I'm saving!" 
But that *only* applies if you need to stuff all of your arguments 
into dictionaries.  I suspect that this is a rather small subset of 
functions.  In most cases, it will be more convenient to use your 
arguments as local variables than as dictionary entries.

     def gcd1(a, b):
         while a:
             a, b = b%a, a
         return b

     def gcd2(ns1:a, ns1:b):
         while ns1['a']:
             ns1['a'], ns1['b'] = ns1['b']%ns1['a'], ns1['a']
         return ns1['b']

Speaking of repetetive typing.... :P

Besides, another function equivalent to the above two would be:

     def g3(arg1, arg2, arg3, arg4):
         ns1 = locals()
         return ns1

...which is quite a bit less repetetive typing than the 'namespace' 

So, you're looking at making the function-calling protocol 
significantly more complex, both for caller and callee, for the rather 
marginal gain of being able to get arguments prepackaged in a 
dictionary or two, when there already exist easy ways of packaging 
function arguments into a dict.  Given the deliberate bias against 
adding lots of new features to Python, one needs a *much* better 
cost/benefit ratio for a feature to be considered worthwhile.

>> Besides, Python already uses the concept of namespaces by mapping them 
>> to object attributes.  [...]
> Here, I don't understand.  Could you give an example of two obvious ways
> of doing the same thing, should my suggestion be adopted?

My main point here is that 'namespace' is a term/concept that is 
already in use in Python, in different circumstances and using a 
completely different mechanism.  Functions already *have* namespaces, 
whose contents can be inspected with locals() and modules have 
namespaces that can be accessed through globals().

I'd note also that the usage you're drawing from, in XML/XSLT, isn't 
really comparable to function parameters.  It's a much closer parallel 
to object attributes.  Python *does* have this concept, but it's 
spelled differently, using a '.' instead of a ':'.  In other words, 
the XML fragment you give,

     <element this:that='other'>

... would be more appropriate to render in Python as

     e = Element()
     e.this.that = 'other'

It's quite reasonable to suppose that some object of type Element may 
have a set of font attributes and a set of image attributes, and that 
some of these may have the same name.  Python would use font objects 
and image objects instead of 'namespaces' --

     e.font.size = '14pt'
     e.image.size = (640, 480)

So while these namespaces are probably a great thing for XML, XSLT, 
they're not very useful in Python.  Which, given the rather different 
goals and design philosophies behind the languages, shouldn't really 
be much of a surprise.

Jeff Shannon
Credit International

More information about the Python-list mailing list