Verbose and flexible args and kwargs syntax
Steven D'Aprano
steve+comp.lang.python at pearwood.info
Mon Dec 12 21:43:00 EST 2011
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.
A language designer might choose to define equals as an identity test, or
as a looser "values are the same" test where the value of an object or
variable is context dependent, *regardless* of how they are spelled: = ==
=== "is" "equals" or even "flibbertigibbet" if they wanted to be
whimsical. The design might allow types to define their own sense of
equality.
Triangle.equals(other_triangle) might be defined to treat any two
congruent triangles as equal; set equality could be defined as an
isomorphism relation; string equality could be defined as case-
insensitive, or to ignore leading and trailing whitespace. Regardless of
whether you or I *would* make those choices, we *could* make those
choices regardless of how our language spells the equality test.
> Of course 'formally' these symbols are well defined, but so is
> brainf*ck
I don't understand your point here.
>> 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.
>> 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.
C is merely one of many languages which have influenced Python, as are
Haskell, ABC, Pascal, Algol 68, Perl (mostly in the sense of "what not to
do" <wink>), Lisp, and probably many others. It merely happens that C's
use of the notation % for the remainder operation likely influenced
Python's choice of the same notation.
I note that the *semantics* of the operation differs in the two
languages, as I understand that the behaviour of % with negative
arguments is left undefined by the C standard, while Python does specify
the behaviour.
>> I can't imagine what sort of Python code you have seen that you
>> consider 90% attribute access "typical". I've just run the Python
>> tokenizer over my startup.py file, and I get these results:
>
> Yes, that was a hyperbole; but quite an often used construct, is it not?
It's hard, but not quite impossible, to write useful Python code without
it, so yes.
>> 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.
>> 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.
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 don't believe you
>> need this added complication. If you want to your var args as a list,
>> call list(args) inside your function.
>
> We dont strictly 'need' any language construct. Real men use assembler,
> right?
"We're not using assembly" is not a reason to add a feature to a
language. Every feature adds cost to the language:
* harder to implement;
* harder to maintainer;
* larger code base;
* more documentation to be written;
* more tests to be written;
* more for users to learn
etc.
>> > 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:
def foo(a, 2*b+1, c): # double the second arg and add 1
>> 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.
--
Steven
More information about the Python-list
mailing list