Verbose and flexible args and kwargs syntax

Eelco hoogendoorn.eelco at gmail.com
Tue Dec 13 04:15:46 EST 2011


On Dec 13, 3:43 am, Steven D'Aprano <steve
+comp.lang.pyt... at pearwood.info> wrote:
> On Mon, 12 Dec 2011 04:21:15 -0800, Eelco wrote:
> >> No more, or less, explicit than the difference between "==" and "is".
>
> > == may be taken to mean identity comparison; 'equals' can only mean one
> > thing.
>
> Nonsense. "Equals" can be taken to mean anything the language designer
> chooses, same as "==". There is no language police that enforces The One
> True Meaning Of Equals. In fact, there is no one true meaning of equals.
> Even my tiny Pocket Oxford dictionary lists five definitions.
>
> It is ironic that the example you give, that of identity, is the standard
> definition of equals in mathematics. 2*2 = 4 does not merely say that
> "there is a thing, 2*2, which has the same value as a different thing,
> 4", but that both sides are the same thing. Two times two *is* four. All
> numbers are, in some sense, singletons and equality implies identity.

We are not talking mathemathics, we are talking programming languages.
Identity versus value equality is a well established concept within
its jargon. Within this context, 'equals' and 'is' have clearly
defined meanings. Indeed within broader use, including everyday
language, the distinction is more subtle and therefore less useful,
but obeying a linguistic rule that holds within computer science is
better than making something up just for python, even though its not
the yet better rule that any five year old might grasp.

> > Of course 'formally' these symbols are well defined, but so is
> > brainf*ck
>
> I don't understand your point here.

Hence the above.

> >>  Modulo is hardly an obscure operation. "What's the remainder...?" is a
> >>  simple question that people learn about in primary school.
>
> > So is 'how much wood would a woodchucker chuck if a woodchucker could
> > chuck wood?'. But how often does that concept turn up in your code?
>
> You didn't make a statement about how often modulo turns up in code
> (which is actually quite frequently, and possibly more frequently than
> regular division), but about the obscurity of the operation. Taking the
> remainder is not an obscure operation. The names "modulo" and "modulus"
> may be obscure to those who haven't done a lot of mathematics, but the
> concept of remainder is not. "How many pieces are left over after
> dividing into equal portions" is something which even small children get.

So 'frequency of use' is no valid interpretation of 'obscurity'? Im
not a native speaker, but im pretty sure it is.

> >> And you can blame C for the use of % instead of mod or modulo.
>
> > I didnt know one of Python's design goals was backwards compatibility
> > with C.
>
> Don't be silly. You know full well Python is not backwards compatible
> with C, even if they do share some syntactical features.

Of course I was being silly; I know this use is following a historical
precedent; but id rather see it rectified in the previous version of
python rather than the next. My sillyness was prompted by the
percieved pointlessness of your remark. Of course python is not trying
to be backwards compatible with C; so why bring it up then?

> >>  If you can supply any function at all, what happens if I write this:
>
> > You cannot; only constructors modelling a sequence or a dict, and only
> > in that order. Is that rule clear enough?
>
> But why limit yourself to those restrictive rules?
>
> If I want to collect a sequence of arguments into a string, why shouldn't
> I be allowed to write this?
>
>     def func(parg, str(args)): ...
>
> If I want to sum a collection of arguments, why not write this?
>
>     def func(pargs, sum(args)): ...
>
> Isn't that better than this?
>
>     def func(pargs, *args):
>         args = sum(args)
>         ...
>
> But no. I don't mean those examples to be taken seriously: when you
> answer to your own satisfaction why they are bad ideas, you may be closer
> to understanding why I believe your idea is also a bad idea.

They are bad ideas because they truely do not lead to the execution of
different code, but are merely a reordering, mixing statements in with
a function declaration. I am proposing no such thing; again, the
type(arg) notation I have dropped, and never was meant to have
anything to do with function calling; it is a way of supplying an
optional type constraint, so in analogy with function annotations, I
changed that to arg::type. Again, this has nothing to do with calling
functions on arguments.

First off, type constraints must have some use; all those languages
cant be wrong. Ofcourse, no type constraints also can not be wrong;
see all those other languages. And I obviously dont mean to make type
constraits mandatory in python, all im saying is that optionally
allowing them can open some doors in the right places. The * and **
syntax are also in effect type constraints, saying 'this is a dict to
collect the remaining kwargs in', but in a rather cryptic and
needlessly ungeneral method.

#define a function with args-list and kwargs-attrdict
def func(args::list, kwargs::attrdict)
#unpack the sequence args into positional arguments, and the mapping
kwargs into keyword arguments
func(::args, ::kwargs)

So now that I have explained what my idea actually is, perhaps youd
like to explain again why it is a bad idea?

> >> I believe that your proposal leads to an over-generalisation "call
> >> arbitrary functions when handling parameter lists".
>
> > I hope the above clears that up. It is as much about calling functions
> > as ** is about raising kwargs to the power of.
>
> I don't understand this comment. Nobody has suggested that ** in function
> parameter lists is the exponentiation operator.

No more or less than I suggested this is about calling functions on
input parameters. The syntax might be superficially similar, but thats
all.

> As for "calling functions", how else do you expect to generate a type if
> you don't call the type constructor? One of your early examples was
> something like:
>
> def func(parg, attrdict(kwargs)): ...
>
> If you expect kwargs to be an attrdict, which is not a built-in,
> presumably you will have needed to have defined attrdict as a type or
> function, and this type or function will get called at run time to
> collect the kwargs. That is all.

I would like attrdict to be a builtin, and as long as it isnt, that
specific example probably wouldnt work. But all I wnat python to do is
whatever it is doing to build a dict, but then for another type
instead. Hence, a typeconstraint.

>
> >>  >  head, tuple(tail) = iterable
> >>  In Python 3, that is spelled:
> >>  head, *tail = iterable
> >>  tail = tuple(tail)
>
> > Yes, I know. How is that not a lot more verbose and worse than what I
> > have proposed in all possible ways?
>
> That *specific* syntax, outside of function declarations, is something
> I've often thought might be useful. But if I were to argue its case, I
> would allow arbitrary functions, and treat it as syntactic sugar for:
>
> head, *tail = iterable
> tail = func(tail)  # or possibly func(*tail)
>
> But that's pie-in-the-sky musing. I certainly wouldn't put it in function
> parameter lists. Param lists should be declarations, not code. Apart from
> the most limited collation of args, code belongs inside the body of the
> function, not the param list:

I agree that this is a bad idea; see the above.

> >> head, tail = somestring[0], somestring[1:]
>
> > Well yes, splendid; we can do that with lists too since the dawn of
> > Python. What you are saying here in effect is that you think the
> > head/tail syntax is superfluous; that youd rather see it eliminated than
> > generalized.
>
> No.
>
> It is not "head/tail" syntax, but sequence unpacking, and it has been
> nicely generalised to allow things like this in Python 3:
>
> a, b, c, d, *lump, x, y z = any_iterable
>
> any_iterable isn't limited to a list, str, or other object which supports
> slicing. It can be any object supporting the iteration protocol.
>
> What I'm saying is that there is no need to OVER-generalise this to
> specify the type of *lump within the packing operation. If you want lump
> to be something other that Python's choice, perform the conversion
> yourself.

Oh, so your primary objection is a semantic nitpick, and the latter is
an OVERgeneralization? Why not restrict unpacking only to lists as
RHS, and convert all your iterables to lists first? Who needs this
overgeneralization of allowing any sequence type to be unpacked,
right?

To answer that question: for the same reasons. The conversion is
wasteful; allowing python to do the right thing based on a
typeconstraint is not. Plus, it is less code, and more readable code;
the only rule you have to learn is quite general, which is that :: is
a type constraint annotation; no need to remember specifics, like
'unpacking always returns lists for some arbitrary reason'.



More information about the Python-list mailing list